線程同步機制
并發(fā):多個線程同時操作同一個對象
線程同步的形成條件:隊列+鎖
隊列
處理多線程問題時,多個線程訪問同一個對象,并且某些線程還想修改這個對象.這時候我們就需要線程同步.線程同步其實就是一種等待機制,多個需要同時訪問比對象的線程進入這個對象的等待池形成隊列,等待前面線程使用完畢,下一個線程再使用。
鎖
由于同一進程的多個線程共享同一塊存儲空間,在帶來方便的同時,也帶來了訪問沖突問題,為了保證數(shù)據(jù)在方法中被訪問時的正確性,在訪問時加入鎖機制synchronized,當一個線程獲得對象的排它鎖,獨占資源,其他線程必須等待,使用后釋放鎖即可·存在以下問題:
- 一個線程持有鎖會導致其他所有需要此鎖的線程掛起;
- 在多線程競爭下,加鎖,釋放鎖會導致比較多的上下文切換和調(diào)度延時,引起性能問題;
- 如果一個優(yōu)先級高的線程等待一個優(yōu)先級低的線程釋放鎖會導致優(yōu)先級倒置,引起性能問題.
三大不安全案例
// 不安全的買票
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
Thread t1 = new Thread(station, "張三");
Thread t2 = new Thread(station, "李四");
Thread t3 = new Thread(station, "王五");
t1.start();
t2.start();
t3.start();
}
}
class BuyTicket implements Runnable {
private int ticket = 10;
private boolean flag = true;
// 買票
public void buy() {
if (ticket <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName()+"獲得"+ticket--);
}
@Override
public void run() {
while (flag) {
buy();
}
}
}

在三人同時去車站買票這個例子中,可能出現(xiàn)兩個人買到同一張票,或者一個人買到負數(shù)的票(兩個人同時對ticket操作)
```java
public class UnsafeDrawing {
public static void main(String[] args) {
Account account = new Account("基金", 100);
Drawing you = new Drawing(account, 50, "you");
Drawing girl = new Drawing(account, 100, "girl");
you.start();
girl.start();
}
}
class Account {
String name;
int balance;
public Account(String name, int balance) {
this.name = name;
this.balance = balance;
}
}
class Drawing extends Thread{
Account account;
int drawingMoney;
int nowMoney = 0;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
if (account.balance < drawingMoney) {
System.out.println(super.getName() + "余額不足");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
account.balance -= drawingMoney;
nowMoney += drawingMoney;
System.out.println(this.getName()+"取錢"+nowMoney);
System.out.println(account.name+"余額為"+account.balance);
}
}

這里同樣是多個線程對同一用戶對操作導致的線程不安全問題,中間我們加入了sleep來模擬網(wǎng)絡延遲
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
在Java的標準庫里,ArrayList是線程不安全的,Vector是線程安全的,可以在這個例子中看出

同步方法
一個實現(xiàn)線程同步的方法是加上synchornized修飾符,包含兩種方法,synchornized方法與synchornized塊;
這種實現(xiàn)方法的弊端就是不停地加鎖釋放鎖非常浪費資源,所以只在需要進行寫操作的時候加鎖就好
注意:synchornized默認鎖的是this,所以我們在第二個案例中,如果對run加鎖鎖住了Drawing對象,但是我們操作的account仍然被同時訪問,導致存款為負數(shù)
我們可以使用同步塊來制定鎖住對象,如下
public void run() {
synchronized (account) {
if (account.balance < drawingMoney) {
System.out.println(super.getName() + "余額不足");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
account.balance -= drawingMoney;
nowMoney += drawingMoney;
}
System.out.println(this.getName()+"取錢"+nowMoney);
System.out.println(account.name+"余額為"+account.balance);
}
第三個案例同樣如此
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
System.out.println(list.size());
}
}
CopyOnWriteArrayList
juc提供的一個安全的集合
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}

浙公網(wǎng)安備 33010602011771號