Java 線程

引言

早期計算機中還不存在操作系統,一臺機器從頭到尾只能執行一個程序,並且這個程序能訪問所有的計算機資源。

操作系統的引入是的計算機“同時”能運行多個程序,不同程序都在單獨的進程中運行:操作系統爲各個獨立的進程分配各種資源,包括內存、文件句柄以及安全證書等。不同進程之間可以通過一些粗粒度的通信機制來交換數據,包括:套接字、信號處理器、共享內存、信號量以及文件等。

促成操作系統協調多進程同時運作的主要原因有:

  • 資源利用率:對於某些需要等待IO或磁盤操作的程序,不應該要求CPU等待,在這種情況下,多進程操作系統的CPU可以在等待時運行其他程序;
  • 公平性:通過時間分片使得不同程序對計算機的資源有同等的使用權;
  • 便利性:一個系統的多個任務應該被拆分成多個進程進行獨立開發和維護,進程之間通過通信進行協調和共享數據;

而同樣的原因也促使線程的出現。線程也被稱爲輕量級進程,在大多數現代操作系統中,都是以線程爲基本的調度單位,而不是進程

線程的出現,允許同一個進程中同時存在多個程序控制流,線程會共享進程範圍內的資源(正是因爲多線程共享同一個進程的資源,加大了多線程使用的負責性),例如內存句柄和文件句柄,每個線程都有各自的程序計數器、棧以及局部變量等

多線程的優勢:

  • 發揮多核處理器的強大能力;
  • 建模的簡單性;
  • 異步事件的簡化處理;
  • 響應更靈敏的用戶界面;

多線程帶來的風險:

  • 安全性問題:由於CPU對多個線程的調度存在隨機性,即在沒有充分運用同步機制(例如,volatile、synchronized、基於AQS的各種鎖)的情況下,多個線程的操作執行次序是不可預測的
// java源碼
private int i; // 類字段
public void incAssign(){
    i++;
}     

// javap.exe -verbose查看class指令碼
public void incAssign();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0           // 將第一引用類型本地變量推送至棧頂
         1: dup               // 複製棧頂數值並將複製值壓入棧頂
         2: getfield      #2  // Field i:I  // 獲取指定類的實例域,並將其值壓入棧頂
         5: iconst_1          // 將int型1推送至棧頂  
         6: iadd              // 將棧頂兩個int型值相加,並將結果壓入棧頂
         7: putfield      #2  // Field i:I  // 爲指定類的實例域賦值
        10: return            // 從當前方法返回void
      LineNumberTable:
        line 10: 0
        line 11: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   LTest;
}

分析上述源碼和編譯後的字節碼可知,value++看上去是單個操作,但事實上在JVM執行字節碼時,它包含了幾個獨立的指令操作,如本例中的2-7指令行:獲取實例域數值並壓入棧頂、將常量1推送至棧頂、棧頂的兩個值相加、給實例域重新賦值,即一個簡單的自加操作,在字節碼的處理過程中至少涉及到了四步驟的操作。

如果A線程執行到第6行,還尚未執行第7行的賦值,而此時B線程已經執行完第2行的讀取指令,那麼A線程的加1的效果還沒來得及被B線程讀取到,也就意味着兩個線程對同一個i的值進行自加操作,雖然加了2次,但兩者得到的結果是一樣的(B在A之後putfield,相當於把A的勞動成果覆蓋掉了)。這就是所謂的多線程併發執行時的不安全問題,而本例還尚未考慮指令重排序帶來的更多複雜性。

 

 

 

 

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