現(xiàn)在計算機和智能手機都是多核處理器蟹腾,為了更好地發(fā)揮設(shè)備的性能,提高應(yīng)用程序的體驗性笋鄙,多線程是必不可少的技術(shù)。線程之間不是孤立的怪瓶,它們共享進程的資源和數(shù)據(jù)萧落,彼此之間還需要進行通信和協(xié)作,最典型的例子就是「生產(chǎn)者-消費者模型」洗贰。下面先介紹 wait/notify 機制和 Lock/Condition 機制找岖,然后用兩個線程交替打印奇偶數(shù)。
1. wait/notify
wait 和 notify 是 Object 類的兩個方法敛滋,理解起來還是有些復(fù)雜的许布。它和多線程同步有關(guān)系,個人覺得放在 Object 類不太合理绎晃,可能是歷史遺留問題吧蜜唾。每個對象都有一把鎖(monitor),在進入同步方法或代碼塊之前庶艾,當(dāng)前線程需要先獲取對象鎖袁余,然后才能執(zhí)行同步塊的代碼,完成后釋放對象鎖咱揍。鎖可以理解為唯一的憑證颖榜,有了它就能入場,而且獨占所有的資源煤裙,立場就得交出去掩完。
wait 方法的作用是使當(dāng)前線程釋放對象鎖,并進入等待狀態(tài)硼砰,不再往下執(zhí)行且蓬。當(dāng)其他線程調(diào)用對象的 notify/notifyAll 時,會喚醒等待的線程题翰,等到其他線程釋放鎖后恶阴,被喚醒的現(xiàn)象將繼續(xù)往下執(zhí)行。notify 隨機喚醒一個等待的線程遍愿,notifAll 喚醒所有等待的線程存淫。注意:wait 和 notify 都需要在拿到對象鎖的情況下調(diào)用。下面是 wait 的標(biāo)準(zhǔn)使用方法(來自 《Effective Java》一書):
synchronized (obj) {
while (condition does not hold) {
obj.wait(); // release lock and reacquire on wakeup
// perform action appropriate to condition
}
}
每個鎖對象都有兩個隊列:就緒隊列和阻塞隊列沼填。就緒隊列存儲了已經(jīng)就緒(將要競爭鎖)的線程桅咆,阻塞隊列存儲了被阻塞的線程。當(dāng)阻塞線程被喚醒后坞笙,才會進入就緒隊列岩饼,然后等待 CPU 的調(diào)度荚虚;反之,當(dāng)一個線程被阻塞后籍茧,就會進入阻塞隊列版述,等待被喚醒。
舉個例子寞冯,線程 A 在執(zhí)行任務(wù)渴析,它等待線程 B 做完某個操作,才能往下執(zhí)行吮龄,這就可以用 wait/notify 實現(xiàn)俭茧。
public void start() {
new Thread(new TaskA()).start();
new Thread(new TaskB()).start();
}
private final Object lock = new Object();
private boolean finished;
private class TaskA implements Runnable {
@Override
public void run() {
synchronized (lock) {
System.out.println("線程 A 拿到鎖了,開始工作");
while (!finished) {
try {
System.out.println("線程 A 釋放了鎖漓帚,進入等待狀態(tài)");
lock.wait();
System.out.println("線程 A 收到信號母债,繼續(xù)工作");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("線程 A 釋放了鎖");
}
}
private class TaskB implements Runnable {
@Override
public void run() {
synchronized (lock) {
System.out.println("線程 B 拿到了鎖,開始工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------------");
System.out.println("線程 B 發(fā)信號了尝抖,完成工作");
finished = true;
lock.notify();
}
System.out.println("線程 B 釋放了鎖");
}
}
/* 打诱泵恰:
線程 A 拿到鎖了,開始工作
線程 A 釋放了鎖昧辽,進入等待狀態(tài)
線程 B 拿到了鎖衙熔,開始工作
-----------------------
線程 B 發(fā)信號了,完成工作
線程 B 釋放了鎖
線程 A 收到信號奴迅,繼續(xù)工作
線程 A 釋放了鎖
*/
2. Lock/Condition
Condition 可以看作 Object 的 wait/notify 的替代方案青责,同樣用來實現(xiàn)線程間的協(xié)作。與使用 wait/notify 相比取具,Condition的 await/signal 更加靈活、安全和高效扁耐。Condition 是個接口暇检,基本的方法就是 await() 和 signal()。Condition 依賴于 Lock 接口婉称,生成一個 Condition 的代碼是 lock.newCondition() 块仆。 需要注意 Condition 的 await()/signal() 使用都必須在lock.lock() 和 lock.unlock() 之間才可以,Conditon 和 Object 的 wait/notify 有著天然的對應(yīng)關(guān)系:
- Conditon 中的 await() 對應(yīng) Object 的 wait()王暗;
- Condition 中的 signal() 對應(yīng) Object 的 notify()悔据;
- Condition 中的 signalAll() 對應(yīng) Object 的 notifyAll();
舉個例子俗壹,使用 Condition 實現(xiàn)和上面的功能科汗。
public void start() {
new Thread(new TaskC()).start();
new Thread(new TaskD()).start();
}
private Lock reentrantLock = new ReentrantLock();
private Condition condition = reentrantLock.newCondition();
private class TaskC implements Runnable {
@Override
public void run() {
reentrantLock.lock();
System.out.println("線程 C 拿到了鎖,開始工作");
try {
System.out.println("線程 C 釋放了鎖绷雏,進入等待狀態(tài)");
condition.await();
System.out.println("線程 C 收到信號头滔,繼續(xù)工作");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("線程 C 釋放了鎖");
reentrantLock.unlock();
}
}
}
private class TaskD implements Runnable {
@Override
public void run() {
reentrantLock.lock();
System.out.println("線程 D 拿到了鎖怖亭,開始工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------------");
try {
System.out.println("線程 D 發(fā)信號了,完成工作");
condition.signal();
} finally {
System.out.println("線程 D 釋放了鎖");
reentrantLock.unlock();
}
}
}
/*打永ぜ臁:
線程 C 拿到了鎖兴猩,開始工作
線程 C 釋放了鎖,進入等待狀態(tài)
線程 D 拿到了鎖早歇,開始工作
-----------------------
線程 D 發(fā)信號了倾芝,完成工作
線程 D 釋放了鎖
線程 C 收到信號,繼續(xù)工作
線程 C 釋放了鎖
*/
相比 Object 的 wait/notify箭跳,Condition 有許多優(yōu)點:
Condition 可以支持多個等待隊列蛀醉,因為一個 Lock 實例可以綁定多個 Condition
Condition 支持等待狀態(tài)下不響應(yīng)中斷
Condition 支持當(dāng)前線程進入等待狀態(tài),直到將來的某個時間
3. 兩個線程交替打印奇偶數(shù)
使用 wait/notify:
public void printNumber() {
new Thread(new EvenTask()).start();
new Thread(new OddTask()).start();
}
private int number = 10;
private final Object numberLock = new Object();
private class EvenTask implements Runnable {
@Override
public void run() {
synchronized (numberLock) {
while (number >= 0 && (number & 1) == 0) {
System.out.println("偶數(shù): " + (number--));
numberLock.notify();
try {
numberLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
private class OddTask implements Runnable {
@Override
public void run() {
synchronized (numberLock) {
while (number >= 0 && (number & 1) == 1) {
System.out.println("奇數(shù): " + (number--));
numberLock.notify();
try {
numberLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
使用 Lock/Condition:
public void printNumber() {
new Thread(new EvenTask()).start();
new Thread(new OddTask()).start();
}
private int number = 10;
private Condition evenCondition = reentrantLock.newCondition();
private Condition oddCondition = reentrantLock.newCondition();
private class EvenTask implements Runnable {
@Override
public void run() {
reentrantLock.lock();
try {
while (number >= 0 && (number & 1) == 0) {
System.out.println("偶數(shù): " + (number--));
oddCondition.signal();
evenCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
private class OddTask implements Runnable {
@Override
public void run() {
reentrantLock.lock();
try {
while (number >= 0 && (number & 1) == 1) {
System.out.println("奇數(shù): " + (number--));
evenCondition.signal();
oddCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
運行后打有坡搿:
偶數(shù): 10
奇數(shù): 9
偶數(shù): 8
奇數(shù): 7
偶數(shù): 6
奇數(shù): 5
偶數(shù): 4
奇數(shù): 3
偶數(shù): 2
奇數(shù): 1
偶數(shù): 0
最后拯刁,建議使用 Lock/Condition 代替 Object 的 wait/notify,因為前者是 java.util.concurrent 包下的接口逝段,對于同步更簡潔高效垛玻,多線程操作優(yōu)先選用 JUC 包的類。
參考文章: