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

線程池的工作原理:線程池可以減少創建和銷燬線程的次數,從而減少系統資源的消耗,當一個任務提交到線程池時

a. 首先判斷核心線程池中的線程是否已經滿了,如果沒滿,則創建一個核心線程執行任務,否則進入下一步
b. 判斷工作隊列是否已滿,沒有滿則加入工作隊列,否則執行下一步
c. 判斷線程數是否達到了最大值,如果不是,則創建非核心線程執行任務,否則執行飽和策略,默認拋出異常

線程池的種類

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

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