java多執行緒總結

qq_39780411發表於2017-11-13

Java多執行緒總結

多執行緒基礎概念介紹

程式

是程式或任務的執行的過程,具有動態性;
它持有資源(共享記憶體,共享檔案)和執行緒
如:開啟QQ,搜狗輸入法軟體時,我們啟動類兩個執行緒

執行緒
系統中最小的執行單元,比如一個軟體裡面的各種任務就是執行緒;
同一個程式中有多個執行緒;
執行緒共享程式資源;

多執行緒
一個執行緒中可以開啟多條執行緒,多執行緒可以並行執行不同的任務;多執行緒技術可以通告程式的執行效率.

執行緒建立的兩種方式比較

繼承Thread類

public class ExtendThread{
  public static void main(String[] args){
      MyThread mt =new MyThread;
      mt.start();
      MyThread mt2=new MyThread;
      mt2.start;
}
 class MyThread extends Thread{
  @Overrdie
  public void run(){
  System.out.println("使用extends建立執行緒!");

 }
}

實現Runnable介面

public class ImplementThread{
  public static void main(String[] args){
    Mythread2 mt = new Mythread();
    Thread th=new Thread(mt);
    th.start();
    Thread th2 = new Thread(mt);
    th2.start();
  }

}
class Mythread2 implements Runnable {
  @Override
   public void run(){
    System.out.println("使用implements建立執行緒!")
  }
}

注意事項:同一執行緒的start()方法不能能被重複呼叫
start()方法相關原始碼如下

public synchronized void start() {        
    if (threadStatus != 0)  //start()方法不能被重複呼叫,否則會丟擲異常
       throw new IllegalThreadStateException();        
            /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);  //加入到執行緒組中
        boolean started = false;        
            try {
            start0();
            started = true;
        } finally {            
                try {                
                if (!started) {
               group.threadStartFailed(this);
           }
            } catch (Throwable ignore) {                
                    /* do nothing. If start0 threw a Throwable then
               it will be passed up the call stack */
            }
        }
    }
}private native void start0();

附Thread常用方法介紹
Thread常用方法介紹

Thread常用方法
兩種實現方法對比:

  • Runnable方式可以避免Thread方式由於Java單繼承特性帶來的缺陷
  • Runnable方式實現可以被多個執行緒共享,適合於多個執行緒處理同一資源的情況

執行緒的生命週期
下圖是執行緒生命週期的詳細狀態轉換圖(注意:不能從阻塞狀態直接到執行狀態)
這裡寫圖片描述
執行緒的生命週期
下面將對每一個狀態進行簡單的的介紹

  • 建立
    新建一個執行緒物件,如Thread td = new Thread();

  • 就緒
    建立執行緒物件之後,呼叫start()方法(注意:此時執行緒只是進入了執行緒佇列,等待獲取CPU服務,具備了執行條件,但不一定已經開始執行了)

  • 執行
    處於就緒狀態的執行緒,一旦獲取CPU資源,就會進入執行狀態,開始執行run()方法裡面的邏輯

  • 阻塞
    一個正在執行的執行緒在某些情況下,由於某種原因而暫時讓出了CPU資源,暫停了自己的執行,便進入了阻塞狀態。如:呼叫了sleep()方法

  • 終止
    執行緒的run()方法執行完畢,或者執行緒呼叫了stop()方法(現在已經不推薦使用這種方式),執行緒便進入了終止狀態

附:更加詳細的執行緒狀態轉換圖
這裡寫圖片描述
更加詳細的執行緒狀態轉換圖
守護執行緒介紹
Java執行緒有兩類

  • 使用者執行緒
    執行在前臺,執行具體的任務。如:使用者的主執行緒,連線網路的子執行緒等

  • 守護執行緒
    執行在後臺,為其他前臺執行緒服務。
    特點:一旦所有使用者執行緒都結束執行,守護執行緒也會隨JVM一起結束工作
    應用:資料庫連線池中的監測執行緒.JVM虛擬機器啟動後的監測執行緒
    常見:垃圾回收執行緒
    使用方法:
    呼叫Thread類的setDaemon(true)方法來設定當前執行緒為守護執行緒
    注意事項:
    ①setDaemon(true)必須在start()方法之前呼叫,否則會丟擲異常IllegalThreadStateException
    ②在守護執行緒中產生的新執行緒也是守護執行緒
    ③不是所有的任務都可已分配給守護執行緒來執行,如:讀寫操作或計算邏輯
    記憶體可見性
    原子性
    跟資料庫事務的原子性概念差不多,即一個操作(有可能包含有多個子操作)要麼全部執行(生效),要麼全部都不執行(都不生效)。
    可見性
    一個執行緒對共享變數值的修改,能夠及時地被其他執行緒看到。
    共享變數
    如果一個變數在多個執行緒的工作記憶體中都存在副本,那麼這個變數就是這幾個執行緒的共享變數。
    Java記憶體模型(JMM)
    描述了Java程式中各種變數(執行緒共享變數)的訪問規則,以及在JVM中將變數儲存到記憶體和從記憶體中讀取出變數這樣的底層細節。這裡寫圖片描述
    Java記憶體模型
    注意:

  • 所有的變數都儲存在主記憶體中
  • 每個執行緒都有自己獨立的工作記憶體,裡面儲存該執行緒使用到的變數的副本(即主記憶體中共享變數的副本)
  • 共享變數可見性實現原理
    這裡寫圖片描述
    共享變數可見性實現原理
    執行緒1對共享變數的修改想要被執行緒2及時看到,必須經過以下兩個步驟:
  • 把工作記憶體1中更新過的共享變數重新整理到主記憶體中
  • 將主記憶體中最新的共享變數的值更新到工作記憶體2中

Java語言層面實現可見性的兩種方式

  • synchronized

synchronized 關鍵字,代表這個方法加鎖,相當於不管哪一個執行緒A每次執行到這個方法時,都要檢查有沒有其它正在用這個方法的執行緒B(或者C D等),有的話要等正在使用這個方法的執行緒B(或者C D)執行完這個方法後再執行此執行緒A,如果沒有其他執行緒正在用這個方法則執行緒A可以執行這個方法。
執行緒執行互斥程式碼的過程:
①獲得互斥鎖
②清空工作記憶體
③從主記憶體拷貝變數的最新副本到工作記憶體
④執行程式碼
⑤將更改後的共享變數的值重新整理到主記憶體
⑥釋放互斥鎖
synchronized 關鍵字的兩種用法
①synchronized 方法
通過在方法宣告中加入 synchronized關鍵字來宣告 synchronized 方法。如:
public synchronized void accessVal(int newVal);
②synchronized 塊
通過 synchronized關鍵字來宣告synchronized 塊。語法如下:
synchronized(syncObject)
{   //允許訪問控制的程式碼}
對synchronized (this)的一些理解(this指的是呼叫這個方法的物件)
當兩個併發執行緒訪問同一個物件object中的這個synchronized(this)同步程式碼塊時,一個時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完這個程式碼塊以後才能執行該程式碼塊。  
然而,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,另一個執行緒仍然可以訪問該object中的非synchronized(this)同步程式碼塊。  
尤其關鍵的是,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,其他執行緒對object中所有其它synchronized(this)同步程式碼塊的訪問將被阻塞。 
第三個例子同樣適用其它同步程式碼塊。也就是說,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,它就獲得了這個object的物件鎖。結果,其它執行緒對該object物件所有同步程式碼部分的訪問都被暫時阻塞。  
以上規則對其它物件鎖同樣適用。
注意:
執行緒加鎖時,將清空工作記憶體中共享變數的值,從而使用共享變數時需要從主記憶體重新讀取最新的值
執行緒解鎖時,必須把共享變數的最新值重新整理到主記憶體中
volatile
能夠保證volatile變數的可見性;不能保證volatile變數複合操作的原子性
實現方式:
通過加入記憶體屏障和禁止重排序優化實現
①對volatile變數執行寫操作時,會在寫操作後加入一條store屏障指令
②對volatile變數執行讀操作時,會在讀操作前加入一條load屏障指令
執行緒寫volatile變數的過程
①改變執行緒工作記憶體中volatile變數副本的值
②將改變後的副本的值從工作記憶體重新整理到主記憶體
執行緒讀volatile變數的過程:
①從主記憶體中讀取volatile變數的最新值到執行緒的工作記憶體中
②從工作記憶體中讀取volatile變數的副本的值
適用場合:
①對變數的寫操作不依賴其當前值
不滿足:number++、count = count+5
滿足:boolean變數,記錄溫度變化的變數
②變數沒有包含在具有其它變數的不變式中
不滿足:不變式 low < up
兩種實現方式比較
volatile不用加鎖,比synchronized更加輕量級,不會阻塞執行緒;
從記憶體可見性分析,volatile讀操作相當於加鎖,volatile寫操作相當於解鎖;
synchronized既能保證可見性,又能夠保證原子性;而volatile只能保證可見性,不能夠保證原子性

補充問題
1.Thread類的yield方法作用
yield()應該做的是讓當前執行執行緒回到可執行狀態,只允許具有相同優先順序的其他執行緒獲得執行機會。因此,使用yield()的目的是讓相同優先順序的執行緒之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的執行緒還有可能被執行緒排程程式再次選中。
注意:sleep()可以使低優先順序的執行緒得到執行的機會,當然也可以讓同優先順序、高優先順序的執行緒有執行的機會。而yield()方法只能讓同優先順序的執行緒有執行的機會。
2.為什麼每個執行緒要使用工作記憶體,而不直接訪問主記憶體
①為了減少各執行緒對主記憶體的頻繁訪問,各執行緒只需要訪問自己的工作記憶體即可以獲取共享變數的副本值,將訪問壓力分攤到各執行緒的工作記憶體中,主記憶體只需要按照一定的規則同步共享變數的值到各執行緒的工作記憶體。
注意:
“synchronized” — 保證在塊開始時都同步主記憶體的值到工作記憶體,而塊結束時將變數同步回主記憶體
轉載自:微信公眾號csh624366188

相關文章