Java并發(fā)編程之二徹底理解Java的wait和Notify機(jī)制
簡單介紹
- wait方法
wait方法的作用是使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,它是Object類的方法,該方法用來將當(dāng)前線程置入等待隊列中,并且在wait所在的代碼行處停止執(zhí)行,直到接到通知或被中斷為止。
該方法只能在同步方法或同步塊中調(diào)用(即需要先獲得對象的監(jiān)視器鎖,一般來說在 synchronized 代碼塊或者synchronized修飾的方法中使用),否則拋出異常
IllegalMonitorStateException。
在A線程中調(diào)用Lock對象的wait方法以后,會釋放Lock對象的監(jiān)視器鎖,同時將A線程放置于Lock對象的等待隊列,A線程進(jìn)入WAITING狀態(tài)(Thread狀態(tài)查看系列一)。
- notify/notifyAll方法
notify/notifyAll方法的作用是喚醒執(zhí)行對象的等待列表中的一個/所有線程,將其喚醒繼續(xù)工作。
同樣的,notify/notifyAll方法也只能在同步方法或同步塊中調(diào)用,即在調(diào)用前,線程也必須獲得該對象的監(jiān)視器鎖。
在B線程中調(diào)用Lock對象的notify/notifyAll方法以后,Lock對象等待隊列中的A線程從WAITING狀態(tài)進(jìn)入BLOCKED狀態(tài),而不是直接進(jìn)入RUNNABLE狀態(tài)。只有等B線程釋放了Lock對象的監(jiān)視器鎖,并且A線程拿到以后,才進(jìn)入到RUNNABLE狀態(tài)。所以在編程中,盡量在使用了notify/notifyAll() 后立即釋放對象的監(jiān)視器鎖,以便讓其他線程獲得鎖進(jìn)入RUNNABLE狀態(tài)。
- 其他注意事項
wait、notify/notifyAll 方法是Object的本地final方法,無法被重寫。
notify 和wait 的順序不能錯,如果A線程先執(zhí)行notify方法,B線程再執(zhí)行wait方法,那么B線程是無法被喚醒的。
實例詳解
- public class WaitThread extends Thread {
- private Object lock;
- public WaitThread(Object lock) {
- this.lock = lock;
- }
- @Override
- public void run() {
- synchronized (lock) {
- System.out.println("WaitThread開始執(zhí)行run方法");
- try {
- System.out.println("WaitThread開始執(zhí)行wait方法");
- lock.wait();
- System.out.println("WaitThread被喚醒");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public class NotifyThread extends Thread {
- private Object lock;
- public NotifyThread(Object lock) {
- this.lock = lock;
- }
- @Override
- public void run() {
- System.out.println("NotifyThread開始執(zhí)行run方法");
- try {
- System.out.println("NotifyThread睡眠2秒");
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (lock) {
- System.out.println("NotifyThread開始執(zhí)行notify方法");
- lock.notify();
- try {
- System.out.println("NotifyThread執(zhí)行notify方法后繼續(xù)睡眠2秒");
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("NotifyThread睡眠2秒結(jié)束,并釋放對象監(jiān)視器鎖");
- }
- }
- }
- public class Main {
- public static void main(String[] args) {
- Object lock = new Object();
- // 創(chuàng)建2個線程
- WaitThread waitThread = new WaitThread(lock);
- NotifyThread notifyThread = new NotifyThread(lock);
- // 啟動他們
- waitThread.start();
- notifyThread.start();
- }
- }
- NotifyThread開始執(zhí)行run方法
- WaitThread開始執(zhí)行run方法
- WaitThread開始執(zhí)行wait方法
- NotifyThread睡眠2秒
- NotifyThread開始執(zhí)行notify方法
- NotifyThread執(zhí)行notify方法后繼續(xù)睡眠2秒
- NotifyThread睡眠2秒結(jié)束,并釋放對象監(jiān)視器鎖
- WaitThread被喚醒
WaitThread在拿到lock的監(jiān)視器鎖以后調(diào)用wait方法。
NotifyThread在啟動以后先睡眠2秒,保證了notify方法在wait方法后面。
NotifyThread執(zhí)行notify方法后繼續(xù)睡眠2秒,這個時候NotifyThread并沒有釋放Lock的監(jiān)視器鎖,因此WaitThread處于BLOCKED狀態(tài)并沒有被真正被喚醒。
NotifyThread睡眠2秒結(jié)束,并釋放對象監(jiān)視器鎖,這個時候NotifyThread取到Lock的監(jiān)視器鎖并進(jìn)入到RUNNABLE狀態(tài)。
進(jìn)階思考
如果A線程獲得了多個對象的監(jiān)視器鎖,然后調(diào)用其中1個對象的wait方法,是釋放所有對象的鎖還是只釋放調(diào)用的那個對象的鎖呢?
我們一起通過一個示例來進(jìn)行一下測試。
- public class WaitThread extends Thread {
- private Object lock;
- private Object other;
- public WaitThread(Object lock, Object other) {
- this.lock = lock;
- this.other = other;
- }
- @Override
- public void run() {
- synchronized (lock) {
- synchronized (other) {
- System.out.println("WaitThread開始執(zhí)行run方法");
- try {
- System.out.println("WaitThread開始執(zhí)行wait方法");
- lock.wait();
- System.out.println("WaitThread被喚醒");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- public class NotifyThread extends Thread {
- private Object lock;
- private Object other;
- public NotifyThread(Object lock, Object other) {
- this.lock = lock;
- this.other = other;
- }
- @Override
- public void run() {
- System.out.println("NotifyThread開始執(zhí)行run方法");
- try {
- System.out.println("NotifyThread睡眠2秒");
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (lock) {
- System.out.println("NotifyThread獲得lock鎖");
- synchronized (other) {
- System.out.println("NotifyThread獲得other鎖");
- System.out.println("NotifyThread開始執(zhí)行notify方法");
- lock.notify();
- try {
- System.out.println("NotifyThread執(zhí)行notify方法后繼續(xù)睡眠2秒");
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("NotifyThread睡眠2秒結(jié)束,并釋放對象監(jiān)視器鎖");
- }
- }
- }
- }
- public class Main {
- public static void main(String[] args) {
- Object lock = new Object();
- Object other = new Object();
- // 創(chuàng)建2個線程
- WaitThread waitThread = new WaitThread(lock, other);
- NotifyThread notifyThread = new NotifyThread(lock, other);
- // 啟動他們
- waitThread.start();
- notifyThread.start();
- }
- }
- WaitThread開始執(zhí)行run方法
- WaitThread開始執(zhí)行wait方法
- NotifyThread開始執(zhí)行run方法
- NotifyThread睡眠2秒
- NotifyThread獲得lock鎖
WaitThread線程拿到lock和other的對象鎖以后,執(zhí)行了lock的wait方法。NotifyThread線程在睡眠2秒后,僅拿到了lock鎖,說明wait方法只釋放了被執(zhí)行對應(yīng)的鎖,這樣就造成了死鎖。
因此如果使用wait和notify機(jī)制時,一定要確認(rèn)Wait線程和Notify線程獲取對象鎖的情況,盡量避免在獲取多個對象鎖的情況下使用,防止造成死鎖問題。