天天看點

4.2019Android多線程總結

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