Java併發程式設計基礎(二)
前言
熬過了考試周,終於可以繼續我的部落格分享了。大笑,哈哈,裝個X。
本篇部落格主要記錄的是自己使用執行緒間通訊的過程。
執行緒間的通訊
併發常常伴隨著多執行緒的執行,然後這就會涉及到多個執行緒間的配合工作,即執行緒間的通訊。
Volatile和Synchronized
—Volatile—
我們都知道,Java中是支援多個執行緒同時訪問一個物件或者物件的成員變數,由於每個執行緒是可以擁有這個變數的拷貝(其中拷貝和引用需要注意區分),儘管物件及其成員變數是分配的記憶體是在共享記憶體中的(這裡需要檢視Java的記憶體分配),但每個執行緒還是可以拷貝一份的。這種設計加快了程式的執行,同時也會帶來一定的問題。
例如,一個物件A它被分配在共享記憶體中,執行緒t1對他進行了記憶體的拷貝,並對A的成員變數進行了一定的修改,還沒來得及更新到共享記憶體中,此時執行緒t2也需要訪問A,實際上他是需要訪問被t1修改後的物件A,而此時執行緒t1還沒來的及更新到共享記憶體中,這時便會帶來一定的問題。
Volatile便是出來解決上述問題。Volatile用來修飾字段(成員變數),程式中對該修飾的變數的訪問都是從共享記憶體中獲取,並且對其進行的改變必須要同步重新整理回共享記憶體,以保證所有執行緒對修飾的變數的訪問的可見性(感知被volatile修飾的變數的變化)。但這也會降低程式的執行效率。此時的併發級別是無障礙
—Synchronized—
Java中對臨界區的訪問,常常可使用Synchronized,Synchronized用於修飾方法或者程式碼塊,它用於確保同一個時刻,只能有一個執行緒處於方法或者修飾的程式碼塊中,從而確保執行緒對變數的訪問是可見和排他的。此時的併發級別是阻塞(具體檢視初始Java併發)
等待/通知機制
通知機制可以簡單的理解為一個執行緒修改了一個變數的值,讓另外一個執行緒能感知該執行緒對變數進行了修改,並作出相應的操作。
如何實現通知機制,需要使用到Java API 中的wait()和notify()函式,並結合關鍵字Synchronized
示例
//1.執行緒A呼叫wait方法
synchronized(obj) {
while(!condition) {
obj.wait();
}
obj.doSomething();
}
//2.執行緒B呼叫notify方法
synchronized(obj) {
condition = true;
obj.notify();
}
//其中虛擬碼中的obj扮演的是內建鎖的角色。
//說明1:java的內建鎖:每個java物件都可以用做一個實現同步的鎖,這些鎖成為內建鎖。執行緒進入同步程式碼塊或方法的時候會自動獲得該鎖,在退出同步程式碼塊或方法時會釋放該鎖。獲得內建鎖的唯一途徑就是進入這個鎖的保護的同步程式碼塊或方法。
//說明2:java內建鎖是一個互斥鎖,這就是意味著最多隻有一個執行緒能夠獲得該鎖,當執行緒A嘗試去獲得執行緒B持有的內建鎖時,執行緒A必須等待或者阻塞,知道執行緒B釋放這個鎖,如果B執行緒不釋放這個鎖,那麼A執行緒將永遠等待下去
理解:
A表示執行緒A,B表示執行緒B
1.呼叫obj的wait(), notify()方法前,必須獲得obj鎖,也就是該方法的呼叫必須寫在synchronized(obj) {…} 程式碼段內。獲得內建鎖的唯一途徑就是進入這個鎖的保護的同步程式碼塊或方法。
2.當執行緒B呼叫obj.notify/notifyAll的時候,執行緒B正持有obj鎖,因此, A雖被喚醒,但是仍無法獲得obj鎖。直到B退出synchronized塊,釋放obj鎖後,A才有機會獲得鎖繼續執行。
3. 當執行緒A呼叫obj.wait()後,執行緒A進入阻塞狀態,並釋放obj的鎖。否則執行緒B無法獲得obj鎖,也就無法在synchronized(obj) {…} 程式碼段內喚醒A。
4.當執行緒B呼叫obj.notify方法時,便會通知在obj物件上等待的執行緒A,使其從obj.wait()方法返回後,執行緒A 需要再次獲得obj鎖 ,才能繼續執行。
5.如果有執行緒A1,A2,A3都在obj.wait(),則B呼叫obj.notify()只能喚醒A1,A2,A3中的一個(具體哪一個由JVM決定)。
6.obj.notifyAll()則能全部喚醒執行緒A1,A2,A3,但是要繼續執行obj.wait()的下一條語句,必須獲得obj鎖,因此,A1,A2,A3只有一個有機會獲得鎖繼續執行,例如執行緒A1獲得物件鎖,其餘的需要等待A1釋放obj鎖之後才能繼續執行。
其他常見的通訊方式
JDK API中的join()函式
thread.Join意思是把指定的執行緒加入到當前執行緒,可以將兩個交替執行的執行緒合併為順序執行的執行緒。
public class TestJoin implements Runnable{
public static void main(String[] sure) throws InterruptedException {
Thread t = new Thread(new TestJoin());
long start = System.currentTimeMillis();
t.start();//t執行緒
t.join();//t執行緒join到主執行緒main中,所以需要依次順序執行兩個執行緒
System.out.println(System.currentTimeMillis()-start);//打印出時間間隔
System.out.println("Main finished");//列印主執行緒結束
}
@Override
public void run() {
//dosomething ...
System.out.println("TestJoin coming");
}
}
ThreadLocal的使用
ThreadLocal,是執行緒變數,是一個map形式的儲存結構,其中key為ThreadLocal物件,Value為任意儲存的變數。它是被附帶線上程上的,一個執行緒可以根據ThreadLocal物件查詢到繫結到這個執行緒上的一個值。參考ThreadLocal
ThreadLocal主要用於執行緒間特殊的資料的隔離,如何用好ThreadLocal需要明確以下幾個問題,網路上也有很多ThreadLocal介紹的,建議還是需要自己去閱讀原始碼。
1.ThreadLocal的在Java多執行緒程式設計中扮演一個什麼角色,起到一個什麼作用。
2.ThreadLocal儲存不同執行緒內的變數是如何做到隔離的。
3.THreadLocal如何高效的儲存資料,並對儲存的資料進行操作的?
4.ThreadLocal記憶體洩漏的問題,ThreadLocal對執行緒變數儲存涉及到很多變數,記憶體回收的時候,是如何有效的做到避免記憶體洩漏。