二、多線程存在的問題和Java內存模型

多線程存在的問題

多線程運用得好可以大大提高系統的性能。但是使用不當也會對系統造成毀滅性災難。

  1. 線程安全問題。多個線程操作共享數據時,會產生線程安全問題。導致讀取髒數據或者丟失更新等問題
  2. 線程活性問題。由於程序問題導致一個線程一直處於非Runnable狀態或者處於Runnable狀態但執行的任務沒有緊張稱爲線程活性問題。例如:兩個線程,線程1需要先佔用鎖1,再佔用鎖2。線程2需要先佔用鎖2,再佔用鎖1。這是如果線程1佔用了鎖1,線程2佔用了鎖2。他們都佔用了對方需要的鎖,雙方都阻塞等待對方的鎖釋放,導致死鎖。
  3. 上下文切換。線程切換引起的上下文切換,會增加系統消耗。

線程安全問題

線程安全問題是多個線程在操作共享數據引起的。要保證線程安全,就需要保證對共享數據的操作有三個性質:原子性,可見性和有序性。

原子性

原子性是指涉及共享數據的操作對別的線程是不可分割的。即其他線程只能看到該操作未發生或者已經結束。

注意 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的定義:

  1. 如果一個操作happens-before另一個操作,那麼操作一的執行結果對第二個操作時可見的,並且第一個操作執行順序在第二個操作之前。
  2. 兩個操作如果存在happens-before規則,並不意味者java平臺會按照happens-before的執行順序執行。如果重排序的執行結果和按happens-before順序執行的結果一致的話,jmm允許這種排序。

規則1是JMM對程序員的保證,而規則2是JMM對編譯器和處理器重排序的約束

下面是happens-before原則規則:

  1. 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作;
  2. 鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作;
  3. volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作;
  4. 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C;
  5. 線程啓動規則:Thread對象的start()方法先行發生於此線程的每個一個動作;
  6. 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生;
  7. 線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行;
  8. 對象終結規則:一個對象的初始化完成先行發生於他的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. 根據程序規則,1 happens-before 2 ; 3 happens-before 4
  2. 根據volatile規則,2 happens-before 3
  3. 根據傳遞性規則,由 1 happens-before 2,2 happens-before 3,3 happens-before 4 => 1 happens before 4

線程安全解決方案

多線程安全問題是因爲多個線程同時操作共享變量,缺乏同步機制來協調線程間數據的訪問和活動。jdk提供了鎖,volatile關鍵字等線程同步機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章