推薦學習:蘑菇街Java大牛純手打肛出的一份多線程文檔,請別丟進收藏夾吃灰
注,本篇只是解析基本概念,用作面試應答,非深入
對於Java併發編程,一般來說有以下的關注點:
- 線程安全性,正確性。
- 線程的活躍性(死鎖,活鎖)
- 性能
其中線程的安全性問題是首要解決的問題,線程不安全,運行出來的結果和預期不一致,那就連基本要求都沒達到了。
保證線程的安全性問題,本質上就是保證線程同步,實際上就是線程之間的通信問題。我們知道,在操作系統中線程通信有以下幾種方式:
- 信號量
- 信號
- 管道
- 共享內存
- 消息隊列
- socket
java中線程通信主要使用共享內存的方式。共享內存的通信方式首先要關注的就是可見性和有序性。而原子性操作一般都是必要的,所以主要關注這三個問題。
1.原子性
原子性是指操作是不可分的。其表現在於對於共享變量的某些操作,應該是不可分的,必須連續完成。例如a++,對於共享變量a的操作,實際上會執行三個步驟:
- 讀取變量a的值
- a的值+1
- 將值賦予變量a 。
這三個操作中任何一個操作過程中,a的值被人篡改,那麼都會出現我們不希望出現的結果。所以我們必須保證這是原子性的。Java中的鎖的機制解決了原子性的問題。
2.可見性
可見性是值一個線程對共享變量的修改,對於另一個線程來說是否是可以看到的。
爲什麼會出現這種問題呢?
我們知道,java線程通信是通過共享內存的方式進行通信的,而我們又知道,爲了加快執行的速度,線程一般是不會直接操作內存的,而是操作緩存。
java線程內存模型:
實際上,線程操作的是自己的工作內存,而不會直接操作主內存。如果線程對變量的操作沒有刷寫會主內存的話,僅僅改變了自己的工作內存的變量的副本,那麼對於其他線程來說是不可見的。而如果另一個變量沒有讀取主內存中的新的值,而是使用舊的值的話,同樣的也可以列爲不可見。
對於jvm來說,主內存是所有線程共享的java堆,而工作內存中的共享變量的副本是從主內存拷貝過去的,是線程私有的局部變量,位於java棧中。
那麼我們怎麼知道什麼時候工作內存的變量會刷寫到主內存當中呢?
這就涉及到java的happens-before關係了。
在JMM中,如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係。
簡單來說,只要滿足了happens-before關係,那麼他們就是可見的。
例如:
線程A中執行i=1,線程B中執行j=i。如果線程A的操作和線程B的操作滿足happens-before關係,那麼j就一定等於1,否則j的值就是不確定的。
happens-before關係如下:
- 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作;
- 鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作;
- volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作;
- 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C;
- 線程啓動規則:Thread對象的start()方法先行發生於此線程的每個一個動作;
- 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生;
- 線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行;
- 對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始;
從上面的happens-before規則,顯然,一般只需要使用volatile關鍵字,或者使用鎖的機制,就能實現內存的可見性了。
3.有序性
有序性是指程序在執行的時候,程序的代碼執行順序和語句的順序是一致的。
爲什麼會出現不一致的情況呢?
這是由於重排序的緣故。
在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。
舉個例子:
線程A:
context = loadContext();
inited = true;
線程B:
while(!inited ){
sleep
}
doSomethingwithconfig(context);
如果線程A發生了重排序:
inited = true;
context = loadContext();
那麼線程B就會拿到一個未初始化的content去配置,從而引起錯誤。
因爲這個重排序對於線程A來說是不會影響線程A的正確性的,而如果loadContext()方法被阻塞了,爲了增加Cpu的利用率,這個重排序是可能的。
如果要防止重排序,需要使用volatile關鍵字,volatile關鍵字可以保證變量的操作是不會被重排序的。