執行緒間通信
- 1.1Volatile和synchronized關鍵字
- 1.2 等待/通知機制
- 1.3 小結
執行緒開始運行,擁有自己的堆疊空間,每個運行中執行緒,如果僅僅是孤立地運行,那么沒有一點點的價值,或者說價值很少,如果每個執行緒能夠互相配合
完成作業,這將會帶來巨大的價值,
1.1Volatile和synchronized關鍵字
Java支持多個執行緒同時訪問一個物件或者物件的成員變數,由于每個執行緒可以擁有這個執行緒這個變數的拷貝,所以程式在執行程序中,一個執行緒看到的變數并不一定是最新的,(雖然物件及成員變數分配的記憶體實在共享記憶體中的,但是每個執行的執行緒還是可以擁有一份拷貝,這樣做的目的是加速程式的執行這是現代多核處理器的一個顯著特性)
關鍵字volatile可以用來修飾欄位,告知程式任何對該變數的訪問都要從共享記憶體中獲取,而對他的改變必須同步重繪會共享記憶體,它能保證所有執行緒對變數訪問的可見性,
關鍵字synchronized可以修飾方法或者同步塊的 形式來進行使用,它主要確保多個執行緒在同一時刻,只能有一個執行緒處于方法或者同步塊中,它保證了執行緒對變數訪問的可見性和排他性,
我們通過javap工具查看生產的class檔案資訊來分析synchronize關鍵字 的實作細節,
public class Synchronized {
public static void main(String[] args) {
// 對synchronized Class物件進行加鎖
synchronized (Synchronized.class) {
}
// 靜態同步方法,對synchronized Class物件進行加鎖
m();
}
public static synchronized void m() {
}
}
在Synchronized.java同級目錄先執行javac Synchronized.java,在執行 javap -v Synchronized.class,部分相關輸出如下:
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #2 // class com/example/demo/thread/Synchronized
2: dup
3: astore_1
4: monitorenter // 監視器進入:獲取鎖
5: aload_1
6: monitorexit // 監視器退出:釋放鎖
7: goto 15
10: astore_2
11: aload_1
12: monitorexit
13: aload_2
14: athrow
15: invokestatic #3 // Method m:()V
18: return
public static synchronized void m();
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
上面class資訊總,對于同步塊的實作使用了monitorenter和monitorexit指令,而同步方法則是依靠方法修飾符上的 ACC_SYNCHRONIZED來完成的,這兩種方式本質都是對一個物件的監視器(monitor)進行獲取,同時這個獲取是排他的,也就是同一時刻只能有一個執行緒獲取到有synchronized所保護物件的監視器,
任意一個物件都擁有自己的監視器,當這個物件有同步代碼塊或者這個物件的同步方法呼叫時,執行方法的執行緒必須先獲取到該物件的監視器才能進入同步塊或者同步方法,而沒有獲取到監視器的執行緒將會被祖冊在同步代碼塊和同步方法的入口處,進入BOLOCKED狀態,
物件、物件的監視器、同步佇列和執行執行緒之間的關系,

從圖中可以看到,任意執行緒對Object的訪問,首先要獲得Object的監視器,如果獲取失敗,該執行緒就進入同步狀態,執行緒狀態變為BLOCKED,當Object的監視器占有者釋放后,在同步佇列中得執行緒就會有機會重新獲取該監視器,
1.2 等待/通知機制
等待/通知的相關方法是任意Java物件都具備的,因為這些方法被定義在所有物件的超類java.lang.Object上,方法和描述如下表所示,

等待/通知機制,是指一個執行緒A呼叫了物件O的wait()方法進入等待狀態,而另一個執行緒B呼叫了物件O的notify()或者notifyAll()方法,執行緒A收到通知后從物件O的wait()方法回傳,進而執行后續操作,
創建兩個執行緒——WaitThread和NotifyThread,前者檢查flag否為false,如果符合要求,進行后續操作,否則在lock上等待,后者在睡眠了一段時間后對lock進行通知,
public class WaitAndNotifyThread {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
SleepUtils.second(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
@Override
public void run() {
//加鎖,擁有lock的Monitor
synchronized (lock) {
//當條件不滿足時,繼續wait,同時釋放了lock鎖
while (flag) {
System.out.println(Thread.currentThread() + " flag is true. wait@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//條件滿足時,完成作業
System.out.println(Thread.currentThread() + " flag is false. running@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
//加鎖,擁有lock的Monitor
synchronized (lock) {
//獲取lock的鎖然后進行通知,通知時不會釋放lock的鎖
//直到當前執行緒釋放了lock后,WaitThread才能從wait方法中回傳
System.out.println(Thread.currentThread() + " hold lock. notify@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
//再次加鎖
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again. sleep@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}
運行結果
Thread[WaitThread,5,main] flag is true. wait@ 00:03:06
Thread[NotifyThread,5,main] hold lock. notify@ 00:03:07
Thread[NotifyThread,5,main] hold lock again. sleep@ 00:03:12
Thread[WaitThread,5,main] flag is false. running@ 00:03:17
上述例子主要說明了呼叫wait()、notify()以及notifyAll()時需要注意的細節,如下,
- 使用wait()、notify()和notifyAll()時需要先對呼叫物件加鎖,
- 呼叫wait()方法后,執行緒狀態由RUNNING變為WAITING,并將當前執行緒放置到物件的等待佇列,
- notify()或notifyAll()方法呼叫后,等待執行緒依舊不會從wait()回傳,需要呼叫notify()或notifAll()的執行緒釋放鎖之后,等待執行緒才有機會從wait()回傳,
- notify()方法將等待佇列中的一個等待執行緒從等待佇列中移到同步佇列中,而notifyAll()方法則是將等待佇列中所有的執行緒全部移到同步佇列,被移動的執行緒狀態由WAITING變為BLOCKED,
- 從wait()方法回傳的前提是獲得了呼叫物件的鎖,
從上述細節中可以看到,等待/通知機制依托于同步機制,其目的就是確保等待執行緒從wait()方法回傳時能夠感知到通知執行緒對變數做出的修改,

WaitThread首先獲取了物件的鎖,然后呼叫物件的wait()方法,從而放棄了鎖并進入了物件的等待佇列WaitQueue中,進入等待狀態,由于WaitThread釋放了物件的鎖,NotifyThread隨后獲取了物件的鎖,并呼叫物件的notify()方法,將WaitThread從WaitQueue移到SynchronizedQueue中,此時WaitThread的狀態變為阻塞狀態,NotifyThread釋放了鎖之后,WaitThread再次獲取到鎖并從wait()方法回傳繼續執行,
1.3 小結
本篇文章主講了Volatile和Synchronized關鍵字的概念(后面會寫這兩個關鍵字的原理以及特性),執行緒間通信的方法,希望對大家有用,感謝觀看,歡迎大家評論區交流探討,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/278459.html
標籤:java
上一篇:【每日藍橋】59、一八年省賽Java組真題“日志統計”
下一篇:mybatis練習-獲取擁有“普通用戶”角色的所有用戶資訊,要求查詢結果除了包含用戶自身資訊,還包括角色名和角色創建時間。
