天天看點

技術自查第四篇:線程基礎篇前言線程與程序的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例

前言

面試題中常見的題目之一,很多人都能解答出簡單的線程問題。但往往面試官再深入,很多時候就啞口無言。同時線程也是我們開發中常用的,故我們必須要好好學習線程,深入了解原理。

要了解線程,請先記住以下這句話。

鎖的機制:基于線程,不是基于方法

線程與程序的關系

https://www.zhihu.com/question/25532384

程序是資源最小排程機關,線程是程序最小排程機關,理論上一個程序可以有無數個線程。

程序之間互不影響,線程之間可以互相影響。

打個比喻:

系統=火車站

程序=火車

線程=車廂

一個火車站可以有多輛火車,一輛火車有多節車廂

火車站可以排程多輛火車,而且每輛火車是獨立的,互不影響。

每輛火車的車廂資源可以是獨立的,也可以是共享的,例如車站服務人員,安全員,食物等

 鎖,線程與代碼塊的關系

打個比喻

場景:下雨天,人想要到房子裡避雨,鑰匙隻有一把,每次隻能有一個人拿着鑰匙進屋避雨,而且每次隻能進入房子五分鐘

鎖:鑰匙

代碼塊:房子

線程:人

  1. 下雨天,很多人都想進屋避雨,但奈何鑰匙隻有一把,這時候這些人隻能在進入區等待鑰匙,他們的狀态是就緒的
  2. 當某個人獲得鑰匙且進入房子時,這個人是在擁有者區,狀态是運作的
  3. 五分鐘過後,他隻能出去屋子了,這時候他的狀态是終止的,不屬于任何區域中,
  4. 但如果這個人死活不出來,這時候就造成阻塞了。
  5. 但如何這個人跟其中某個人達成協定,先出去一塊兒,後面再補上剩餘時間,也是可以的,這時候這個人就處于等待狀态,進入等待區。

線程的六種狀态

  1. 初始(NEW)
  2. 就緒(READY)
  3. 運作(RUNNING)
  4. 阻塞(BLOCKED)
  5. 等待(WAIT/TIMED_WAITING)
  6. 終止(TERMINATED)
技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例

上圖出現比較多的關鍵詞,都是很重要的

關鍵詞:yield、sleep、wait、notify、notifyall、park、unpark、join

  1. yield:讓出cpu排程權,優先讓給同級或進階的線程,但也有可能下一個獲得cpu排程權的是自己,不釋放鎖,讓線程進入就緒狀态
  2. sleep:讓線程暫停運作一段時間,讓出cpu排程權,無所謂高低之分的線程,不釋放鎖,進入等待狀态
  3. join:并行改串行,A線程中優先運作B線程,不釋放鎖,也可以說釋放鎖(比較特殊)。
  4. wait:讓線程暫停運作,進入等待區,等待notify/notifyAll喚醒,釋放鎖
  5. notify:喚醒處于某個等待狀态的線程,僅限調用wait()方法的線程,随機喚醒一個!!!
  6. notifyall:喚醒所有處于等待狀态的線程,僅限調用wait()方法的線程,全部!!!
  7. park:讓線程處于阻塞狀态,不釋放鎖
  8. unpark:讓特定某個處于阻塞狀态的線程重新運作

差別

所屬方法 線程狀态 是否釋放鎖 是否造成阻塞 作用 備注
sleep Thread 等待 一定時間停止運作線程,讓出cpu排程權,讓其他線程運作 任何等級的線程都可争奪cpu排程權
yield Thread 就緒 讓出cpu排程權,進入就緒狀态,讓其他線程運作 讓給同級或進階的線程,但也有可能下一個運作的線程是自己
join Thread 等待 是(也可以說否,視情況而定) 并行該串行,A線程裡運作B線程,且等待B線程運作完畢

1. 當鎖的類型是線程本身,則會釋放鎖,否則不釋放鎖

2. 低層調用wait()方法

wait Object 等待 釋放鎖,進入等待狀态 與park不同,線程是釋放鎖,且進入等待狀态
park LockSupport 阻塞 線程挂起,不釋放鎖 與wait不同,線程不釋放鎖,且進入阻塞狀态
notify Object / / / 随機喚醒等待狀态的線程,但僅限調用wait狀态的線程 随機喚醒一條
notifyAll Object / / / 喚醒全部等待狀态的線程,僅限調用wait狀态的線程 喚醒全部
unPark LockSupport / / / 喚醒特定某條阻塞狀态的線程 特定某條阻塞狀态的線程

 線程的三個區域

  1. 進入區
  2. 擁有者區
  3. 等待區
技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例

還有一個特殊區域

臨界區:指的是同步代碼塊

上圖可以看出

  1. 擁有者區(OWNER SET)域永遠隻有一個線程且是正在執行的線程,也就是代表是獲得鎖的線程
  2. 等待區(WAIT SET),區中有多個線程,這些線程曾經獲得所,但都是調用wait()、join()方法,釋放鎖的線程,它們等待着被某種條件喚醒,重新獲得鎖,進入擁有者區
  3. 進入區(ENTRY SET),區中有多個線程,這些線程未曾獲得所,都在等待擁有者區的線程釋放鎖
線程的六種狀态裡還有synchronized和lock這兩個關鍵詞,它們都是同步線程的标志。

線程

系統角度

守護線程:一般指jvm的日志記錄線程,gc線程等含有daemon辨別的線程(gc分析日志的常客)

使用者線程:非守護線程

預設是Thread.setDaemon(false)
使用者線程可以通過Thread.setDaemon(true)設定為守護線程
           

同步角度

同步線程:指的是使用synch和lock關鍵詞的線程

非同步線程:顧名思義

jstack的線程日志,含有daemon辨別

"http-nio-8080-Poller" #27 daemon prio=5 os_prio=0 cpu=0.00ms elapsed=625.33s tid=0x0000018711395800 nid=0x469c runnable  [0x0000004209dfe000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0([email protected]/Native Method)
	at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll([email protected]/WindowsSelectorImpl.java:339)
	at sun.nio.ch.WindowsSelectorImpl.doSelect([email protected]/WindowsSelectorImpl.java:167)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect([email protected]/SelectorImpl.java:124)
	- locked <0x0000000710b2ce48> (a sun.nio.ch.Util$2)
	- locked <0x0000000710b2cdc0> (a sun.nio.ch.WindowsSelectorImpl)
	at sun.nio.ch.SelectorImpl.select([email protected]/SelectorImpl.java:136)
	at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:787)
	at java.lang.Thread.run([email protected]/Thread.java:834)

   Locked ownable synchronizers:
	- None
           

synchronize和lock的差別

  1. synchronize是關鍵字,可以修飾成員變量,方法,lock是一個類
  2. synchronize線程運作結束,會自動釋放鎖,而lock需要手動釋放
  3. synchronize線程異常時,會自動釋放鎖,而lock不會,且必須要在finally代碼塊釋放
  4. synchronize是公平鎖,lock可以設定成非公平鎖,也可以是公平鎖
  5. synchronize不支援非阻塞式擷取鎖,lock支援
  6. synchronize不可以響應中斷,lock可以(使用interrupt()方法中斷)
  7. synchronize使用wait()方法進入等待狀态的線程,通過notify/notifyAll喚醒,而且隻能是随機喚醒,而lock可以通過condition喚醒特定的等待線程
  8. synchronize是基于jvm實作的内置鎖,每個對象都可以作為鎖,對于同步方法,鎖是目前示例對象,對于同步靜态方法,鎖是目前class對象,對于同步代碼塊,鎖是目前synchronize代碼内的對象,lock是程式設計語言層實作的鎖
  9. synchronize和lock都是可重入鎖
技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例
技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例
本質 運作結束是否自動釋放鎖 異常時是否自動釋放鎖 鎖類型 鎖的機制 是否支援非阻塞式擷取鎖 是否可響應中斷 能否擷取鎖的狀态 是否獨享鎖 排程機制 是否可重入鎖
synchronize java的關鍵字 非公平鎖 悲觀鎖 不支援 不可以 不能 通過notify/notifyAll喚醒,且隻能随機喚醒
lock java的一個類 公平鎖/非公平鎖 樂觀鎖 支援 支援

分情況:

1.ReadWriteLock的writeLock是獨享鎖

2. ReadWriteLock的readLock是共享鎖

可以通過condition喚醒特定的線程

額外點

lock的方法

  1. lock:擷取鎖,必須配合unlock使用
  2. unlock:釋放鎖,必須放到finally代碼塊
  3. lockinterrupt:中斷線程等待鎖狀态
  4. trylock:一定次數嘗試擷取鎖
  5. trylock(time):一定時間内嘗試擷取鎖

synchronize

  1. 修飾方法:鎖是目前this對象
  2. 修飾靜态方法:鎖是目前class的對象
  3. 修飾代碼塊:指定加鎖對象,(代碼塊内的内容)

剛才也提到過鎖的類型,其實鎖還分公平鎖/非公平鎖,獨享鎖/共享鎖,悲觀鎖/樂觀鎖,偏向鎖/輕量級鎖/重量級鎖,可重入鎖

鎖的類型

公平鎖/非公平鎖(java代碼)

公平鎖:線程排隊式擷取鎖,先到先得

非公平鎖:線程競争式擷取鎖,誰搶到是誰

synch是非公平鎖,lock可以是公平鎖,也可以是非公平鎖

獨享鎖/共享鎖(設計想法)

獨享鎖:一次隻能一個線程擷取

共享鎖:一次可以被多個線程擷取

synch是獨享鎖,lock分情況:ReadWriteLock的ReadLock是共享鎖,WriteLock是獨享鎖

讀寫鎖/互斥鎖

如果說獨享鎖/共享鎖是設計想法,那麼讀寫鎖/互斥鎖就是實作

互斥鎖:synchronize

讀寫鎖:lock

技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例

 樂觀鎖/悲觀鎖(設計思想)

樂觀鎖:認為線程擷取鎖操作時,不會對資料進行修改

悲觀鎖:與樂觀鎖想法,認為線程擷取鎖時,會對資料進行修改

分段鎖(設計思想)

細化鎖的顆粒度,實作高效的并發操作。例如concurrentHash,内部擁有一個entry數組,每個元素既是元素又是連結清單,同時又是一個ReentLock,操作時,會被元素進行加鎖

可中斷鎖(java代碼編寫)

處于阻塞或等待狀态的線程,不想繼續等待,想處理其他事情,我們可以中斷它的等待狀态。但僅限lock類,synchronize不可中斷

偏向鎖/輕量級鎖/重量級鎖(java代碼編寫)

偏向鎖:當鎖一直隻被同一個線程擷取,則該鎖是偏向鎖

輕量級鎖:當鎖是偏向鎖,這時候被其他線程擷取,則該鎖更新為輕量級鎖

重量級鎖:當鎖是輕量級鎖,且其他線程自旋一定次數都未能擷取鎖,該鎖更新為重量級鎖,jdk1.6之前隻有重量級鎖

自旋鎖(java代碼編寫)

輕量級鎖并不是馬上更新為重量級鎖,而是通過嘗試一定次數擷取鎖,這時候是自旋鎖

可重入鎖(java代碼編寫)

先回顧鎖的機制:基于線程,不是基于方法

當一個線程擷取得到鎖,調用一個方法,該方法又調用其他方法,則其他方法自動擷取得到鎖,不需要通過競争擷取。lock和synchronize都是可重入鎖

lock示例

/**
 * @author Leo
 * @description 可重入鎖案例1,lock示例
 * @createDate 2021/9/7 22:40
 **/
public class DemoThread1 {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    public static void method1() {
        lock.lock();
        try {
            System.out.println("method1");
            method2();
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

    public static void method2() {
        lock.lock();
        try {
            System.out.println("method2");
            method3();
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

    public static void method3() {
        lock.lock();
        try {
            System.out.println("method3");
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

}
           

synchronize示例

/**
 * @author Leo
 * @description 可重入鎖案例2,synchronize示例
 * @createDate 2021/9/7 22:40
 **/
public class DemoThread2 implements Runnable {

    private static Object o = new Object();

    public static void main(String[] args) {
        DemoThread2 demoThread2 = new DemoThread2();
        Thread thread = new Thread(demoThread2);
        thread.start();
    }

    public synchronized void method1(Object o) {
        System.out.println("method1" + o.toString());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method2(Object o) {
        System.out.println("method2" + o.toString());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method3(Object o) {
        System.out.println("method3" + o.toString());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        synchronized (o) {
            System.out.println("method開始" + o.toString());
            method1(o);
            method2(o);
            method3(o);
            System.out.println("method結束" + o.toString());
        }
    }
}
           

同步線程和鎖實作的原理(重點)

要了解原理,需要先知道對象的組成

對象的組成

  1. 對象頭
  2. 類對象指針/填充位元組
  3. 執行個體資料
技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例

 對象頭又分為Mark Word(标記字段)和Klass Point(類型指針)

  1. Klass Point:是對象指向它的類中繼資料的指針,顧名思義,是告訴jvm,本身對象是什麼類型。
  2. Mark Word:是存儲對象運作時狀态變化的資訊,鎖就是與它相關,下面圖檔出現的指針指向的是與之對應的monitor的位址
技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例

Monitor

每個對象的生成都有一個與之對應的monitor(監控器),線程擷取鎖,實際上擷取的是monitor。

monitor是有ObjectMonitor實作的,其主要結構如下(位于虛拟機源碼的ObjectMonitor.hpp檔案,c++實作)

ObjectMonitor() {
    _count        = 0; //記錄數
    _recursions   = 0; //鎖的重入次數
    _owner        = NULL; //指向持有ObjectMonitor對象的線程 
    _WaitSet      = NULL; //調用wait後,線程會被加入到_WaitSet
    _EntryList    = NULL ; //等待擷取鎖的線程,會被加入到該清單
}
           

對應的java源碼

技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例

 從上面可以看出monitor擁有owner,count屬性對應的作用是owner是誰擁有它,count是使用次數

monitor和mark word都已講解完畢,正式開講擷取鎖的過程

  1. 當對象無任何線程擷取,此時owner=null,count=0。
  2. 有線程擷取對象鎖時,owner會記錄線程id,count+1。
  3. 線程運作完畢,釋放鎖,owner會重新變成null,count-1。
結論:線程擷取鎖,實際上擷取的monitor,目的是改變其屬性,改變owner和count,這也是為什麼java裡任何對象都可以當鎖

示例一

public class SyncCodeBlock {
    public int count = 0;
    public void addOne() {
        synchronized (this) {
            count++;
        }
    }
}
           
反編譯後,詳細看到了monitorenter,monitorexit兩個關鍵詞,進入同步代碼塊,monitorenter,退出同步代碼塊,monitorexit,為什麼出現兩次monitorexit,第一次是正常退出,第二次是異常退出(上面也說過synchronize異常時會自動釋放鎖)
public void addOne();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter // 進入同步方法
         4: aload_0
         5: dup
         6: getfield      #2                  // Field count:I
         9: iconst_1
        10: iadd
        11: putfield      #2                  // Field count:I
        14: aload_1
        15: monitorexit // 退出同步方法
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit // 退出同步方法
        22: aload_2
        23: athrow
        24: return
      Exception table
           

示例二

public class SyncMethod {
    public int count = 0;
    public synchronized void addOne() {
        count++;
    }
}
           
反編譯後,這裡沒出現monitorenter,monitorexit兩個關鍵詞,但相反出現了acc_synchronized關鍵詞,該詞也能說明該類是同步類
public synchronized void addOne();
    descriptor: ()V
    // 方法辨別ACC_PUBLIC代表public修飾,ACC_SYNCHRONIZED指明該方法為同步方法
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field count:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field count:I
        10: return
      LineNumberTable:
           

說了這麼久線程的運作,實作原理,線程的區分,鎖的區分,鎖的原理。如果我們想終止該線程,那麼我們需要怎麼辦呢?

線程的終止和中斷

線程終止:是馬上停止運作線程

線程的中斷:通知正在運作的線程,停止運作

區分:一個是馬上終止,一個是通知終止

線程中斷和終止的三個方法

  1. 辨別法
  2. 調用stop(),destory()方法,(被廢棄,jdk11甚至被删除,原因是造成資料不一緻問題,沒有起到真正的作用,JDK文檔也說明過:《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?》)
  3. 調用interrupt()方法,其中調用需要謹慎,當調用時出現異常,該異常就會被捕捉,interrupt()方法生成的通知就會被清楚掉,故也要在異常處增加interrupt()方法。

額外知識

stop、destory、suspend、resume方法差別

  • void suspend() :暫停目前線程
  • void resume() :恢複目前線程
  • void stop() :結束目前線程
  • void destory():抛出一個NoSuchMethodError異常

stop和destory的差別詳解:深入多線程四:停止多線程,你不會還以為是用stop和destroy吧?_一顆剽悍的種子的部落格-CSDN部落格

終止與中斷的圖檔例子

舉個例子,看下圖

終止

當student擷取對象鎖,并且準備寫入資料時,調用了stop方法,線程突然被終止,導緻資料丢失,出現不準确問題

技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例

 中斷

當student擷取對象鎖,并且準備寫入資料時,調用了interrupt方法,通知線程停止運作,但線程不是馬上運作,最後資料沒有丢失,沒有出現不準确問題

技術自查第四篇:線程基礎篇前言線程與程式的關系 鎖,線程與代碼塊的關系線程的六種狀态 線程的三個區域線程synchronize和lock的差別鎖的類型同步線程和鎖實作的原理(重點)線程的終止和中斷附:wait/join/yelid/park/notiy/notifyall/unpark例子經典消費者生産者示例
 線程終止導緻了資料不安全的問題,而且基本上沒有運用的場景,最後在jdk11,stop(),destory(),resume()方法都被删除了

代碼示例

辨別法

/**
 * 通過标志位終止線程
 */
public class FlagThread implements Runnable {

    private static boolean isFlag = true;

    private static int i = 0;

    @Override
    public void run() {
        while (isFlag) {
            System.out.println(i++);
        }
    }

    public static void main(String[] args) {
        FlagThread flagThread = new FlagThread();
        Thread thread = new Thread(flagThread);
        thread.start();
        try {
            // 由于過快,是以加上休眠時間,用于列印,效果
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isFlag = false;
    }
}
           

調用stop()方法

/**
 * 通過stop強制終止線程
 */
public class StopThread implements Runnable {

    private static boolean isFlag = true;

    private static int i = 0;

    @Override
    public void run() {
        while (isFlag) {
            System.out.println(i++);
        }
    }

    public static void main(String[] args) {
        StopThread stopThread = new StopThread();
        Thread thread = new Thread(stopThread);
        thread.start();
        try {
            // 由于過快,是以加上休眠時間,用于列印,效果
            Thread.sleep(5);
            thread.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
           

調用interrupt()方法

/**
 * 通過Interrupt終止線程,不能強制停止線程,隻是通知jvm讓該線程停止運作,具體看線程
 */
public class InterruptThread implements Runnable {

    @Override
    public void run() {
        for (int j = 0; j < 1000; j++) {
            System.out.println(j);
        }
    }

    public static void main(String[] args) {
        InterruptThread interruptThread = new InterruptThread();
        Thread thread = new Thread(interruptThread);
        thread.start();
        try {
            thread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
           

列印日志

0
1
2
3
.........
98
99
           
interrupt方法使用注意,出現異常時解決辦法,如果運作時,出現異常,interrupt将會失效,需要在異常處重新調用interrupt方法
/**
 * 通過Interrupt終止線程,不能強制停止線程,隻是通知jvm讓該線程停止運作,具體看線程
 */
public class Interrupt2Thread implements Runnable {

    @Override
    public void run() {
        for (int j = 0; j < 100; j++) {
            System.out.println(Thread.currentThread().getName() + "...." + j);
            if (Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + "線程中斷");
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //Thread.currentThread().interrupt();// 恢複這句注釋即可運作成功
            }
        }
    }

    public static void main(String[] args) {
        Interrupt2Thread interruptThread = new Interrupt2Thread();
        Thread thread = new Thread(interruptThread,"interruptSleep線程");
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
           

異常時日志

interruptSleep線程....0
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.gc.demo.thread.zhongduan.Interrupt2Thread.run(Interrupt2Thread.java:17)
	at java.base/java.lang.Thread.run(Thread.java:834)
interruptSleep線程....1
interruptSleep線程....2
interruptSleep線程....3
interruptSleep線程....4
interruptSleep線程....5
....
interruptSleep線程....99
           

正常日志

interruptSleep線程....0
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.gc.demo.thread.zhongduan.Interrupt2Thread.run(Interrupt2Thread.java:17)
	at java.base/java.lang.Thread.run(Thread.java:834)
interruptSleep線程....1
interruptSleep線程線程中斷
           

附:wait/join/yelid/park/notiy/notifyall/unpark例子

wait()例子

/**
 * @author Leo
 * @description wait()案例
 * @createDate 2021/8/31 10:39
 **/
public class WaitThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(currentThread().getName() + "...." + i);
        }
    }

    /**
     * wait()是讓運作該行代碼的線程處于wait狀态,釋放鎖
     * main線程運作A線程,當運作到thread.wait()改行代碼時,main線程處于wait狀态,讓A線程運作
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        WaitThread waitThread = new WaitThread();
        Thread thread = new Thread(waitThread, "等待線程");
        synchronized (thread) {
            for (int i = 0; i < 20; i++) {
                if (i == 10) {
                    thread.start();
                    thread.wait();
                }
                System.out.println(currentThread().getName() + "...." + i);
            }
        }

    }
}
           

日志列印

main....0
main....1
main....2
main....3
main....4
main....5
main....6
main....7
main....8
main....9
等待線程....0
等待線程....1
等待線程....2
等待線程....3
等待線程....4
等待線程....5
等待線程....6
等待線程....7
等待線程....8
等待線程....9
等待線程....10
等待線程....11
等待線程....12
等待線程....13
等待線程....14
等待線程....15
等待線程....16
等待線程....17
等待線程....18
等待線程....19
main....10
main....11
main....12
main....13
main....14
main....15
main....16
main....17
main....18
main....19
           

join()-沒有鎖例子

/**
 * @author Leo
 * @description join案例,底層實際調用wait
 * @createDate 2021/9/1 16:44
 **/
public class JoinThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "..." + i);
        }
    }

    /**
     * 線程的join用法,運作結果是,完全輸出線程1結果後,再輸出main結果
     * 讓權,底層調用的wait方法
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        JoinThread thread = new JoinThread();
        Thread t1 = new Thread(thread, "join線程");
        for (int j = 0; j < 20; j++) {
            System.out.println(Thread.currentThread().getName() + "..." + j);
            if (j == 10) {
                t1.start();
                t1.join();
            }
        }
    }
}
           

日志列印

main...0
main...1
main...2
main...3
main...4
main...5
main...6
main...7
main...8
main...9
main...10
join線程...0
join線程...1
join線程...2
join線程...3
join線程...4
join線程...5
join線程...6
join線程...7
join線程...8
join線程...9
join線程...10
join線程...11
join線程...12
join線程...13
join線程...14
join線程...15
join線程...16
join線程...17
join線程...18
join線程...19
main...11
main...12
main...13
main...14
main...15
main...16
main...17
main...18
main...19
           

join()-釋放鎖示例

/**
 * @author Leo
 * @description join-釋放鎖案例,底層實際調用wait
 * @createDate 2021/9/1 16:44
 **/
public class Join3Thread extends Thread {


    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "..." + i);
            }
        }
    }

    /**
     * 釋放鎖篇
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Join3Thread thread = new Join3Thread();
        thread.setName("join3線程");
        thread.start();
        synchronized (thread) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "..." + i);
                if (i == 1) {
                    thread.join();
                }
            }
        }
    }
}
           

日志

main...0
main...1
join3線程...0
join3線程...1
join3線程...2
join3線程...3
join3線程...4
join3線程...5
join3線程...6
join3線程...7
join3線程...8
join3線程...9
main...2
main...3
main...4
main...5
main...6
main...7
main...8
main...9
           

join-不會釋放鎖示例

/**
 * @author Leo
 * @description join-不會釋放鎖案例,底層實際調用wait
 * @createDate 2021/9/1 16:44
 **/
public class Join2Thread implements Runnable {

    private static Object o = new Object();

    @Override
    public void run() {
        synchronized (o) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "..." + i);
            }
        }
    }

    /**
     * 不會釋放鎖篇
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Join2Thread thread = new Join2Thread();
        Thread join2 = new Thread(thread, "join2線程");
        join2.start();
        synchronized (o) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "..." + i);
                if (i == 1) {
                    join2.join();
                }
            }
        }
    }
}
           

日志 

main...0
main...1
           

notify示例

/**
 * @author Leo
 * @description notify示例
 * @createDate 2021/8/31 10:39
 **/
public class NotifyThread implements Runnable {

    private String z = "鎖";

    @Override
    public void run() {
        synchronized (z) {
            for (int i = 0; i < 20; i++) {
                try {
                    z.notify();
                    System.out.println(Thread.currentThread().getName() + "...." + i);
                    z.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * notify()是讓運作該行代碼的線程處于wait狀态,釋放鎖
     * main線程運作A、B線程,由于A、B線程持有同一個鎖,是以導緻互相釋放鎖,争奪鎖
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        NotifyThread notifyThread1 = new NotifyThread();
        NotifyThread notifyThread2 = new NotifyThread();
        Thread thread1 = new Thread(notifyThread1, "notify1線程");
        Thread thread2 = new Thread(notifyThread2, "notify2線程");
        thread1.start();
        thread2.start();
    }
}
           

日志

notify1線程....0
notify2線程....0
notify1線程....1
notify2線程....1
notify1線程....2
notify2線程....2
notify1線程....3
notify2線程....3
notify1線程....4
notify2線程....4
notify1線程....5
notify2線程....5
notify1線程....6
notify2線程....6
notify1線程....7
notify2線程....7
notify1線程....8
notify2線程....8
notify1線程....9
notify2線程....9
notify1線程....10
notify2線程....10
notify1線程....11
notify2線程....11
notify1線程....12
notify2線程....12
notify1線程....13
notify2線程....13
notify1線程....14
notify2線程....14
notify1線程....15
notify2線程....15
notify1線程....16
notify2線程....16
notify1線程....17
notify2線程....17
notify1線程....18
notify2線程....18
notify1線程....19
notify2線程....19
           

notifyAll示例

/**
 * @author Leo
 * @description notifyAll示例
 * @createDate 2021/8/31 10:39
 **/
public class NotifyAllThread implements Runnable {

    private String z = "鎖";

    @Override
    public void run() {
        synchronized (z) {
            for (int i = 0; i < 20; i++) {
                try {
                    z.notifyAll();
                    System.out.println(Thread.currentThread().getName() + "...." + i);
                    z.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * notifyAll()是讓運作該行代碼的線程處于wait狀态,争奪鎖
     * main線程運作A、B、C線程,由于A、B、C線程持有同一個鎖,是以導緻互相釋放鎖,争奪鎖,導緻死鎖
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        NotifyAllThread notifyThread1 = new NotifyAllThread();
        NotifyAllThread notifyThread2 = new NotifyAllThread();
        NotifyAllThread notifyThread3 = new NotifyAllThread();
        Thread thread1 = new Thread(notifyThread1, "notify1線程");
        Thread thread2 = new Thread(notifyThread2, "notify2線程");
        Thread thread3 = new Thread(notifyThread3, "notify3線程");
        thread1.start();
        thread2.start();
        thread3.start();
        return;
    }
} 
           

日志

notify1線程....0
notify3線程....0
notify2線程....0
notify3線程....1
notify2線程....1
notify3線程....2
notify2線程....2
notify3線程....3
notify2線程....3
notify3線程....4
notify2線程....4
notify3線程....5
notify2線程....5
notify3線程....6
notify2線程....6
notify3線程....7
notify2線程....7
notify3線程....8
notify2線程....8
notify3線程....9
notify2線程....9
notify3線程....10
notify2線程....10
notify3線程....11
notify2線程....11
notify3線程....12
notify2線程....12
notify3線程....13
notify2線程....13
notify3線程....14
notify2線程....14
notify3線程....15
notify2線程....15
notify3線程....16
notify2線程....16
notify3線程....17
notify2線程....17
notify3線程....18
notify2線程....18
notify3線程....19
notify2線程....19
notify1線程....1
           

sleep示例

/**
 * @author Leo
 * @description sleep示例
 * @createDate 2021/8/31 10:39
 **/
public class SleepThread implements Runnable {

    private String z = "鎖";

    @Override
    public void run() {
        synchronized (z) {
            for (int i = 0; i < 20; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "...." + i);
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * sleep()是讓運作該行代碼的線程暫時處于wait狀态(),但并不會釋放鎖
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        SleepThread sleepThread1 = new SleepThread();
        SleepThread sleepThread2 = new SleepThread();
        SleepThread sleepThread3 = new SleepThread();
        Thread thread1 = new Thread(sleepThread1, "sleep1線程");
        Thread thread2 = new Thread(sleepThread2, "sleep2線程");
        Thread thread3 = new Thread(sleepThread3, "sleep3線程");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
           

日志

sleep1線程....0
sleep1線程....1
sleep1線程....2
sleep1線程....3
sleep1線程....4
sleep1線程....5
sleep1線程....6
sleep1線程....7
sleep1線程....8
sleep1線程....9
sleep1線程....10
sleep1線程....11
sleep1線程....12
sleep1線程....13
sleep1線程....14
sleep1線程....15
sleep1線程....16
sleep1線程....17
sleep1線程....18
sleep1線程....19
sleep2線程....0
sleep2線程....1
sleep2線程....2
sleep2線程....3
sleep2線程....4
sleep2線程....5
sleep2線程....6
sleep2線程....7
sleep2線程....8
sleep2線程....9
sleep2線程....10
sleep2線程....11
sleep2線程....12
sleep2線程....13
sleep2線程....14
sleep2線程....15
sleep2線程....16
sleep2線程....17
sleep2線程....18
sleep2線程....19
sleep3線程....0
sleep3線程....1
sleep3線程....2
sleep3線程....3
sleep3線程....4
sleep3線程....5
sleep3線程....6
sleep3線程....7
sleep3線程....8
sleep3線程....9
sleep3線程....10
sleep3線程....11
sleep3線程....12
sleep3線程....13
sleep3線程....14
sleep3線程....15
sleep3線程....16
sleep3線程....17
sleep3線程....18
sleep3線程....19
           

yield示例

/**
 * @author Leo
 * @description yield示例
 * @createDate 2021/8/31 10:39
 **/
public class YieldThread implements Runnable {

    private String z = "鎖";

    @Override
    public void run() {
        synchronized (z) {
            for (int i = 0; i < 20; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "...." + i);
                    Thread.yield();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * yield()是讓運作該行代碼的線程暫時處于wait狀态(),讓鎖出來,但也有可能會馬上擷取到鎖
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        YieldThread yieldThread1 = new YieldThread();
        YieldThread yieldThread2 = new YieldThread();
        YieldThread yieldThread3 = new YieldThread();
        Thread thread1 = new Thread(yieldThread1, "yield1線程");
        Thread thread2 = new Thread(yieldThread2, "yield2線程");
        Thread thread3 = new Thread(yieldThread3, "yield3線程");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
           

日志

yield1線程....0
yield1線程....1
yield1線程....2
yield1線程....3
yield1線程....4
yield1線程....5
yield1線程....6
yield1線程....7
yield1線程....8
yield1線程....9
yield1線程....10
yield1線程....11
yield1線程....12
yield1線程....13
yield1線程....14
yield1線程....15
yield1線程....16
yield1線程....17
yield1線程....18
yield1線程....19
yield3線程....0
yield3線程....1
yield3線程....2
yield3線程....3
yield3線程....4
yield3線程....5
yield3線程....6
yield3線程....7
yield3線程....8
yield3線程....9
yield3線程....10
yield3線程....11
yield3線程....12
yield3線程....13
yield3線程....14
yield3線程....15
yield3線程....16
yield3線程....17
yield3線程....18
yield3線程....19
yield2線程....0
yield2線程....1
yield2線程....2
yield2線程....3
yield2線程....4
yield2線程....5
yield2線程....6
yield2線程....7
yield2線程....8
yield2線程....9
yield2線程....10
yield2線程....11
yield2線程....12
yield2線程....13
yield2線程....14
yield2線程....15
yield2線程....16
yield2線程....17
yield2線程....18
yield2線程....19
           

Park示例

/**
 * @author Leo
 * @description
 * @createDate 2021/9/4 21:52
 **/
public class  Park implements Runnable {

    @Override
    public void run() {
        System.out.println("park開始");
        LockSupport.park();
        System.out.println("park結束");
    }
}
           

UnPark

/**
 * @author Leo
 * @description
 * @createDate 2021/9/4 21:52
 **/
public class UnPark implements Runnable {

    private Thread thread;

    public UnPark(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        System.out.println("unPark開始");
        LockSupport.unpark(thread);
        System.out.println("unPark結束");
    }
}
           

運作

/**
 * @author Leo
 * @description park示例
 * @createDate 2021/8/31 10:39
 **/
public class ParkThread {

    /**
     * park()是讓運作該行代碼的線程暫時處于阻塞狀态,且需要unpark讓線程喚醒
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        Park parkThread = new Park();
        Thread threadPark = new Thread(parkThread);
        threadPark.start();
        UnPark unPark = new UnPark(threadPark);
        Thread threadUnPark = new Thread(unPark);
        threadUnPark.start();
    }
}
           

日志

park開始
unPark開始
unPark結束
park結束
           

經典消費者生産者示例

商品

/**
 * @author Leo
 * @description 商品
 * @createDate 2021/9/4 16:07
 **/
public class SteamBread {
    int id;

    public SteamBread(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "SteamBread{" +
                "id=" + id +
                '}';
    }
}
           

貨架

/**
 * @author Leo
 * @description 貨架,最多隻放六件商品
 * @createDate 2021/9/4 16:08
 **/
public class SyncStack {
    int index = 0;

    SteamBread[] stb = new SteamBread[6];

    public synchronized void push(SteamBread steamBread) {
        while (index == stb.length) {
            try {
                this.wait();
            } catch (Exception e) {

            } finally {

            }
        }
        this.notify();
        stb[index] = steamBread;
        this.index++;
    }

    public synchronized SteamBread pop() {
        while (index == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        this.index--;
        return stb[index];
    }
}
           

消費者

/**
 * @author Leo
 * @description 消費者
 * @createDate 2021/9/4 16:09
 **/
public  class Consumer implements Runnable {

    SyncStack ss = null;

    public Consumer(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            SteamBread steamBread = new SteamBread(i);
            ss.pop();
            System.out.println("消費了" + steamBread.toString());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
           

生産者

/**
 * @author Leo
 * @description 生産者
 * @createDate 2021/9/4 16:08
 **/
public class Producer implements Runnable {

    SyncStack ss = null;

    public Producer(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            SteamBread steamBread = new SteamBread(i);
            ss.push(steamBread);
            System.out.println("生産了" + steamBread.toString());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
           

購買場景

/**
 * @author Leo
 * @description 經典消費者,生産者多線程
 * @createDate 2021/8/31 10:39
 **/
public class NotifyCBThread {

    public static void main(String[] args) {
        SyncStack syncStack = new SyncStack();
        Consumer consumer = new Consumer(syncStack);
        Producer producer = new Producer(syncStack);
        Thread cT = new Thread(consumer);
        Thread pT = new Thread(producer);
        pT.start();
        cT.start();
    }


}
           

日志

生産了SteamBread{id=0}
消費了SteamBread{id=0}
消費了SteamBread{id=1}
生産了SteamBread{id=1}
生産了SteamBread{id=2}
消費了SteamBread{id=2}
生産了SteamBread{id=3}
消費了SteamBread{id=3}
生産了SteamBread{id=4}
消費了SteamBread{id=4}
生産了SteamBread{id=5}
消費了SteamBread{id=5}
生産了SteamBread{id=6}
消費了SteamBread{id=6}
生産了SteamBread{id=7}
消費了SteamBread{id=7}
消費了SteamBread{id=8}
生産了SteamBread{id=8}
生産了SteamBread{id=9}
消費了SteamBread{id=9}
生産了SteamBread{id=10}
消費了SteamBread{id=10}
生産了SteamBread{id=11}
消費了SteamBread{id=11}
消費了SteamBread{id=12}
生産了SteamBread{id=12}
生産了SteamBread{id=13}
消費了SteamBread{id=13}
生産了SteamBread{id=14}
消費了SteamBread{id=14}
生産了SteamBread{id=15}
消費了SteamBread{id=15}
生産了SteamBread{id=16}
消費了SteamBread{id=16}
生産了SteamBread{id=17}
消費了SteamBread{id=17}
生産了SteamBread{id=18}
消費了SteamBread{id=18}
生産了SteamBread{id=19}
消費了SteamBread{id=19}