很遺憾,沒有一篇文章能講清楚線程的生命周期!

来源:https://www.cnblogs.com/tong-yuan/archive/2019/10/18/11701358.html
-Advertisement-
Play Games

(手機橫屏看源碼更方便) 註:java源碼分析部分如無特殊說明均基於 java8 版本。 簡介 大家都知道線程是有生命周期,但是彤哥可以認真負責地告訴你網上幾乎沒有一篇文章講得是完全正確的。 常見的錯誤 有:就緒狀態、運行中狀態(RUNNING)、死亡狀態、中斷狀態、只有阻塞沒有等待狀態、流程圖亂畫 ...


thread_life

(手機橫屏看源碼更方便)


註:java源碼分析部分如無特殊說明均基於 java8 版本。

簡介

大家都知道線程是有生命周期,但是彤哥可以認真負責地告訴你網上幾乎沒有一篇文章講得是完全正確的。

常見的錯誤有:就緒狀態、運行中狀態(RUNNING)、死亡狀態、中斷狀態、只有阻塞沒有等待狀態、流程圖亂畫等,最常見的錯誤就是說線程只有5種狀態。

今天這篇文章會徹底講清楚線程的生命周期,並分析synchronized鎖、基於AQS的鎖中線程狀態變化的邏輯。

所以,對synchronized鎖和AQS原理(源碼)不瞭解的同學,請翻一下彤哥之前的文章先熟悉這兩部分的內容,否則肯定記不住這裡講的線程生命周期。

問題

(1)線程的狀態有哪些?

(2)synchronized鎖各階段線程處於什麼狀態?

(3)重入鎖、條件鎖各階段線程處於什麼狀態?

先上源碼

關於線程的生命周期,我們可以看一下java.lang.Thread.State這個類,它是線程的內部枚舉類,定義了線程的各種狀態,並且註釋也很清晰。


public enum State {
    /**
     * 新建狀態,線程還未開始
     */
    NEW,

    /**
     * 可運行狀態,正在運行或者在等待系統資源,比如CPU
     */
    RUNNABLE,

    /**
     * 阻塞狀態,在等待一個監視器鎖(也就是我們常說的synchronized)
     * 或者在調用了Object.wait()方法且被notify()之後也會進入BLOCKED狀態
     */
    BLOCKED,

    /**
     * 等待狀態,在調用了以下方法後進入此狀態
     * 1. Object.wait()無超時的方法後且未被notify()前,如果被notify()了會進入BLOCKED狀態
     * 2. Thread.join()無超時的方法後
     * 3. LockSupport.park()無超時的方法後
     */
    WAITING,

    /**
     * 超時等待狀態,在調用了以下方法後會進入超時等待狀態
     * 1. Thread.sleep()方法後【本文由公從號“彤哥讀源碼”原創】
     * 2. Object.wait(timeout)方法後且未到超時時間前,如果達到超時了或被notify()了會進入BLOCKED狀態
     * 3. Thread.join(timeout)方法後
     * 4. LockSupport.parkNanos(nanos)方法後
     * 5. LockSupport.parkUntil(deadline)方法後
     */
    TIMED_WAITING,

    /**
     * 終止狀態,線程已經執行完畢
     */
    TERMINATED;
}

流程圖

線程生命周期中各狀態的註釋完畢了,下麵我們再來看看各狀態之間的流轉:

thread_life

怎麼樣?是不是很複雜?彤哥幾乎把網上的資料都查了一遍,沒有一篇文章把這個流程圖完整畫出來的,下麵彤哥就來一一解釋:

(1)為了方便講解,我們把鎖分成兩大類,一類是synchronized鎖,一類是基於AQS的鎖(我們拿重入鎖舉例),也就是內部使用了LockSupport.park()/parkNanos()/parkUntil()幾個方法的鎖;

(2)不管是synchronized鎖還是基於AQS的鎖,內部都是分成兩個隊列,一個是同步隊列(AQS的隊列),一個是等待隊列(Condition的隊列);

(3)對於內部調用了object.wait()/wait(timeout)或者condition.await()/await(timeout)方法,線程都是先進入等待隊列,被notify()/signal()或者超時後,才會進入同步隊列;

(4)明確聲明,BLOCKED狀態只有線程處於synchronized的同步隊列的時候才會有這個狀態,其它任何情況都跟這個狀態無關;

(5)對於synchronized,線程執行synchronized的時候,如果立即獲得了鎖(沒有進入同步隊列),線程處於RUNNABLE狀態;

(6)對於synchronized,線程執行synchronized的時候,如果無法獲得鎖(直接進入同步隊列),線程處於BLOCKED狀態;

(5)對於synchronized內部,調用了object.wait()之後線程處於WAITING狀態(進入等待隊列);

(6)對於synchronized內部,調用了object.wait(timeout)之後線程處於TIMED_WAITING狀態(進入等待隊列);

(7)對於synchronized內部,調用了object.wait()之後且被notify()了,如果線程立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;

(8)對於synchronized內部,調用了object.wait(timeout)之後且被notify()了,如果線程立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;

(9)對於synchronized內部,調用了object.wait(timeout)之後且超時了,這時如果線程正好立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;

(10)對於synchronized內部,調用了object.wait()之後且被notify()了,如果線程無法獲得鎖(也就是進入了同步隊列),線程處於BLOCKED狀態;

(11)對於synchronized內部,調用了object.wait(timeout)之後且被notify()了或者超時了,如果線程無法獲得鎖(也就是進入了同步隊列),線程處於BLOCKED狀態;

(12)對於重入鎖,線程執行lock.lock()的時候,如果立即獲得了鎖(沒有進入同步隊列),線程處於RUNNABLE狀態;

(13)對於重入鎖,線程執行lock.lock()的時候,如果無法獲得鎖(直接進入同步隊列),線程處於WAITING狀態;

(14)對於重入鎖內部,調用了condition.await()之後線程處於WAITING狀態(進入等待隊列);

(15)對於重入鎖內部,調用了condition.await(timeout)之後線程處於TIMED_WAITING狀態(進入等待隊列);

(16)對於重入鎖內部,調用了condition.await()之後且被signal()了,如果線程立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;

(17)對於重入鎖內部,調用了condition.await(timeout)之後且被signal()了,如果線程立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;

(18)對於重入鎖內部,調用了condition.await(timeout)之後且超時了,這時如果線程正好立即獲得了鎖(也就是沒有進入同步隊列),線程處於RUNNABLE狀態;

(19)對於重入鎖內部,調用了condition.await()之後且被signal()了,如果線程無法獲得鎖(也就是進入了同步隊列),線程處於WAITING狀態;

(20)對於重入鎖內部,調用了condition.await(timeout)之後且被signal()了或者超時了,如果線程無法獲得鎖(也就是進入了同步隊列),線程處於WAITING狀態;

(21)對於重入鎖,如果內部調用了condition.await()之後且被signal()之後依然無法獲取鎖的,其實經歷了兩次WAITING狀態的切換,一次是在等待隊列,一次是在同步隊列;

(22)對於重入鎖,如果內部調用了condition.await(timeout)之後且被signal()或超時了的,狀態會有一個從TIMED_WAITING切換到WAITING的過程,也就是從等待隊列進入到同步隊列;

為了便於理解,彤哥這裡每一條都分的比較細,麻煩耐心看完。

測試用例

看完上面的部分,你肯定想知道怎麼去驗證,下麵彤哥就說說驗證的方法,先給出測試用例。

public class ThreadLifeTest {
    public static void main(String[] args) throws IOException {
        Object object = new Object();
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(()->{
            synchronized (object) {
                try {
                    System.out.println("thread1 waiting");
                    object.wait();
//                    object.wait(5000);
                    System.out.println("thread1 after waiting");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread1").start();

        new Thread(()->{
            synchronized (object) {
                try {
                    System.out.println("thread2 notify");
                    // 打開或關閉這段註釋,觀察Thread1的狀態
//                    object.notify();【本文由公從號“彤哥讀源碼”原創】
                    // notify之後當前線程並不會釋放鎖,只是被notify的線程從等待隊列進入同步隊列
                    // sleep也不會釋放鎖
                    Thread.sleep(10000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread2").start();

        new Thread(()->{
            lock.lock();
            System.out.println("thread3 waiting");
            try {
                condition.await();
//                condition.await(200, (TimeUnit).SECONDS);
                System.out.println("thread3 after waiting");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread3").start();

        new Thread(()->{
            lock.lock();
            System.out.println("thread4");
            // 打開或關閉這段註釋,觀察Thread3的狀態
//            condition.signal();【本文由公從號“彤哥讀源碼”原創】
            // signal之後當前線程並不會釋放鎖,只是被signal的線程從等待隊列進入同步隊列
            // sleep也不會釋放鎖
            try {
                Thread.sleep(1000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread4").start();

    }
}

打開或關閉上面註釋部分的代碼,使用IDEA的RUN模式運行代碼,然後點擊左邊的一個攝像頭按鈕(jstack),查看各線程的狀態。

註:不要使用DEBUG模式,DEBUG模式全都變成WAITING狀態了,很神奇。

thread_life

彩蛋

其實,本來這篇是準備寫線程池的生命周期的,奈何線程的生命周期寫了太多,等下一篇我們再來一起學習線程池的生命周期吧。


歡迎關註我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

qrcode


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、繼承帶來的擴展和復用問題 繼承作為面向對象的三大要素(封裝、繼承、多態)之一為什麼會帶來問題,問題如何解決然後形成一種設計模式,head frist設計模式書中以鴨子作為例子講解什麼情況下繼承的方式會帶來問題。首先有各種各樣的鴨子,那麼自然想到各種鴨子繼承自一個父類:父類為Duck,現有綠頭鴨G ...
  • 場景 在使用SVN進行版本管理時,有時一些自動生成的文件比如證書等,在每臺電腦上都會不同,如果將其提交,則會衝突。 怎樣將指定的文件或者指定文件尾碼的文件忽略提交。 註: 博客主頁: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號霸道的程式猿 獲取編程 ...
  • 第一天的工作:找到數據源,數據下載,數據處理。 數據源:"http://webhelp.esri.com/arcgisserver/9.3/java/geodatabases/definition_frame.htm"。 數據下載:右擊網頁另存為。 數據處理:bs4 + 對比觀察 + chrome檢 ...
  • python創建虛擬環境 python安裝第三方庫大都是通過pip命令安裝,這個命令確實是很簡便的,而對於每個獨立的項目來說,需要用到的庫或許會不同,如果刪除或更新原來已有的版本庫,或許會導致其他的項目出錯,這時候,虛擬環境就能夠起作用了。哈哈~~~ 所以呢,通過python創建虛擬環境,可以分隔開 ...
  • 一、 this什麼時候是不能省略的,我們舉個例子來說明 this用來區分局部變數和實例變數的時候,是不能省略的。 二、我們對於構造方法的再次練習,註意我們的註釋,這是這個聯繫的重要總結。 我們看一下這裡面的兩個構造函數,一個是可以傳入參數的,一個是不傳入參數,我們不傳入參數的有個固定的格式:this ...
  • 元組格式 (元素1, 元素2, 元素3, ...) 元素能被訪問,但不能被修改 列表嵌套 列表名[][] print(id(變數名)) 獲取變數的地址值 列表,元組有序集合,字典無序集合 字典 python中唯一的映射類型 格式 字典名 = {'鍵',‘值’,...} 或 字典名 = dict(‘鍵 ...
  • 2019-10-18-21:35:36 面向對象 學面向對象前要先知道什麼叫面向過程,瞭解了什麼叫面向過程才容易理解面向對象 面向過程(強調步驟) 概念:當需要實現一個功能的時候,每一個具體的步驟都要親歷親為,詳細處理每一個細節的過程稱為面向過程 如:把衣服脫下來==>找一個盆==>放點洗衣粉==> ...
  • 在處理模板時,一般情況都是使用變數表達式 ${...} 來顯示變數,還可以使用選定對象表達式 *{...},它也稱為星號表達式。 如果在模板中先選定了對象,則需要使用星號表達式。Thymeleaf的內置對象#object效果等同於星號表達式。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...