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方法而死亡。