多線程存在的問題
多線程運用得好可以大大提高系統的性能。但是使用不當也會對系統造成毀滅性災難。
- 線程安全問題。多個線程操作共享數據時,會產生線程安全問題。導致讀取髒數據或者丟失更新等問題
- 線程活性問題。由於程序問題導致一個線程一直處於非Runnable狀態或者處於Runnable狀態但執行的任務沒有緊張稱爲線程活性問題。例如:兩個線程,線程1需要先佔用鎖1,再佔用鎖2。線程2需要先佔用鎖2,再佔用鎖1。這是如果線程1佔用了鎖1,線程2佔用了鎖2。他們都佔用了對方需要的鎖,雙方都阻塞等待對方的鎖釋放,導致死鎖。
- 上下文切換。線程切換引起的上下文切換,會增加系統消耗。
線程安全問題
線程安全問題是多個線程在操作共享數據引起的。要保證線程安全,就需要保證對共享數據的操作有三個性質:原子性,可見性和有序性。
原子性
原子性是指涉及共享數據的操作對別的線程是不可分割的。即其他線程只能看到該操作未發生或者已經結束。
注意 i++ 並不是原子性操作,i++實際上是一個`read-modify-write`操作。
1. 先讀取出i的值
2. 修改i的值
3. 寫回內存
可見性
可見性是指一個線程對共享數據修改後,其他線程可以看到修改後的值。
1. 由於java內存模型中,每個線程都有一個工作內存。在對共享數據進行修改和讀取時,
是先對工作內存中的數據進行操作。所以其他線程讀取的共享變量可能是髒數據,無法保證可見性。
2. 重排序導致的有序性問題也是影響可見性的重要因素。
Java內存模型 java內存模型,簡稱JMM。java線程之間的通信是通過JMM控制的。JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存中,每個線程都有其工作內存,存有共享變量的副本。線程對共享變量的讀寫都是先對工作內存進行,工作內存在將共享變量和內存同步。
工作內存是抽象概念,並非真實存在。它蘊含了緩存,寫緩存區,寄存器還有其他硬件等。
有序性
JIT編譯器爲了優化系統,會對代碼進行重排序。重排序按照as-if-serial語義,保證重排序後在單線程時運行結果是一樣的。但是多線程時,無法保證有序性。
代碼經過各級重排序優化再最終執行
happens-before規則是JMM對多線程重排序的約束規則,遵循happens-before規則的重排序不會改變多線程的執行結果。
int a=1; //A
int b=3; //B
int c=a+b; //C
A happens-before B(非必須)
A happens-before C
B happens-before C
JMM對happens-before的定義:
- 如果一個操作happens-before另一個操作,那麼操作一的執行結果對第二個操作時可見的,並且第一個操作執行順序在第二個操作之前。
- 兩個操作如果存在happens-before規則,並不意味者java平臺會按照happens-before的執行順序執行。如果重排序的執行結果和按happens-before順序執行的結果一致的話,jmm允許這種排序。
規則1是JMM對程序員的保證,而規則2是JMM對編譯器和處理器重排序的約束
下面是happens-before原則規則:
- 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作;
- 鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作;
- volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作;
- 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C;
- 線程啓動規則:Thread對象的start()方法先行發生於此線程的每個一個動作;
- 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生;
- 線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行;
- 對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始;
happens-before規則也保證了可見性,先執行的操作結果對後面執行的操作是可見的。
happens-before推導
private volatile boolean flag;
private int i;
public void read(){
i=1; //1
flag=true; //2
}
public void write(){
if(flag){ // 3
int j=i; //4
}
}
- 根據程序規則,1 happens-before 2 ; 3 happens-before 4
- 根據volatile規則,2 happens-before 3
- 根據傳遞性規則,由 1 happens-before 2,2 happens-before 3,3 happens-before 4 => 1 happens before 4
線程安全解決方案
多線程安全問題是因爲多個線程同時操作共享變量,缺乏同步機制來協調線程間數據的訪問和活動。jdk提供了鎖,volatile關鍵字等線程同步機制