Java語法進階11-多線程

多線程

併發與並行、進程,線程調度自行百度html

線程(thread):是一個進程中的其中一條執行路徑,CPU調度的最基本調度的單位。同一個進程中線程能夠共享一些內存(堆、方法區),每個線程又有本身的獨立空間(棧、程序計數器)。由於線程之間有共享的內存,在實現數據共享方面,比較方便,可是又由於共享數據的問題,會有線程安全問題。java

當運行Java程序時,其實已經有一個線程了,那就是main線程。編程

Thread類

全部的線程對象都必須是Thread類或其子類的實例,Java中經過繼承Thread類來建立啓動多線程的步驟以下:緩存

  1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就表明了線程須要完成的任務,所以把run()方法稱爲線程執行體。安全

  2. 建立Thread子類的實例,即建立了線程對象多線程

  3. 調用線程對象的start()方法來啓動該線程併發

Runnable接口

咱們還能夠實現Runnable接口,重寫run()方法,而後再經過Thread類的對象代理啓動和執行咱們的線程體run()方法。步驟以下:ide

  1. 定義Runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體一樣是該線程的線程執行體。函數

  2. 建立Runnable實現類的實例,並以此實例做爲Thread的target來建立Thread對象,該Thread對象纔是真正 的線程對象。this

  3. 調用線程對象的start()方法來啓動線程。

例: public class MyRunnable implements Runnable  //定義實現線程類

    MyRunnable mr = new MyRunnable();     //建立線程對象

         Thread t = new Thread(mr);         //經過Thread類的實例,啓動線程
          t.start();

實際上全部的多線程代碼都是經過運行Thread的start()方法來運行的。所以,不論是繼承Thread類仍是實現 Runnable接口來實現多線程,最終仍是經過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。

tips:Runnable對象僅僅做爲Thread對象的target,Runnable實現類裏包含的run()方法僅做爲線程執行體。 而實際的線程對象依然是Thread實例,只是該Thread線程負責執行其target的run()方法。

兩種方式的區別

一、繼承的方式有單繼承的限制,實現的方式能夠多實現

二、啓動方式不一樣

三、繼承:在實現共享數據時,可能須要靜態的

  實現:只要共享同一個Runnable實現類的對象便可。

四、繼承:選擇鎖時this可能不能用,

  實現:選擇鎖時this能夠用。

匿名內部類對象來實現線程的建立和啓動

new Thread("新的線程!"){
  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(getName()+":正在執行!"+i);
    }
  }
}.start();

構造方法

public Thread() :分配一個新的線程對象。

public Thread(String name) :分配一個指定名字的新的線程對象。

public Thread(Runnable target) :分配一個帶有指定目標新的線程對象。

public Thread(Runnable target,String name) :分配一個帶有指定目標新的線程對象並指定名字。

線程經常使用方法

volatile:修飾變量

變量不必定在何時值就會被修改了,爲了老是獲得最新的值,volatile修飾以後那麼每次都從主存中去取值,不會在寄存器中緩存它的值。

守護線程

守護線程有個特色,就是若是全部非守護線程都死亡,那麼守護線程自動死亡。

調用setDaemon(true)方法可將指定線程設置爲守護線程。必須在線程啓動以前設置,不然會報IllegalThreadStateException異常。

調用isDaemon()能夠判斷線程是不是守護線程。

線程安全

線程安全問題的判斷

一、是否有多個線程

二、這多個線程是否使用共享數據

三、這些線程在使用共享數據時,是否有寫有讀操做

同步代碼塊

synchronized 關鍵字能夠用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。 格式:

synchronized(同步鎖){
     須要同步操做的代碼
}

同步鎖必須是對象

鎖對象能夠是任意類型。

多個線程對象必須使用同一把鎖 注意:在任什麼時候候,最多容許一個線程擁有同步鎖,誰拿到鎖就進入代碼塊,其餘的線程只能在外等着(BLOCKED)。

同步方法

使用synchronized修飾的方法,就叫作同步方法,保證持有鎖線程執行該方法的時候,其餘線程只能在方法外等着

【其餘修飾符】 synchronized 返回值類型 方法名(【形參列表】)【throws 異常列表】{
     //可能會產生線程安全問題的代碼
}

鎖對象不能由咱們本身選,它是默認的:

(1)靜態方法:鎖對象是當前類的Class對象

(2)非靜態方式:this

線程間通訊

當「數據緩衝區」滿的時候,「生產者」須要wait,等着被喚醒;

當「數據緩衝區」空的時候,「消費者」須要wait,等着被喚醒。

在一個線程知足某個條件時,就進入等待狀態(wait()/wait(time)), 等待其餘線程執行完他們的指定代碼事後再將其喚醒(notify());或能夠指定wait的時間,等時間到了自動喚醒;在有多個線程進行等待時,若是須要,可使用 notifyAll()來喚醒全部的等待線程。

  1. wait:線程再也不活動,再也不參與調度,進入 wait set 中,所以不會浪費 CPU 資源,也不會去競爭鎖了,這時的線程狀態便是 WAITING或TIMED_WAITING。它還要等着別的線程執行一個特別的動做,也便是「通知(notify)」或者等待時間到,在這個對象上等待的線程從wait set 中釋放出來,從新進入到調度隊列(ready queue)中

  2. notify:則選取所通知對象的 wait set 中的一個線程釋放;

  3. notifyAll:則釋放所通知對象的 wait set 上的所有線程。

注意:

被通知線程被喚醒後也不必定能當即恢復執行,由於它當初中斷的地方是在同步塊內,而此刻它已經不持有鎖,因此她須要再次嘗試去獲取鎖(極可能面臨其它線程的競爭),成功後才能在當初調用 wait 方法以後的地方恢復執行。

總結以下:

  • 若是能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE(可運行) 狀態;

  • 不然,線程就從 WAITING 狀態又變成 BLOCKED(等待鎖) 狀態

調用wait和notify方法須要注意的細節

  1. wait方法與notify方法必需要由同一個鎖對象調用。由於:對應的鎖對象能夠經過notify喚醒使用同一個鎖對象調用的wait方法後的線程。

  2. wait方法與notify方法是屬於Object類的方法的。由於:鎖對象能夠是任意對象,而任意對象的所屬類都是繼承了Object類的。

  3. wait方法與notify方法必需要在同步代碼塊或者是同步函數中使用。由於:必需要經過鎖對象調用這2個方法。

等待喚醒機制能夠解決經典的「生產者與消費者」的問題

要解決該問題,就必須讓生產者線程在緩衝區滿時等待(wait),暫停進入阻塞狀態,等到下次消費者消耗了緩衝區中的數據的時候,通知(notify)正在等待的線程恢復到就緒狀態,從新開始往緩衝區添加數據。反之亦然

線程生命週期

1、站在線程的角度上:5種

一、新建:建立了線程對象,還未start

二、就緒:已啓動,而且可被CPU調度

三、運行:正在被調度

四、阻塞:遇到了:sleep(),wait(),wait(time),其它線程的join(),join(time),suspend(),鎖被其餘線程佔用等

  解除阻塞回到就緒狀態:sleep()時間,notify(),wait的時間到,加塞的線程結束,加塞的時間到,resume(),其餘佔用鎖的線程釋放了鎖等。

五、死亡:run()正常結束,遇到了未處理的異常或錯誤,stop()

注意:

程序只能對新建狀態的線程調用start(),而且只能調用一次,若是對非新建狀態的線程,如已啓動的線程或已死亡的線程調用start()都會報錯IllegalThreadStateException異常。

2、站在代碼的角度上6種

在java.lang.Thread.State的枚舉類中這樣定義

public enum State {
  NEW,
  RUNNABLE,
  BLOCKED,
  WAITING,
  TIMED_WAITING,
  TERMINATED;
}

一、新建NEW:建立了線程對象,還未start

二、可運行RUNNABLE:能夠被CPU調度,或者正在被調度

三、阻塞BLOCKED:等待鎖

四、等待WAITING:wait(),join()等沒有設置時間的,必須等notify(),或加塞的線程結束才能恢復

五、有時間等待TIMED_WAITING:sleep(time),wait(time),join(time)等有時間的阻塞,等時間到了恢復,或被interrupt也會恢復

六、終止TERMINATED:run()正常結束,遇到了未處理的異常或錯誤,stop()

釋放鎖操做與死鎖

任何線程進入同步代碼塊、同步方法以前,必須先得到對同步監視器的鎖定,那麼什麼時候會釋放對同步監視器的鎖定呢?

一、釋放鎖的操做

當前線程的同步方法、同步代碼塊執行結束

當前線程在同步代碼塊、同步方法中出現了未處理的Error或Exception,致使當前線程異常結束。

當前線程在同步代碼塊、同步方法中執行了鎖對象的wait()方法,當前線程被掛起,並釋放鎖。

二、不會釋放鎖的操做

線程執行同步代碼塊或同步方法時,程序調用Thread.sleep()、Thread.yield()方法暫停當前線程的執行。

線程執行同步代碼塊時,其餘線程調用了該線程的suspend()方法將該線程掛起,該線程不會釋放鎖(同步監視器)。應儘可能避免使用suspend()和resume()這樣的過期來控制線程。

三、死鎖

不一樣的線程分別鎖住對方須要的同步監視器對象不釋放,都在等待對方先放棄時就造成了線程的死鎖。一旦出現死鎖,整個程序既不會發生異常,也不會給出任何提示,只是全部線程處於阻塞狀態,沒法繼續。

相關文章
相關標籤/搜索