1. 程式人生 > 實用技巧 >Java進階——執行緒與多執行緒

Java進階——執行緒與多執行緒

執行緒和多執行緒

概念

  • 程式

    程式是一段靜態程式碼。
  • 程序

    程序是程式的一次動態執行過程(從程式碼載入、執行、執行完畢的完整過程)。程序是資源分配的最小單位。
  • 執行緒

    執行緒是CPU排程的最小執行單位。程式執行過程中可以產生多個執行緒。

程序和執行緒的區別

  1. 程序:一個應用程式對應一個程序;程序是資源分配的最小單位;通過多執行緒佔據系統資源;程序之間資料狀態完全獨立。
  2. 執行緒:一個程序可以有多個執行緒;執行緒是執行程式的最小單元;執行緒是佔用CPU的基本單位;執行緒之間共享一塊記憶體空間。

執行緒的生命週期

  • 新建狀態

    執行緒物件建立,還未呼叫start()方法。
  • 就緒狀態

    呼叫start()方法,但排程程式還未將其選為可執行執行緒。
  • 執行狀態

    執行緒排程程式從可執行池中選擇一個執行緒作為當前執行緒。
  • 阻塞狀態

    • 等待狀態
    • 阻塞狀態
    • 睡眠狀態
      執行緒是活的,但沒有條件執行;當某事件發生後,可返回到就緒狀態。
  • 死亡狀態

    執行緒run()方法完成。執行緒一旦死亡,不可復生(呼叫start()會拋異常)。

Java的執行緒

主執行緒

每個Java程式都有一個預設的主執行緒。

當JVM載入程式碼,發現main方法後,會立即啟動一個執行緒(主執行緒)
主執行緒特點

  1. 產生其他子執行緒的執行緒
  2. 不一定是最後完成執行的執行緒

建立執行緒

  1. 繼承Thread類
    • 重寫run()
      方法
    • new一個執行緒物件
    • 呼叫物件的start()方法啟動執行緒
  2. 實現Runnable介面
    • 實現run()方法
    • 建立一個Runnable類的物件
    • 建立Thread類物件,將Runnable物件作為引數
    • 呼叫Thread物件的start()方法啟動執行緒
  • 一個執行緒只能被啟動一次run()方法執行結束後,執行緒結束。
  • 一個程式多個執行緒,執行緒只能保證開始時間,結束時間和執行順序無法確定。
  • 執行緒排程採用佇列形式;JVM執行緒排程程式決定執行就緒狀態的某個執行緒。
  • 執行的執行緒有名字
    • 可通過JVM預設執行緒名字
    • 自定義執行緒名字setName()
//給子執行緒命名
//預設為Thread-
Thread1 t1=new Thread1();
Thread t=new Thread(t1);
t.start();
t.setName("t1");

//獲取主執行緒,並命名
Thread.currentThread().setName("mm");
System.out.println(Thread.currentThread().getName());

建立執行緒方法對比

  1. 繼承Thread類
    1. 編寫簡單,訪問當前執行緒this
    2. java是單繼承機制,不能繼承其他父類;沒有達到資源共享
  2. 實現Runnable介面
    1. 程式設計複雜,訪問當前執行緒Thread.currentThread()
    2. java是多介面實現,可繼承其他類;可多個執行緒共享同一個目標物件。
//Runnable
//多執行緒解決同一問題
Thread1 t1 = new Thread1();
new Thread(t1).start();
new Thread(t1).start();
new Thread(t1).start();

//Thread
//資源不共享
Thread t1 = new Thread1();
Thread t2 = new Thread2();
Thread t3 = new Thread3();
t1.start();
t2.start();
t3.start();

執行緒的方法

方法名 功能
start() 啟動執行緒,讓執行緒從新建狀態進入就緒狀態佇列
run() 普通方法,執行緒物件被排程後執行的操作
sleep() 暫停執行緒的執行,休眠執行緒
yield() 暫停正在執行的執行緒,讓同等優先順序的執行緒執行
join() 暫停當前執行緒的執行,等呼叫該方法的執行緒執行完後,執行緒返回就緒狀態
interrupt() 喚醒休眠的執行緒
stop() 終止執行緒
isAlive() 測試執行緒的狀態;新建/死亡狀態=false
currentThread() 返回當前正在執行執行緒物件的引用
//輸出 (新建狀態、死亡狀態為false)
//false  false
//join方法將主執行緒暫停,先執行Thread1執行緒
Thread1 t1=new Thread1();
Thread t=new Thread(t1);
System.out.print(t.isAlive());
t.start();
t.join();
System.out.print(t.isAlive());

設定執行緒優先順序

  • 通過Thread的setPriority()方法設定執行緒優先順序
    • Thread.MIN_PRIORITY ——1
    • Thread.NORM_PRIORITY ——5
    • Thread.MAX_PRIORUTY ——10
  • 通過thread的getPriority()方法得到執行緒優先順序
  • 執行緒預設優先順序為建立其的執行狀態執行緒的優先順序

執行緒讓步

當執行緒池中的執行緒具有相同優先順序

  1. 選擇一個執行緒執行,知道執行緒阻塞或執行結束
  2. 時間分片,為執行緒池中每個執行緒提供均等執行機會

多執行緒執行時,JVM按優先順序排程,級別相同的由作業系統按時間片分配。

阻止執行緒執行

執行緒睡眠 sleep()

當執行緒睡眠時,暫停執行;甦醒前不會回到就緒狀態;
當睡眠時間到期,執行緒回到就緒狀態。

  • 執行緒睡眠可幫助其他執行緒獲得執行機會的最好方法
  • 執行緒甦醒後,返回到就緒狀態
  • sleep()指定時間為最短睡眠時間
  • sleep()為靜態方法,只能控制當前執行的執行緒

執行緒等待 yield()

執行緒讓步,暫停當前正在執行的執行緒物件,並執行同等優先順序的其他執行緒
yield()使執行緒從執行狀態——>就緒狀態;讓步的執行緒也有可能被執行緒排程程式選中。

執行緒阻塞 join()

執行緒A中呼叫執行緒B的join()方法,讓執行緒A置於執行緒B的尾部。
線上程B執行完畢之前,執行緒A一直處於阻塞狀態,只有當B執行緒執行完畢時,A執行緒才能繼續執行

當join(100)帶有引數時,如果A執行緒中掉用B執行緒的join(100),則表示A執行緒會等待B執行緒執行100毫秒,100毫秒過後,A、B執行緒並行執行;同時join(0)==join()

join方法必須線上程start方法呼叫之後呼叫才有意義

在主執行緒中執行程式:建立A、B兩個子執行緒,首先呼叫執行緒A的start()方法執行執行緒A;
呼叫執行緒A的join()方法,使主執行緒進入阻塞狀態,只有當執行緒A執行完畢後,才能執行主執行緒
執行緒A執行完畢後,主執行緒才可執行,呼叫執行緒B的start()方法執行執行緒B。

Thread a = new ThreadA();
Thread b = new ThreadB();
//執行緒A開始執行
a.start();
//執行緒A呼叫join()
a.join();
//執行緒B開始執行
b.start();

多執行緒

物件互斥鎖

Java每個物件都對應一個互斥鎖的標記。
每個物件只有一個鎖(lock)與之相關聯.
synchronized關鍵字與物件互斥鎖聯合使用,保證物件在任意時刻只能由一個執行緒訪問。
避免多個執行緒進行訪問導致資料不同步的問題。

  • 修飾程式碼塊,該程式碼塊在任意時刻只能由一個執行緒訪問
    • 作用範圍:程式碼塊{}的內容
    • 作用物件:呼叫該程式碼塊的物件
//實現Runnable介面
public class Thread1 implements Runnable {

	private static int count;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		// 1. 修飾程式碼塊
		// 同步語句塊
		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				count++;
				System.out.println(Thread.currentThread().getName() + ":" + count);
			}
		}

	}

}
// 同一個物件
//實現資源共享、資源同步
Thread1 thread = new Thread1();
new Thread(thread).start();
new Thread(thread).start();

// 不同物件
//實現資源同步、多執行緒進行處理
Thread1 thread = new Thread1();
Thread1 thread1 = new Thread1();
new Thread(thread).start();
new Thread(thread1).start();
  • 修飾方法,表示該方法在任意時刻只能由一個執行緒訪問
    • 作用範圍:方法內容
    • 作用物件:呼叫方法的物件
    • 關鍵字synchronized不可繼承
    • 定義介面不可使用關鍵字synchronized修飾
    • 構造方法不可使用關鍵字synchronized修飾,但可以用同步程式碼塊
public synchronized void print(){
      //todo
}
  • 修飾靜態方法,
    • 作用範圍:靜態方法內容
    • 作用物件:這個類的所有物件
public synchronized static void print(){
      //todo
}
  • 修飾類,表示該類的所有物件公用一把鎖
    • 作用範圍:{}包括的所有內容
    • 作用物件:這個類的所有物件
class ClassTest{
      public void method(){
            synchronized(ClassTest.class){
                  //todo
            }
      }
}

多執行緒同步

為了更好的解決多個互動執行緒之間的執行進度。
引入wait()方法與notify()方法
wait()方法:使當前執行緒進行等待狀態
notify()方法:通知那些等待該物件鎖的其他執行緒,使其重新獲取該物件的物件鎖。

  • wait()notify()方法必須配合synchronized關鍵字使用
  • wait()會釋放鎖,notify()不會釋放鎖
  • wait()方法執行後,執行interrupt()方法會報異常

死鎖

死鎖:當兩個或兩個以上的執行緒在執行過程中時,因爭奪資源造成互相等待,若無外力作用,執行緒都無法推進下去的現象。
必要條件

  1. 互斥條件
    • 執行緒對分配到的資源進行排他性使用;其他執行緒不可使用。
  2. 請求、保持條件
    • 執行緒保持至少一個資源,但又提出新的資源請求。
  3. 不可剝奪條件
    • 執行緒獲得的資源在使用完之前,不可被剝奪;只能在使用完後自主釋放。
  4. 環路等待條件
    • 發生死鎖時,必然存線上程請求資源、資源被另一執行緒佔用的環。

執行緒池

[java 執行緒方法join的簡單總結][join]
[Java中Runnable和Thread的區別][thread]
[Java中Synchronized的用法][synchronized]
[join]:https://www.cnblogs.com/lcplcpjava/p/6896904.html
[thread]:https://developer.51cto.com/art/201203/321042.htm
[synchronized]:https://www.cnblogs.com/fnlingnzb-learner/p/10335662.html