1. 程式人生 > 實用技巧 >多執行緒專題

多執行緒專題

一、基礎概念

1、什麼是程序?什麼是執行緒?

  程序是os排程的最小單元,比如啟動一個java程式就會建立一個程序,執行緒是cpu排程的最小單元,一個程序可以建立很多個執行緒,這些執行緒有各自的計數器、堆疊、區域性變數等屬性。

2、什麼是JMM模型?

  java記憶體模型(Java Memory Model),並不真實存在,描述的是一組規則或規範,這組規範定義了各個變數的訪問方式。JVM執行程式的實體是執行緒,而每個執行緒建立時JVM都會為其建立一個工作記憶體,用於儲存執行緒私有的資料,JMM規定所有變數都儲存在主記憶體,主記憶體是共享區域,所有的執行緒都可以訪問,但是執行緒變數的操作必須在工作記憶體中進行,首先將變數拷貝到自己的工作記憶體

空間,然後進行操作,操作完成後回寫主記憶體。不同執行緒之間無法訪問對方的工作記憶體,執行緒間的通訊必須通過主記憶體完成。

  主記憶體:例項共享區域,包括類資訊、常量、靜態變數。由於是共享資料區域,多條執行緒對同一個變數進行訪問時可能會發生執行緒安全問題。

  工作記憶體:儲存當前方法的所有本地變數資訊副本,每個執行緒只能訪問自己的工作記憶體,即執行緒中的本地變數對其他執行緒是不可見的,就算兩個執行緒執行的是同一段程式碼。

資料同步的八大原子操作

  1. lock(鎖定):作用於主記憶體的變數,把一個變數標記為一條執行緒獨佔狀態
  2. unlock(解鎖):作用於主記憶體的變數,把一個處於鎖定狀態的變數釋放出來,釋放後的變數才可以被其他執行緒鎖定
  3. read(讀取):作用於主記憶體的變數,把一個變數值從主記憶體傳輸到執行緒的工作記憶體中,以便隨後的load動作使用
  4. load(載入):作用於工作記憶體中的變數,把read操作從主記憶體中獲取到的變數寫入到工作記憶體的副本中
  5. use(使用):作用於工作記憶體的變數,把工作記憶體中的一個變數值傳遞給執行引擎
  6. assign(賦值):作用於工作記憶體中的變數,把執行引擎接收到的值傳送到主記憶體的變數
  7. store(儲存):把工作記憶體中的值傳送到主記憶體,以便後續的write操作
  8. write(寫入):把store操作從工作記憶體中一個變數的值寫入到主記憶體

同步規則分析

  1. 不允許一個執行緒無原因的(沒有發生過任何assign操作)把資料從工作記憶體同步回主記憶體中
  2. 一個新的變數只能在主記憶體中誕生,不允許在工作記憶體中直接使用一個未被初始化的變數。即對一個變數實施use和store操作之前,必須先自行load和assign操作
  3. 一個變數在同一時刻只允許一條執行緒對其進行lock操作,但lock操作可以被同一執行緒重複執行多次,lock與unlock必須成對出現
  4. 如果對一個變數執行lock操作,將會清空工作記憶體中此變數的值,在執行引擎使用這個變數之前需要重新執行load和assign操作
  5. 如果一個變數事先沒有被lock操作鎖定,則不允許對他執行unlock操作
  6. 執行unlock之前,必須先把此變數同步到主記憶體中(執行store和write操作)

併發程式設計的可見性,原子性與有序性問題

  1. 原子性:是指一個操作是不可中斷的,即使多執行緒環境下,一旦開始就不會被其它執行緒影響。基本資料型別都是原子操作(32為作業系統的long和double不一定,因為long和double是64位儲存單元,虛擬機器讀取到的可能是半個變數的值)
    解決:通過syncchronized和lock能保證任意時刻只有一個執行緒訪問該程式碼塊
  2. 可見性:理解指令重排之後,可見性就容易理解了,指的是,當一個執行緒修改了某個共享變數的值,其它執行緒是否能夠馬上得知這個修改的值
    解決:volatile關鍵字可保證可見性,syncchronized和lock也能保證可見性,因為他們可以保證同一時刻只有一個執行緒能訪問共享資源,並在釋放鎖之前將修改值重新整理到主記憶體中  
  3. 有序性:如果是多執行緒環境下,指令重排後的順序與原指令未必一致
    解決:valatile可以保證一定的有序性,syncchronized和lock可以保證有序性,相當於同一時刻只有一段程式執行同步程式碼,自然保證了有序性

happens-before原則

  1. 程式順序原則:即在一個執行緒內,必須保證語義的序列性,也就是說按照程式碼順序執行
  2. 鎖規則:解鎖動作必須在加鎖之前,那麼加鎖動作也必須在解鎖之後
  3. volatile原則:簡單理解就是volatile變數在每次執行緒訪問時,都強迫從主變數中讀該變數的值,而當該變數發生變化時,又會強迫將最新的值重新整理到主記憶體中
  4. 執行緒啟動規則:start方法先於他的每個動作,A在B執行start方法之前修改了共享變數的值,那麼這個值對執行緒B可見
  5. 傳遞性:A先於B,B先於C,那麼A必然先於C
  6. 執行緒終止規則:執行緒所有的操作先於執行緒的終結,Thread.join()的作用是等待當前執行的執行緒終止
  7. 執行緒中斷規則:可以通過Thread.interrupted()方法檢測執行緒是否中斷
  8. 物件的終結規則:結束先於finalize()方法。

volatile記憶體語義

  1. 保證被volatile修飾的共享變數對所有的執行緒總數是可見的,也就是當一個執行緒修改了被volatile修改的變數值,新值總是可以被其他執行緒立即得知,但是volatile無法保證原子性
  2. 禁止指令重排優化
    memory = allocate();//1、分配物件記憶體空間
    instance(memory);//2、初始化物件
    instance = memory; // 3、設定instance指向剛分配的記憶體地址
    2、3之間可能存在指令重排,而這就產生了一致性的問題,volatile可以禁止這種指令重排

  3. volatile重排序規則:
    • 第二個操作是volatile寫時,不管第一個操作是什麼,都不能重新排序。這個規則確保volatile寫之前的操作不會被編譯器重排到volatile讀之前
    • 當第一個操作時volatile讀時,不管第二個操作是什麼,都不能重新排序。這個規則缺包volatile讀之後的操作不會被編譯器重排到volatile讀之前
    • 第一個是volatile寫,第二個是volatile讀時不能重排序