1. 程式人生 > >Java多執行緒程式設計學習

Java多執行緒程式設計學習

  1. 擴充套件Java.lang.Thread類
  2. 實現java.lang.Runnable介面
  3. Thread和Runnable的區別
  4. 執行緒狀態轉換
  5. 執行緒排程

程序:每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換比較大,一個進行包含1-n個執行緒。(程序是資源分配的最小單位)
執行緒:同一類執行緒共享程式碼和資料空間,每個執行緒都有獨立的執行棧和程式計數器(PC),執行緒切換開銷小。(執行緒是CPU排程的最小單位)
執行緒和程序的五個階段:建立、就緒、執行、阻塞、停止。
多程序是指作業系統同事執行多個任務(程式)。
多執行緒是指同一程式中有多個順序流在執行。

在Java中要想實現多執行緒,有兩種手段,一個是繼承Thread類,一個是實現Runnable介面,(其實還有第三種,實現Callable介面,並與Future、執行緒池結合使用)。

擴充套件java.lang.Thread類

public class ThreadTest extends Thread{
    private Strign name;
    public ThreadTest(String name){
        this.name = name;
    }
    @Override
    public void run(){
        for
(int i=0;i<5;i++){ System.out.println(name+"執行:"+i); try{ sleep((int)Math.random() * 10); }catch(InterruptedException e){ e.printStackTrace(); } } } } public class Main{ public static void main(String[]args){ ThreadTest xc1 = new
ThreadTest("A"); ThreadTest xc2 = new ThreadTest("B"); xc1.start(); xc2.start(); } }

注意:start()方法的呼叫並不是立即執行多執行緒程式碼,而是使得該執行緒變成可執行狀態(Runnable),什麼時候執行是作業系統決定的。
從執行結果來看,多執行緒程式是亂序執行。
因此,只有亂執行的程式碼才有必要設計多執行緒。

Thread.sleep()方法呼叫目的是不讓當前執行緒獨自霸佔該進行所獲取的CPU資源,以留出一定時間給其他執行緒執行的機會。
實際上所有的多執行緒程式碼執行順序都是不確定的,每次執行的結果都是隨機的。
但是start方法重複呼叫的話,會出現java.lang.IllegalThreadStateException異常。

ThreadTest xc1 = new ThreadTest("A");
ThreadTest xc2 = xc1;
xc1.start();
xc2.start();

輸出:

Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Unknown Source)
    at com.multithread.learning.Main.main(Main.java:31)

實現java.lang.Runnable介面

採用Runnable也是非常常見的一種,我們只需要重寫Run方法即可。

class Thread2 implements Runnable{  
    private String name;  

    public Thread2(String name) {  
        this.name=name;  
    }  

    @Override  
    public void run() {  
          for (int i = 0; i < 5; i++) {  
                System.out.println(name + "執行  :  " + i);  
                try {  
                    Thread.sleep((int) Math.random() * 10);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
    }  
}  
public class Main {  
    public static void main(String[] args) {  
        new Thread(new Thread2("C")).start();  
        new Thread(new Thread2("D")).start();  
    }  
}  

說明:
run方法是多執行緒程式的一個約定,所有的多執行緒程式碼都在run方法裡面,Thread類實際上是實現了Runnable介面的類。
在啟動多執行緒的時候,需要先通過Thread類的構造方法,Thread(Runnable target)構造出物件,然後呼叫Thread物件的start()方法來執行多執行緒程式碼。
實際上所有的執行緒都是通過執行Thread的start方法來執行的,因此,不管是擴充套件Thread類還是實現Runnable介面來實現多執行緒,最終還是通過Thread物件的API來控制執行緒的,熟悉Thread類的API是進行多執行緒程式設計的基礎。

Thread和Runnable的區別

如果一個類繼承Thread,則不適合資源共享,如果實現了Runnable介面,很容易實現資源共享。
總結:
實現Runnable介面比繼承Thread類所具有的優勢:
- 適合多個相同的程式程式碼的執行緒去處理同一個資源
- 可以避免java中的單繼承的限制
- 增加程式的健壯性,程式碼可以被多個執行緒共享,程式碼和資料獨立
- 執行緒池只能放入實現Runnable或Callable類執行緒,不能直接放入繼承Thread的類。
- main方法其實也是一個執行緒。
- java中每次程式啟動至少啟動兩個執行緒,一個是main執行緒,一個是垃圾收集執行緒。因為每當使用java命令執行一個類的時候,實際上都啟動了一個JVM,每個JVM就是在作業系統中啟動了一個程序。

執行緒狀態轉換

如下:

  1. 新建狀態(New)
  2. 就緒狀態(Runnable):執行緒物件建立後,其他執行緒呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,變得可用權,等待獲取CPU的使用權。
  3. 執行狀態(Running):就緒狀態的執行緒獲取了CPU,執行程式程式碼。
  4. 阻塞狀態(Blocked):阻塞狀態是因為執行緒因為某種原因放棄CPU使用權,停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態,阻塞情況分為三種:

    (1)等待阻塞:執行的執行緒執行wait()方法,JVM會把該執行緒放入等待池中。
    (2)同步阻塞:執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,JVM就會把執行緒放入鎖池中。
    (3)其他阻塞:執行的執行緒執行sleep()方法或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態,當sleep()狀態超時時、json()等待執行緒終結或者超時、或者I/O處理完畢時,執行緒重新載入就緒狀態。(注意,sleep是不會釋放持有的鎖的)
    5.死亡狀態:執行緒執行完了或者因異常而退出了run()方法,該執行緒結束生命週期。

執行緒排程

執行緒的排程:
1.調整執行緒優先順序: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類有一下三個靜態常量作為優先順序,這樣保證同樣的優先順序採用了同樣的排程方式。
2. 執行緒睡眠:Thread.sleep(long millis)方法,是執行緒轉到阻塞狀態,millis引數設定的睡眠時間,當睡眠結束後,就轉為就緒(Runnable)狀態。sleep()平臺移植性好。
3. 執行緒等待:Object類中的這個wait方法,導致當前的執行緒等待,直到其他執行緒呼叫此物件的notify()方法或者notifyAll()喚醒方法,這兩個喚醒方法也是Object類中的方法,行為等價於呼叫wait(0)一樣。
4. 執行緒讓步:Thread.yield()方法,暫停當前正在執行的執行緒物件,把執行機會讓給相同或者更高優先順序的執行緒。
5. 執行緒加入:join方法,等待其他執行緒終止,在當前執行緒中呼叫另一個執行緒的join方法,則當前執行緒轉入阻塞狀態,直到另一個程序執行結束,當前執行緒再由阻塞轉為就緒狀態。
6. 執行緒喚醒:Object類中的notify()方法,喚醒在此物件監視器上等待的單個執行緒。如果所有執行緒都在此物件上等待,則會選擇喚醒其中一個執行緒,選擇是任意性的,並對實現做出決定時發生,執行緒通過呼叫其中一個wati方法,在物件的監視器上等待,直到當前執行緒檔期此物件上的鎖,才能繼續執行被喚醒的執行緒,被喚醒的執行緒將以常規的方式與該物件上主動同步的其他所有執行緒進行競爭,例如,喚醒的執行緒在作為鎖定此物件的下一個執行緒方面沒有可靠的特權或劣勢。類似的方法還有一個notifyAll方法,喚醒此物件監視器上等待的所有執行緒。
注意:Thread中suspend和resume這倆方法已經在JDK1.5中廢除了,因為死鎖傾向!