1. 程式人生 > 實用技巧 >Java基礎Day08(多執行緒)

Java基礎Day08(多執行緒)

多執行緒

1. 執行緒

1.1 什麼是執行緒:

  程式中負責執行的哪個東東就叫做執行緒(執行路線,程式內部的執行序列或者說是程式的子任務)

  多執行緒執行時,在棧記憶體中,每一個執行執行緒都有自己所屬的棧記憶體空間。進行方法的壓棧和彈棧。

1.2 流程圖:

1.3 自定義多執行緒:

public class MyThread extends Thread{
/*
* 利用繼承中的特點
* 將執行緒名稱傳遞 進行設定
*/
  public MyThread(String name){
  super(name);
}
/*
* 重寫run方法
* 定義執行緒要執行的程式碼
*/
  public void run(){
  for (int i = 0; i < 20; i++) {
  //getName()方法 來自父親
  System.out.println(getName()+i);
    }
  }
}
    //測試類
    public class Demo {     public static void main(String[] args) {       System.out.println("這裡是main執行緒");       MyThread mt = new MyThread("小強");       mt.start();//開啟了一個新的執行緒         for (int i = 0; i < 20; i++) {           System.out.println("旺財:"+i);           }         }       }

  

1.4 併發與並行

  • 併發:指兩個或多個事件在同一個時間段內發生。
  • 並行:指兩個或多個事件在同一時刻發生(同時發生)。
  注意:單核處理器的計算機肯定是不能並行的處理多個任務的,只能是多個任務在單個CPU上併發執行。同   理,執行緒也是一樣的,從巨集觀角度上理解執行緒是並行執行的,但是從微觀角度上分析卻是序列執行的,即一個   執行緒一個執行緒的去執行,當系統只有一個CPU時,執行緒會以某種順序執行多個執行緒,我們把這種情況稱之為   執行緒排程。

1.5 執行緒與程式

  程式:是指一個記憶體中執行的應用程式,每個程式都有一個獨立的記憶體空間,一個應用程式可以同時執行多 個程式;程式也是程式的一次執行過程,是系統執行程式的基本單位;系統執行一個程式即是一個程式從創 建、執行到消亡的過程。   執行緒
:執行緒是程式中的一個執行單元,負責當前程式中程式的執行,一個程式中至少有一個執行緒。一個程式 中是可以有多個執行緒的,這個應用程式也可以稱之為多執行緒程式。 簡而言之:一個程式執行後至少有一個程式,一個程式中可以包含多個執行緒

1.6 執行緒排程:

  • 分時排程 : 所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間。
  • 搶佔式排程 : 優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個(執行緒隨機性),Java使用的為搶佔式排程。設定執行緒的優先順序

1.7 搶佔式排程詳解

  大部分作業系統都支援多程式併發執行,現在的作業系統幾乎都支援同時執行多個程式。比如:現在我 們上課一邊使用編輯器,一邊使用錄屏軟體,同時還開著畫圖板,dos視窗等軟體。此時,這些程式是 在同時執行,”感覺這些軟體好像在同一時刻執行著“。   實際上,CPU(中央處理器)使用搶佔式排程模式在多個執行緒間進行著高速的切換。對於CPU的一個核而 言,某個時刻,只能執行一個執行緒,而 CPU的在多個執行緒間切換速度相對我們的感覺要快,看上去就是 在同一時刻執行。 其實,多執行緒程式並不能提高程式的執行速度,但能夠提高程式執行效率,讓CPU的 使用率更高。

1.8 Thread類

API中該類中定義了有關執行緒的一些方法,具體如下:

1.9 構造方法:

  • public Thread() :分配一個新的執行緒物件。
  • public Thread(String name) :分配一個指定名字的新的執行緒物件。
  • public Thread(Runnable target) :分配一個帶有指定目標新的執行緒物件。
  • public Thread(Runnable target,String name) :分配一個帶有指定目標新的執行緒物件並指定名字。

1.10 常用方法:

  • public String getName() :獲取當前執行緒名稱。
  • public void start() :導致此執行緒開始執行; Java虛擬機器器呼叫此執行緒的run方法。
  • public void run() :此執行緒要執行的任務在此處定義程式碼。
  • public static void sleep(long millis) :使當前正在執行的執行緒以指定的毫秒數暫停(暫時停止執行)。
  • public static Thread currentThread() :返回對當前正在執行的執行緒物件的引用。

1.11 建立執行緒類

  Java使用 java.lang.Thread 類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項。每個執行緒的作用是完成一定的任務,實際上就是執行一段程式流即一段順序執行的程式碼。Java使用執行緒執行體來代表這段程式流。 Java中通過繼承Thread類來建立並啟動多執行緒的步驟如下:   1. 定義Thread類的子類,並重寫該類的run()方法該run()方法的方法體就代表了執行緒需要完成的任務,因此把run()方法稱為執行緒執行體。   2. 建立Thread子類的例項,即建立了執行緒物件   3. 呼叫執行緒物件的start()方法來啟動該執行緒
測試類
public class Demo01 {
  public static void main(String[] args) {
  //建立自定義執行緒物件
  MyThread mt = new MyThread("新的執行緒!");
  //開啟新執行緒
  mt.start(); //在主方法中執行for迴圈
  for (int i = 0; i < 10; i++) {
    System.out.println("main執行緒!"+i);
    }
  }
} 自定義執行緒類:
public class MyThread extends Thread {
  //定義指定執行緒名稱的構造方法
  public MyThread(String name) {
 //呼叫父類的String引數的構造方法,指定執行緒的名稱
 super(name);
  /**   * 重寫run方法,完成該執行緒執行的邏輯   */   @Override   public void run() {     for (int i = 0; i < 10; i++) {     System.out.println(getName()+":正在執行!"+i);     }    }   } }

1.12 建立執行緒方式二

步驟如下:

  1. 定義Runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體。   2. 建立Runnable實現類的例項,並以此例項作為Thread的target來建立Thread物件,該Thread物件才是真正的執行緒物件。   3. 呼叫執行緒物件的start()方法來啟動執行緒。
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
} } }
public class Demo {
public static void main(String[] args) {
//建立自定義類物件 執行緒任務物件
MyRunnable mr = new MyRunnable();
//建立執行緒物件
Thread t = new Thread(mr, "小強");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("旺財 " + i);
} } }

  

  • 通過實現Runnable介面,使得該類有了多執行緒類的特徵。
  • run()方法是多執行緒程式的一個執行目標。
  • 所有的多執行緒程式碼都在run方法裡面。
  • Thread類實際上也是實現了Runnable介面的類。

1.13 Thread和Runnable的區別

  如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable介面的話,則很容易的實現資源共享。

1.14 實現Runnable介面比繼承Thread類所具有的優勢:

  1. 適合多個相同的程式程式碼的執行緒去共享同一個資源。   2. 可以避免java中的單繼承的侷限性。   3. 增加程式的健壯性,實現解耦操作,程式碼可以被多個執行緒共享,程式碼和執行緒獨立。   4. 執行緒池只能放入實現Runable或Callable類執行緒,不能直接放入繼承Thread的類。   擴充:在java中,每次程式執行至少啟動2個執行緒。一個是main執行緒,一個是垃圾收集執行緒。因為每當使用命Java令執行一個類的時候,都會啟動一個JVM,每一個JVM其實在就是在作業系統中啟動了一個程式。

1.15 匿名內部類方式實現執行緒的建立

  使用執行緒的內匿名內部類方式,可以方便的實現每個執行緒執行不同的執行緒任務操作。   使用匿名內部類的方式實現Runnable介面,重新Runnable介面中的run方法:
public class NoNameInnerClassThread {
public static void main(String[] args) {
// new Runnable(){
// public void run(){
// for (int i = 0; i < 20; i++) {
// System.out.println("張宇:"+i);
// }
// }
// }; //‐‐‐這個整體 相當於new MyRunnable()
Runnable r = new Runnable(){
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("張宇:"+i);
} }
};
new Thread(r).start();
for (int i = 0; i < 20; i++) {
System.out.println("費玉清:"+i);
} } }

2.1 執行緒安全

  如果有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。程式每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。

2.2 執行緒同步

  使用多個執行緒訪問同一資源的時候,且多個執行緒中對資源有寫的操作,就容易出現執行緒安全問題。
  • 同步程式碼塊。

    • synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
      • 格式:synchronized(同步鎖){需要同步操作的程式碼}
      • /*
        * 執行賣票操作
        */
        @Override
        public void run() {
        //每個視窗賣票的操作
        //視窗 永遠開啟
        while(true){
        synchronized (lock) {
        if(ticket>0){//有票 可以賣
        //出票操作
        //使用sleep模擬一下出票時間
        try {
        Thread.sleep(50);
        } catch (InterruptedException e) {
        // TODO Auto‐generated catch block
        e.printStackTrace();
        }
        //獲取當前執行緒物件的名字
        String name = Thread.currentThread().getName();
        System.out.println(name+"正在賣:"+ticket‐‐);
        } } }
  • 同步方法。

    • 同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A執行緒執行該方法的時候,其他執行緒只能在方法外等著。

      • 格式: public synchronized void method(){可能會產生執行緒安全問題的程式碼}
      • /*
        * 執行賣票操作
        */
        @Override
        public void run() {
        //每個視窗賣票的操作
        //視窗 永遠開啟
        while(true){
        sellTicket();
        } }
        /*
        * 鎖物件 是 誰呼叫這個方法 就是誰
        * 隱含 鎖物件 就是 this
        *
        */
        public synchronized void sellTicket(){
        if(ticket>0){//有票 可以賣
        //出票操作
        //使用sleep模擬一下出票時間
        try {
        Thread.sleep(100);
        } catch (InterruptedException e) {
        // TODO Auto‐generated catch block
        e.printStackTrace();
        }
        //獲取當前執行緒物件的名字
        String name = Thread.currentThread().getName();
        System.out.println(name+"正在賣:"+ticket‐‐);
        } } }
  • 鎖機制。

    • java.util.concurrent.locks.Lock 機制提供了比synchronized程式碼塊和synchronized方法更廣泛的鎖定操作,同步程式碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向物件。Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了。

      • public void lock() :加同步鎖。
      • public void unlock() :釋放同步鎖 
      • Lock lock = new ReentrantLock();
        /*
        * 執行賣票操作
        */
        @Override
        public void run() {
        //每個視窗賣票的操作
        //視窗 永遠開啟
        while(true){
        lock.lock();
        if(ticket>0){//有票 可以賣
        //出票操作
        //使用sleep模擬一下出票時間
        try {
        Thread.sleep(50);
        } catch (InterruptedException e) {
        // TODO Auto‐generated catch block
        e.printStackTrace();
        }
        //獲取當前執行緒物件的名字
        String name = Thread.currentThread().getName();
        System.out.println(name+"正在賣:"+ticket‐‐);
        }
        lock.unlock();
        } } }

          

2.3 執行緒狀態

執行緒狀態           導致狀態發生條件
    NEW(新建)       執行緒剛被建立,但是並未啟動。還沒呼叫start方法。
Runnable(可執行) 執行緒可以在java虛擬機器器中執行的狀態,可能正在執行自己程式碼,也可能沒有,這取決於作業系統處理器。
Blocked(鎖阻塞) 當一個執行緒試圖獲取一個物件鎖,而該物件鎖被其他的執行緒持有,則該執行緒進入Blocked狀態;當該執行緒持有鎖時,該執行緒將變成Runnable狀態。
Waiting(無限等待) 一個執行緒在等待另一個執行緒執行一個(喚醒)動作時,該執行緒進入Waiting狀態。進入這個狀態後是不能自動喚醒的,必須等待另一個執行緒呼叫notify或者notifyAll方法才能夠喚醒。
TimedWaiting(計時等待) 同waiting狀態,有幾個方法有超時引數,呼叫他們將進入Timed Waiting狀態。這一狀態將一直保持到超時期滿或者接收到喚醒通知。帶有超時引數的常用方法有Thread.sleep 、Object.wait。
Teminated(被終止) 因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。