線程中斷 interrupt 和 LockSupport

来源:https://www.cnblogs.com/starry-skys/archive/2020/03/01/12392753.html
-Advertisement-
Play Games

本文章將要介紹的內容有以下幾點,讀者朋友也可先自行思考一下相關問題: 1. 線程中斷 interrupt 方法怎麼理解,意思就是線程中斷了嗎?那當前線程還能繼續執行嗎? 2. 判斷線程是否中斷的方法有幾個,它們之間有什麼區別? 3. LockSupport的 park/unpark 和 wait/n ...


本文章將要介紹的內容有以下幾點,讀者朋友也可先自行思考一下相關問題:

  1. 線程中斷 interrupt 方法怎麼理解,意思就是線程中斷了嗎?那當前線程還能繼續執行嗎?
  2. 判斷線程是否中斷的方法有幾個,它們之間有什麼區別?
  3. LockSupport的 park/unpark 和 wait/notify 有什麼區別?
  4. sleep 方法是怎麼響應中斷的?
  5. park 方法又是怎麼響應中斷的?

線程中斷相關方法

線程中和中斷相關的方法有三個,分別介紹如下:

1) interrupt

我們一般都說這個方法是用來中斷線程的,那麼這個中斷應該怎麼理解呢? 就是說把當前正在執行的線程中斷掉,不讓它繼續往下執行嗎?

其實,不然。 此處,說的中斷僅僅是給線程設置一個中斷的標識(設置為true),線程還是會繼續往下執行的。而線程怎麼停止,則需要由我們自己去處理。 一會兒會用代碼來說明這個。

2) isInterrupted

判斷當前線程的中斷狀態,即判斷線程的中斷標識是true還是false。 註意,這個方法不會對線程原本的中斷狀態產生任何影響。

3) interrupted

也是判斷線程的中斷狀態的。但是,需要註意的是,這個方法和 isInterrupted 有很大的不同。我們看下它們的源碼:

public boolean isInterrupted() {  
    return isInterrupted(false);  
}

public static boolean interrupted() {  
    return currentThread().isInterrupted(true);  
}
//調用同一個方法,只是傳參不同
private native boolean isInterrupted(boolean ClearInterrupted);

首先 isInterrupted 方法是線程對象的方法,而 interrupted 是Thread類的靜態方法。

image

其次,它們都調用了同一個本地方法 isInterrupted,不同的只是傳參的值,這個參數代表的是,是否要把線程的中斷狀態清除(清除即不論之前的中斷狀態是什麼值,最終都會設置為false)。

因此,interrupted 靜態方法會把原本線程的中斷狀態清除,而 isInterrupted 則不會。所以,如果你調用兩次 interrupted 方法,第二次就一定會返回false,除非中間又被中斷了一次。

下麵證明一下 interrupt 方法只是設置一個中斷狀態,而不是使當前線程中斷運行:

public class TestFlag {
    static volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("線程中斷標誌:"+Thread.currentThread().isInterrupted());
                while (flag){

                }
                System.out.println("標誌flag為:" + flag);
                System.out.println("線程中斷標誌:"+Thread.currentThread().isInterrupted());
                System.out.println("我還在繼續執行");
            }
        });

        t.start();
        Thread.sleep(100);
        flag = false;
        t.interrupt();
    }
}

運行結果:

線程中斷標誌:false
標誌flag為:false
線程中斷標誌:true
我還在繼續執行

當線程啟動,還沒調用中斷方法時,中斷狀態為false,然後調用中斷方法,並把flag設置為false。此時,run方法跳出while死迴圈。我們會發現線程的中斷狀態為true,但是線程還是會繼續往下執行,直到執行結束。

sleep 響應中斷

線程中常用的阻塞方法,如sleep,join和wait 都會響應中斷,然後拋出一個中斷異常 InterruptedException。但是,註意此時,線程的中斷狀態會被清除。所以,當我們捕獲到中斷異常之後,應該保留中斷信息,以便讓上層代碼知道當前線程中斷了。通常有兩種方法可以做到。

一種是,捕獲異常之後,再重新拋出異常,讓上層代碼知道。另一種是,在捕獲異常時,通過 interrupt 方法把中斷狀態重新設置為true。

下麵,就以sleep方法為例,捕獲中斷異常,然後重新設置中斷狀態:

public class TestInterrupt {
    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(new Runnable() {
            private int count = 0;
            @Override
            public void run() {
                try {
                    count = new Random().nextInt(1000);
                    count = count * count;
                    System.out.println("count:"+count);
                    Thread.sleep(5000);
                } catch (Exception e) {
                    System.out.println(Thread.currentThread().getName()+"線程第一次中斷標誌:"+Thread.currentThread().isInterrupted());
                    //重新把線程中斷狀態設置為true,以便上層代碼判斷
                    Thread.currentThread().interrupt();
                    System.out.println(Thread.currentThread().getName()+"線程第二次中斷標誌:"+Thread.currentThread().isInterrupted());
                }
            }
        });

        t.start();

        Thread.sleep(100);
        t.interrupt();
    }
}

結果:

count:208849
Thread-0線程第一次中斷標誌:false
Thread-0線程第二次中斷標誌:true

LockSupport方法介紹

LockSupport 方法中重要的兩個方法就是park 和 unpark 。

park和interrupt中斷

park方法可以阻塞當前線程,如果調用unpark方法或者中斷當前線程,則會從park方法中返回。

park方法對中斷方法的響應和 sleep 有一些不太一樣。它不會拋出中斷異常,而是從park方法直接返回,不影響線程的繼續執行。我們看下代碼:

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new ParkThread());
        t.start();
        Thread.sleep(100); //①
        System.out.println(Thread.currentThread().getName()+"開始喚醒阻塞線程");
        t.interrupt();
        System.out.println(Thread.currentThread().getName()+"結束喚醒");

    }
}

class ParkThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"開始阻塞");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName()+"第一次結束阻塞");
        LockSupport.park();
        System.out.println("第二次結束阻塞");
    }
}

列印結果如下:

Thread-0開始阻塞
main開始喚醒阻塞線程
main結束喚醒
Thread-0第一次結束阻塞
第二次結束阻塞

當調用interrupt方法時,會把中斷狀態設置為true,然後park方法會去判斷中斷狀態,如果為true,就直接返回,然後往下繼續執行,並不會拋出異常。註意,這裡並不會清除中斷標誌。

unpark

unpark會喚醒被park的指定線程。但是,這裡要說明的是,unpark 並不是簡單的直接去喚醒被park的線程。看下JDK的解釋:

unpark只是給當前線程設置一個許可證。如果當前線程已經被阻塞了(即調用了park),則會轉為不阻塞的狀態。如若不然,下次調用park方法的時候也會保證不阻塞。這句話的意思,其實是指,park和unpark的調用順序無所謂,只要unpark設置了這個許可證,park方法就可以在任意時刻消費許可證,從而不會阻塞方法。

還需要註意的是,許可證最多只有一個,也就是說,就算unpark方法調用多次,也不會增加許可證。 我們可以通過代碼驗證,只需要把上邊代碼修改一行即可:

//LockSupportTest類
//原代碼
t.interrupt();
//修改為
LockSupport.unpark(t);
LockSupport.unpark(t);

就會發現,只有第一次阻塞會被喚醒,但是第二次依然會繼續阻塞。結果如下:

Thread-0開始阻塞
main開始喚醒阻塞線程
main結束喚醒
Thread-0第一次結束阻塞

另外,在此基礎上,把主線程的sleep方法去掉(代碼中①處),讓主線程先運行,也就是有可能先調用unpark方法,然後子線程才開始調用park方法阻塞。我們會發現,出現以下結果,證明瞭上邊我說的park方法和unpark不分先後順序,park方法可以隨時消費許可證。

main開始喚醒阻塞線程
main結束喚醒
Thread-0開始阻塞
Thread-0第一次結束阻塞

park/unpark和 wait/notify區別

瞭解了 park/unpark的用法之後,想必你也能分析出來它們和 wait、notify有什麼不同之處了。

1) wait和notify方法必須和同步鎖 synchronized一塊兒使用。而park/unpark使用就比較靈活了,沒有這個限制,可以在任何地方使用。

2) park/unpark 使用時沒有先後順序,都可以使線程不阻塞(前面代碼已驗證)。而wait必須在notify前先使用,如果先notify,再wait,則線程會一直等待。

3) notify只能隨機釋放一個線程,並不能指定某個特定線程,notifyAll是釋放鎖對象中的所有線程。而unpark方法可以喚醒指定的線程。

4) 調用wait方法會使當前線程釋放鎖資源,但使用的前提是必須已經獲得了鎖。 而park不會釋放鎖資源。(以下代碼驗證)

public class LockSyncTest {
    private static Object lock = new Object();
    //保存調用park的線程,以便後續喚醒
    private static Thread parkedThread;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{
             synchronized (lock){
                 System.out.println("unpark前");
                 LockSupport.unpark(parkedThread);
                 System.out.println("unpark後");
             }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //和t1線程用同一把鎖時,park不會釋放鎖資源,若換成this鎖,則會釋放鎖
                synchronized (lock){
                    System.out.println("park前");
                    parkedThread = Thread.currentThread();
                    LockSupport.park();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("park後");
                }
            }
        });

        t2.start();
        Thread.sleep(100);
        t1.start();

    }
}
//列印結果
//park前

以上代碼,會一直卡在t2線程,因為park不會釋放鎖,因此t1也無法執行。

如果把t2的鎖換成this鎖,即只要和t1不是同一把鎖,則t1就會正常執行,然後把t2線程喚醒。列印結果如下:

park前
unpark前
unpark後
park後

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

-Advertisement-
Play Games
更多相關文章
  • 記錄怎麼使用text-align與vertical-align屬性設置元素在容器中垂直居中對齊。text-align與vertical-align雖然都是設置元素內部對齊方式的,但兩者的用法還是有略微不同的。在討論這兩者的用法之前,我們首先需要瞭解元素的分類。 塊元素:獨占一行、可設寬高、標準盒模型 ...
  • 圖解Java設計模式之單例設計模式 設計模式介紹 設計模式類型 單例設計模式介紹 餓漢式(靜態常量) 餓漢式(靜態代碼塊) 懶漢式(線程不安全) 懶漢式(線程安全,同步方法) 懶漢式(線程安全,同步代碼塊) 雙重檢查 靜態內部類 枚舉 單例模式註意事項和細節說明 設計模式介紹 1)設計模式是程式員在 ...
  • @RestController RestController是@Controller和@ResponseBody的合併 @EnableAutoConfigurationSpringBoot建議只有一個帶有該註解的類SpringBoot會自動根據jar包的依賴來自動配置項目當項目下麵有HSQLDB的依 ...
  • 前言 去年供職於一家電商公司,被分配到做商城營銷體系的開發設計。本文章記錄了我這個小菜鳥在營銷設計中遇到的坑坑窪窪,以及在重構中的思路。 線性思維,線性設計 首先我們列一列營銷組件有哪些 。 滿減 折扣 秒殺 組合購 換購 優惠券 新人有禮 邀請有禮 簽到 紅包裂變 ... 其次是要知道營銷在 正向 ...
  • 一、Map常用方法簡介 package com.bjpowernode.java_learning; ​ import java.util.HashMap; import java.util.*; ​ public class D91_1_MapCommonMethod { public stati ...
  • 模型評價是指對於已經建立的一個或多個模型,根據其模型的類別,使用不同的指標評價其性能優劣的過程。常用的聚類模型評價指標有ARI評價法(蘭德繫數)、AMI評價法(互信息)、V-measure評分、FMI評價法和輪廓繫數等。常用的分類模型評價指標有準確率(Accuracy)、精確率(Precision) ...
  • 員工管理系統 因為學業要求,需要完成一個過關檢測,但是因為檢測之前沒有做好準備,且想到之前用mysql+jdbc+Struts2+bootstrap做成了一個ATM系統(主要有對數據的增刪改查操作),應對這次的檢測應該不成問題,但是萬萬沒想到,過關檢測重在“檢測”,需要在規定的時間內完成一個系統,且 ...
  • Python 程式能用很多方式處理日期和時間,轉換日期格式是一個常見的功能。Python 提供了 time ,datatime, calendar 等模塊可以用於格式化日期和時間。時間間隔是以秒為單位的浮點小數。每個時間戳都以自從 1970 年 1 月 1 日午夜(歷元)經過了多長時間來表示。Pyt ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...