4.2019Android多執行緒總結

黑夜路口發表於2019-02-20

1.什麼是執行緒

執行緒就是程式中執行的多個子任務,是作業系統呼叫的最小單元

2.執行緒的狀態

New:新建狀態,new出來,還沒有呼叫start
Runnable:可執行狀態,呼叫start進入可執行狀態,可能執行也可能沒有執行,取決於作業系統的排程
Blocked:阻塞狀態,被鎖阻塞,暫時不活動,阻塞狀態是執行緒阻塞在進入synchronized關鍵字修飾的方法或程式碼塊(獲取鎖)時的狀態。
Waiting:等待狀態,不活動,不執行任何程式碼,等待執行緒排程器排程,wait sleep
Timed Waiting:超時等待,在指定時間自行返回
Terminated:終止狀態,包括正常終止和異常終止

2.執行緒的建立

a.繼承Thread重寫run方法
b.實現Runnable重寫run方法
c.實現Callable重寫call方法
實現Callable和實現Runnable類似,但是功能更強大,具體表現在
a.可以在任務結束後提供一個返回值,Runnable不行
b.call方法可以丟擲異常,Runnable的run方法不行
c.可以通過執行Callable得到的Fulture物件監聽目標執行緒呼叫call方法的結果,得到返回值,(fulture.get(),呼叫後會阻塞,直到獲取到返回值)

3.執行緒中斷

一般情況下,執行緒不執行完任務不會退出,但是在有些場景下,我們需要手動控制執行緒中斷結束任務,Java中有提供執行緒中斷機制相關的Api,每個執行緒都一個狀態位用於標識當前執行緒物件是否是中斷狀態

public boolean isInterrupted() //判斷中斷標識位是否是true,不會改變標識位
public void interrupt()  //將中斷標識位設定為true
public static boolean interrupted() //判斷當前執行緒是否被中斷,並且該方法呼叫結束的時候會清空中斷標識位

需要注意的是interrupt()方法並不會真的中斷執行緒,它只是將中斷標識位設定為true,具體是否要中斷由程式來判斷,如下,只要執行緒中斷標識位為false,也就是沒有中斷就一直執行執行緒方法

new Thread(new Runnable(){
      while(!Thread.currentThread().isInterrupted()){
              //執行執行緒方法
      }
}).start();

前邊我們提到了執行緒的六種狀態,New Runnable Blocked Waiting Timed Waiting Terminated,那麼在這六種狀態下呼叫執行緒中斷的程式碼會怎樣呢,New和Terminated狀態下,執行緒不會理會執行緒中斷的請求,既不會設定標記位,在Runnable和Blocked狀態下呼叫interrupt會將標誌位設定位true,在Waiting和Timed Waiting狀態下會發生InterruptedException異常,針對這個異常我們如何處理?
1.在catch語句中通過interrupt設定中斷狀態,因為發生中斷異常時,中斷標誌位會被複位,我們需要重新將中斷標誌位設定為true,這樣外界可以通過這個狀態判斷是否需要中斷執行緒

try{
    ....
}catch(InterruptedException e){
    Thread.currentThread().interrupt();
}

2.更好的做法是,不捕獲異常,直接丟擲給呼叫者處理,這樣更靈活

4.重入鎖與條件物件,同步方法和同步程式碼塊

。。。

5.volatile關鍵字

volatile為例項域的同步訪問提供了免鎖機制,如果宣告一個域為volatile,那麼編譯器和虛擬機器就直到該域可能被另一個執行緒併發更新

java記憶體模型

堆記憶體是被所有執行緒共享的執行時記憶體區域,存在可見性的問題。執行緒之間共享變數儲存在主存中,每個執行緒都有一個私有的本地記憶體,本地記憶體儲存了該執行緒共享變數的副本(本地記憶體是一個抽象概念,並不真實存在),兩個執行緒要通訊的話,首先A執行緒把本地記憶體更新過的共享變數更新到主存中,然後B執行緒去主存中讀取A執行緒更新過的共享變數,也就是說假設執行緒A執行了i = 1這行程式碼更新主執行緒變數i的值,會首先在自己的工作執行緒中堆變數i進行賦值,然後再寫入主存當中,而不是直接寫入主存

原子性 可見性 有序性

原子性:對基本資料型別的讀取和賦值操作是原子性操作,這些操作不可被中斷,是一步到位的,例如x=3是原子性操作,而y = x就不是,它包含兩步:第一讀取x,第二將x寫入工作記憶體;x++也不是原子性操作,它包含三部,第一,讀取x,第二,對x加1,第三,寫入記憶體。原子性操作的類如:AtomicInteger AtomicBoolean AtomicLong AtomicReference

可見性:指執行緒之間的可見性,既一個執行緒修改的狀態對另一個執行緒是可見的。volatile修飾可以保證可見性,它會保證修改的值會立即被更新到主存,所以對其他執行緒是可見的,普通的共享變數不能保證可見性,因為被修改後不會立即寫入主存,何時被寫入主存是不確定的,所以其他執行緒去讀取的時候可能讀到的還是舊值

有序性:Java中的指令重排序(包括編譯器重排序和執行期重排序)可以起到優化程式碼的作用,但是在多執行緒中會影響到併發執行的正確性,使用volatile可以保證有序性,禁止指令重排

volatile可以保證可見性 有序性,但是無法保證原子性,在某些情況下可以提供優於鎖的效能和伸縮性,替代sychronized關鍵字簡化程式碼,但是要嚴格遵循使用條件。

執行緒池ThreadPoolExecutor

執行緒池的工作原理:執行緒池可以減少建立和銷燬執行緒的次數,從而減少系統資源的消耗,當一個任務提交到執行緒池時
  1. 首先判斷核心執行緒池中的執行緒是否已經滿了,如果沒滿,則建立一個核心執行緒執行任務,否則進入下一步
  2. 判斷工作佇列是否已滿,沒有滿則加入工作佇列,否則執行下一步
  3. 判斷執行緒數是否達到了最大值,如果不是,則建立非核心執行緒執行任務,否則執行飽和策略,預設丟擲異常
執行緒池的種類

1.FixedThreadPool:可重用固定執行緒數的執行緒池,只有核心執行緒,沒有非核心執行緒,核心執行緒不會被回收,有任務時,有空閒的核心執行緒就用核心執行緒執行,沒有則加入佇列排隊
2.SingleThreadExecutor:單執行緒執行緒池,只有一個核心執行緒,沒有非核心執行緒,當任務到達時,如果沒有執行執行緒,則建立一個執行緒執行,如果正在執行則加入佇列等待,可以保證所有任務在一個執行緒中按照順序執行,和FixedThreadPool的區別只有數量
3.CachedThreadPool:按需建立的執行緒池,沒有核心執行緒,非核心執行緒有Integer.MAX_VALUE個,每次提交
任務如果有空閒執行緒則由空閒執行緒執行,沒有空閒執行緒則建立新的執行緒執行,適用於大量的需要立即處理的並且耗時較短的任務
4.ScheduledThreadPoolExecutor:繼承自ThreadPoolExecutor,用於延時執行任務或定期執行任務,核心執行緒數固定,執行緒總數為Integer.MAX_VALUE


相關文章