JAVA多線程阻塞

原文出處:http://blog.csdn.net/evankaka/article/details/44153709#t9


4、線程狀態轉換

下面的這個圖很是重要!你若是看懂了這個圖,那麼對於多線程的理解將會更加深入!


一、新建狀態(New):新建立了一個線程對象。
二、就緒狀態(Runnable):線程對象建立後,其餘線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
三、運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
四、阻塞狀態(Blocked):阻塞狀態是線程由於某種緣由放棄CPU使用權,暫時中止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的狀況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。(wait會釋放持有的鎖)
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。
(三)、其餘阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入就緒狀態。(注意,sleep是不會釋放持有的鎖)
五、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。

5、線程調度

線程的調度java

一、調整線程優先級:Java線程有優先級,優先級高的線程會得到較多的運行機會。
 
Java線程的優先級用整數表示,取值範圍是1~10,Thread類有如下三個靜態常量:
static int MAX_PRIORITY
          線程能夠具備的最高優先級,取值爲10。
static int MIN_PRIORITY
          線程能夠具備的最低優先級,取值爲1。
static int NORM_PRIORITY
          分配給線程的默認優先級,取值爲5。
 
Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級。
 
每一個線程都有默認的優先級。主線程的默認優先級爲Thread.NORM_PRIORITY。
線程的優先級有繼承關係,好比A線程中建立了B線程,那麼B將和A具備相同的優先級。
JVM提供了10個線程優先級,但與常見的操做系統都不能很好的映射。若是但願程序能移植到各個操做系統中,應該僅僅使用Thread類有如下三個靜態常量做爲優先級,這樣能保證一樣的優先級採用了一樣的調度方式。
 
二、線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒爲單位。當睡眠結束後,就轉爲就緒(Runnable)狀態。sleep()平臺移植性好。
 
三、線程等待:Object類中的wait()方法,致使當前的線程等待,直到其餘線程調用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行爲等價於調用 wait(0) 同樣。
 
四、線程讓步:Thread.yield() 方法,暫停當前正在執行的線程對象,把執行機會讓給相同或者更高優先級的線程。
 
五、線程加入:join()方法,等待其餘線程終止。在當前線程中調用另外一個線程的join()方法,則當前線程轉入阻塞狀態,直到另外一個進程運行結束,當前線程再由阻塞轉爲就緒狀態。
 
六、線程喚醒:Object類中的notify()方法,喚醒在此對象監視器上等待的單個線程。若是全部線程都在此對象上等待,則會選擇喚醒其中一個線程,選擇是任意性的,並在對實現作出決定時發生。相似的方法還有一個notifyAll(),喚醒在此對象監視器上等待的全部線程。
 

6、經常使用函數說明


①sleep(long millis): 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)
        sleep()使當前線程進入停滯狀態(阻塞當前線程),讓出CUP的使用、目的是不讓當前線程獨自霸佔該進程所獲的CPU資源,以留必定時間給其餘線程執行的機會;
        sleep()是Thread類的Static(靜態)的方法;所以他不能改變對象的機鎖,因此當在一個Synchronized塊中調用Sleep()方法是,線程雖然休眠了,可是對象的機鎖並木有被釋放,其餘線程沒法訪問這個對象(即便睡着也持有對象鎖)。
        sleep()使當前線程進入停滯狀態(阻塞當前線程),讓出CUP的使用、目的是不讓當前線程獨自霸佔該進程所獲的CPU資源,以留必定時間給其餘線程執行的機會;

②join():指等待t線程終止。

使用方式。

join是Thread類的一個方法,啓動線程後直接調用,即join()的做用是:「等待該線程終止」,這裏須要理解的就是該線程是指的主線程等待子線程的終止。也就是說,在子線程調用了join()方法後面的代碼要等到子線程結束了才能繼續執行。面試

[java]  view plain  copy
  在CODE上查看代碼片 派生到個人代碼片
  1. Thread t = new AThread(); t.start(); t.join();  

爲何要用join()方法

        在不少狀況下,主線程生成並起動了子線程,若是子線程裏要進行大量的耗時的運算,主線程每每將於子線程以前結束,可是若是主線程處理完其餘的事務後,須要用到子線程的處理結果,也就是主線程須要等待子線程執行完成以後再結束,這個時候就要用到join()方法了。安全


③yield():暫停當前正在執行的線程對象,並執行其餘線程。
        Thread.yield()方法做用是:暫停當前正在執行的線程對象,並執行其餘線程。
          yield()應該作的是讓當前運行線程回到可運行狀態,以容許具備相同優先級的其餘線程得到運行機會。所以,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。可是,實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中。
        結論:yield()從未致使線程轉到等待/睡眠/阻塞狀態。在大多數狀況下,yield()將致使線程從運行狀態轉到可運行狀態,但有可能沒有效果。

sleep()和yield()的區別:
        sleep()使當前線程進入停滯狀態,因此執行sleep()的線程在指定的時間內確定不會被執行;yield()只是使當前線程從新回到可執行狀態,因此執行yield()的線程有可能在進入到可執行狀態後立刻又被執行。
        sleep 方法使當前運行中的線程睡眼一段時間,進入不可運行狀態,這段時間的長短是由程序設定的,yield 方法使當前線程讓出 CPU 佔有權,但讓出的時間是不可設定的。實際上,yield()方法對應了以下操做:先檢測當前是否有相同優先級的線程處於同可運行狀態,若有,則把 CPU  的佔有權交給此線程,不然,繼續運行原來的線程。因此yield()方法稱爲「退讓」,它把運行機會讓給了同等優先級的其餘線程。
       另外,sleep 方法容許較低優先級的線程得到運行機會,但 yield()  方法執行時,當前線程仍處在可運行狀態,因此,不可能讓出較低優先級的線程些時得到 CPU 佔有權。在一個運行系統中,若是較高優先級的線程沒有調用 sleep 方法,又沒有受到 I\O 阻塞,那麼,較低優先級線程只能等待全部較高優先級的線程運行結束,纔有機會運行。 

④setPriority(): 更改線程的優先級。

    MIN_PRIORITY = 1
        NORM_PRIORITY = 5
             MAX_PRIORITY = 10多線程

用法:
Thread4 t1 = new Thread4("t1"); Thread4 t2 = new Thread4("t2"); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY);

⑤interrupt():不要覺得它是中斷某個線程!它只是線線程發送一箇中斷信號,讓線程在無限等待時(如死鎖時)能拋出拋出,從而結束線程,可是若是你吃掉了這個異常,那麼 這個線程仍是不會中斷的!

⑥wait()ide

        Obj.wait(),與Obj.notify()必需要與synchronized(Obj)一塊兒使用,也就是wait,與notify是針對已經獲取了Obj鎖進行操做,從語法角度來講就是Obj.wait(),Obj.notify必須在synchronized(Obj){...}語句塊內函數

        從功能上來講wait就是說線程在獲取對象鎖後,主動釋放對象鎖,同時本線程休眠。直到有其它線程調用對象的notify()喚醒該線程,才能繼續獲取對象鎖,並繼續執行。相應的notify()就是對對象鎖的喚醒操做。但有一點須要注意的是notify()調用後,並非立刻就釋放對象鎖的,而是在相應的synchronized(){}語句塊執行結束,自動釋放鎖後,JVM會在wait()對象鎖的線程中隨機選取一線程,賦予其對象鎖,喚醒線程,繼續執行。這樣就提供了在線程間同步、喚醒的操做。測試

        Thread.sleep()與Object.wait()兩者均可以暫停當前線程,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了對象鎖的控制。this


        單單在概念上理解清楚了還不夠,須要在實際的例子中進行測試才能更好的理解。對Object.wait(),Object.notify()的應用最經典的例子,應該是三線程打印ABC的問題了吧,這是一道比較經典的面試題,題目要求以下:spa

        創建三個線程,A線程打印10次A,B線程打印10次B,C線程打印10次C,要求線程同時運行,交替打印10次ABC。這個問題用Object的wait(),notify()就能夠很方便的解決。代碼以下:操作系統

[java]  view plain  copy
  在CODE上查看代碼片 派生到個人代碼片
  1. /** 
  2.  * wait用法 
  3.  * @author DreamSea  
  4.  * @time 2015.3.9  
  5.  */  
  6. package com.multithread.wait;  
  7. public class MyThreadPrinter2 implements Runnable {     
  8.         
  9.     private String name;     
  10.     private Object prev;     
  11.     private Object self;     
  12.     
  13.     private MyThreadPrinter2(String name, Object prev, Object self) {     
  14.         this.name = name;     
  15.         this.prev = prev;     
  16.         this.self = self;     
  17.     }     
  18.     
  19.     @Override    
  20.     public void run() {     
  21.         int count = 10;     
  22.         while (count > 0) {     
  23.             synchronized (prev) {     
  24.                 synchronized (self) {     
  25.                     System.out.print(name);     
  26.                     count--;    
  27.                       
  28.                     self.notify();     
  29.                 }     
  30.                 try {     
  31.                     prev.wait();     
  32.                 } catch (InterruptedException e) {     
  33.                     e.printStackTrace();     
  34.                 }     
  35.             }     
  36.     
  37.         }     
  38.     }     
  39.     
  40.     public static void main(String[] args) throws Exception {     
  41.         Object a = new Object();     
  42.         Object b = new Object();     
  43.         Object c = new Object();     
  44.         MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);     
  45.         MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);     
  46.         MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);     
  47.              
  48.              
  49.         new Thread(pa).start();  
  50.         Thread.sleep(100);  //確保按順序A、B、C執行  
  51.         new Thread(pb).start();  
  52.         Thread.sleep(100);    
  53.         new Thread(pc).start();     
  54.         Thread.sleep(100);    
  55.         }     
  56. }    
輸出結果:

ABCABCABCABCABCABCABCABCABCABC

     先來解釋一下其總體思路,從大的方向上來說,該問題爲三線程間的同步喚醒操做,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循環執行三個線程。爲了控制線程執行的順序,那麼就必需要肯定喚醒、等待的順序,因此每個線程必須同時持有兩個對象鎖,才能繼續執行。一個對象鎖是prev,就是前一個線程所持有的對象鎖。還有一個就是自身對象鎖。主要的思想就是,爲了控制執行的順序,必需要先持有prev鎖,也就前一個線程要釋放自身對象鎖,再去申請自身對象鎖,二者兼備時打印,以後首先調用self.notify()釋放自身對象鎖,喚醒下一個等待線程,再調用prev.wait()釋放prev對象鎖,終止當前線程,等待循環結束後再次被喚醒。運行上述代碼,能夠發現三個線程循環打印ABC,共10次。程序運行的主要過程就是A線程最早運行,持有C,A對象鎖,後釋放A,C鎖,喚醒B。線程B等待A鎖,再申請B鎖,後打印B,再釋放B,A鎖,喚醒C,線程C等待B鎖,再申請C鎖,後打印C,再釋放C,B鎖,喚醒A。看起來彷佛沒什麼問題,但若是你仔細想一下,就會發現有問題,就是初始條件,三個線程按照A,B,C的順序來啓動,按照前面的思考,A喚醒B,B喚醒C,C再喚醒A。可是這種假設依賴於JVM中線程調度、執行的順序。

wait和sleep區別:
共同點: 

1. 他們都是在多線程的環境下,均可以在程序的調用處阻塞指定的毫秒數,並返回。 
2. wait()和sleep()均可以經過interrupt()方法打斷線程的暫停狀態 ,從而使線程馬上 拋出InterruptedException 。 
         若是線程A但願當即結束線程B,則能夠對線程B對應的Thread實例調用interrupt方法。若是此刻線程B正在wait/sleep /join,則線程B會馬上拋出InterruptedException,在catch() {} 中直接return便可安全地結束線程。 
         須要注意的是,InterruptedException是線程本身從內部拋出的,並非interrupt()方法拋出的。對某一線程調用 interrupt()時,若是該線程正在執行普通的代碼,那麼該線程根本就不會拋出InterruptedException。可是,一旦該線程進入到 wait()/sleep()/join()後,就會馬上拋出InterruptedException 。 
不一樣點: 
1. Thread類的方法:sleep(),yield()等 ;Object的方法:wait()和notify()等 。
2. 每一個對象都有一個鎖來控制同步訪問。Synchronized關鍵字能夠和對象的鎖交互,來實現線程的同步。 
        sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其餘線程可使用同步控制塊或者方法。 
3. wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep能夠在任何地方使用。
因此sleep()和wait()方法的最大區別是:sleep()睡眠時,保持對象鎖,仍然佔有該鎖;而wait()睡眠時,釋放對象鎖。   可是wait()和sleep()均可以經過interrupt()方法打斷線程的暫停狀態,從而使線程馬上拋出InterruptedException(但不建議使用該方法)。