原來這才是 JDK 推薦的線程關閉方式,別再亂用了!

来源:https://www.cnblogs.com/javastack/archive/2023/12/04/17874961.html
-Advertisement-
Play Games

原文:juejin.cn/post/7291564831710445622 JDK線上程的Stop方法時明確不得強行銷毀一個線程,要優雅的退出線程。 何謂優雅退出線程,即業務將進行中請求正確被處理,取消待執行請求,執行資源回收,最終Thread Runable run 方法return 結束執行。 ...


原文:juejin.cn/post/7291564831710445622

JDK線上程的Stop方法時明確不得強行銷毀一個線程,要優雅的退出線程。

何謂優雅退出線程,即業務將進行中請求正確被處理,取消待執行請求,執行資源回收,最終Thread Runable run 方法return 結束執行。

首先問為什麼要退出一個線程,再提問如何退出一個線程

需要線程退出的常見場景

  1. 任務執行完成,或異常終止,任務認為無需再占用線程。
  2. 線程池根據當前任務執行情況,伸縮線程池。當任務執行較少時,退出空閑的線程。
  3. 服務或進程在關閉階段,例如滾動發佈時,需要退出線程、關閉線程池、關閉進程。
  4. 定時任務、周期任務需要終止執行時,需要退出當前線程。或者退出當前任務的執行。

總之既然能創建一個線程,就會有退出一個線程的能力。也會有退出線程的場景。

關閉一個線程的方式分為兩種類型:通知線程主動關閉和強行關閉銷毀線程。

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

優雅關閉Or強行關閉

標題 好處 壞處
優雅關閉(主動通知線程關閉) 能優雅退出線程,保證資源被釋放,保證處理中請求正確被處理完成 無法立即關閉線程,執行中的任務不響應關閉信號,拒絕關閉線程
強行關閉線程 可以立刻關閉線程 存在資源未釋放、處理中請求異常中斷,例如分散式鎖漏釋放。寫流程異常中斷,數據不一致,重試也無法恢復

實際上強行關閉一個線程,壞處很多,假如要釋放分散式鎖前,突然關閉線程,那麼這個分散式鎖就無法釋放。導致後續正常請求加鎖失敗被阻塞,影響用戶提單等。

強行關閉一個線程無異於給伺服器直接斷電。

其他語言和Java語言退出線程的方式

除了Java其他語言如何退出線程呢,實際上每一種實現方式都有。例如C++中可以通過ExitThread、TerminateThread強行終止線程執行。linux既提供了pthread_exit C語言系統調用強行關閉線程,也提供了pthread_cancel通知線程關閉等優雅退出方式。

Java 也分別提供優雅和強制兩種退出方式,但是目前jdk中明確極不推薦強制中斷線程,在Thread.stop()強制中斷線程的註釋中, JDK這樣解釋

Thread.stop() 這種方法本身就是不安全的,Stop一個線程會隨之解鎖這個線程所持有的監視器(可以理解為鎖),如果受這些監視器(鎖)保護的臨界對象處在不一致狀態,則其他線程可能會看到這些對象處於不一致狀態,那麼將導致未知的行為。對Thread.Stop()的調用應該被簡單的代碼代替,例如 修改一個變數,目標線程定期檢查這個變數,有序從run 方法return出來。如果目標線程在一個條件變數上wait,則其他線程應該使用interrupt方法中斷目標線程。

實際上關閉一個線程強行和通知是兩種理念,即是否應該相信線程任務的開發者優雅的、快速的主動退出線程,而不是被其他線程強制終止。在Java中,退出線程的方式只有一種推薦,即優雅退出,並且jdk也給了建議,通過修改變數,由目標線程定期檢查狀態。或者通過interrupt中斷方式通知目標線程。

下麵我們探討下如何優雅退出一個線程?

優雅退出線程

有哪些方式呢?

業務欄位標記

業務系統經常遇到終止一個任務的訴求,例如系統中存在定時任務,例如外賣券包在過期後,未使用的金額,自動給用戶退款。假設任務執行中,我需要重新制定任務的入參,需要先終止任務。如何做呢?大部分任務類代碼都會迴圈處理,例如掃描全表執行某個業務邏輯。一定存在迴圈處理的場景,可以在迴圈入口處判斷任務是否需要終止執行,這樣通過控制這個欄位,我們就可以終止任務執行。

具體實施時,可以通過配置中心控制某一個任務是否要終止。

while(config.isTaskEnable()){//從配置中心獲取任務是否要終止
    //迴圈執行業務邏輯。直到執行完成退出,或者被終止。
}

這種退出方式,是告知線程“你應該在合適時機退出”, 由線程自己選擇在合適的時機檢查該狀態。那麼開發者在設計任務代碼時,就要提前設計 合理的退出點,在退出點檢查是否需要退出。

Thread.interrupt()

JDK中提到瞭如果目標線程沒有處於運行態,而是處於阻塞狀態,自然無法檢查退出的狀態標記,如何通知這個線程退出呢?

JDK: 如果目標線程在一個條件變數上wait,則其他線程應該使用interrupt方法中斷目標線程。

interrupt的JDK註釋提到,

如果其他線程調用目標線程的interrupt方法,

  1. 恰好目標線程在調用. Object.wait(),object.join(),Object.sleep()等方法時,目標線程的中斷位標記被清除,同時目標線程會立即從sleep、wait等調用中恢復,並且被拋出InterruptException。
  2. 如果目標線程在IO操作中被阻塞,例如io.channels.InterruptibleChannel,Channel將被關閉,線程的中斷位被設置,同時目標線程收到java.nio.channels.ClosedByInterruptException。
  3. 如果目標線程被阻塞在java.nio.channels.Selector,線程中斷狀態被設置,然後目標線程立即從select中返回非零值。
  4. 如果其他條件都不成立,該線程中斷位會被設置。

線程中斷位標記了當前線程是否處於被中斷狀態,並且提供了Thread.isInterrupted方法查看當前是否處於中斷位?那為什麼目標線程阻塞在Object.wait(),Sleep()方法時,拋出了interruptException,會取消標記呢?實際上interrupt操作執行兩件事,1)設置中斷位標記 2)通過unpark喚醒目標線程。(park和unpark分別可以阻塞線程和喚醒線程)

推薦一個非常好的博客 通過JVM源碼分析 interrupt和sleep的實現原理。

然而目標線程醒來時會檢查當前是否處於中斷位,如果是sleep或者wait操作。如果處於中斷位則取消中斷位,拋出異常。取消中段位的原因應該是一種規範,即拋出中斷異常,即通知了線程中斷,無需再用中段位標記。

其他場景2、場景3 在被喚醒後,分別執行對應的中斷響應策略。

interrupt中斷邏輯是確定的,業務線程要考慮自己是否調用了sleep、wait或者io、selector等操作,根據不同的場景,選擇自己合適的中斷響應策略。

那麼推薦業務線程如何響應中斷呢?

推薦的中斷響應策略

立即響應中斷
  1. 目標線程的任務在InterruptedException異常處理中,要主動回收資源,列印日誌,退出任務執行。
  2. 目標線程如果沒有阻塞操作,例如sleep、wait。可以通過 Thread.isInterrupted(),查看當前中斷位狀態,如果被中斷了,則採取以上第一步操作。
忽略中斷,交給上一層處理

所謂上一層,可以理解為是調用堆棧的上一層,例如本層代碼不負責處理中斷這個場景,那麼Interrupt異常被拋出後,可以選擇如何方案

  1. 拋出InterruptedException給上層,由上層代碼處理。
  2. 調用Thread.interrupt()。重新設置中斷位標記(自己中斷自己)。由上游代碼在本層方法返回後,檢查中斷位標記,進行中斷處理。

當然最推薦的方式還是拋出InterruptedException,讓上游感知到下游調用鏈中存在阻塞,讓上游對中斷異常進行處理。

千萬不要吞掉中斷

什麼是吞掉中斷?例如當sleep拋出InterruptedException後,忽略異常,不執行任何操作,繼續執行業務邏輯。

for (int i = 0; i < cnt; i++) {
   try {
      //執行業務邏輯
      Thread.sleep(10000);
   } catch (InterruptedException e) {

      System.out.println("被中斷");
   }
   System.out.println("子線程執行中");
}

如果這樣處理,中斷異常被忽略,中斷標記位也被忽略。即便上游方法對中斷有處理策略,也無法感知到中斷。例如上游調用可能會判斷

while(true){
    callChildMethod();//調用下游方法,但是下游吞掉了中斷
    if (Thread.currentThread().isInterrupted()) {
       //回收資源,退出線程
    }
}

有人會問,既然上層都能知道處理中斷,為什麼下層方法開發者會不記得拋出中斷或重置中斷位呢?

因為上下兩層,很可能不是一個開發者。例如上層是通用的框架代碼,定義了任務的指定邏輯,提供了擴展點方法,下游只需要實現擴展方法即可。但是另一個開發者在實現擴展點方法時,吞掉了中斷異常,導致本來框架層已經處理好中斷了,但還是無法響應中斷。

所以中斷的響應是需要上下層,每一層代碼邏輯都需要考慮的事情。就算框架層處理好中斷異常處理,業務邏輯層也要關註中斷處理。

最後提醒一下,Thread.interrupted方法會返回當前中斷標記,並且取消中斷位。如果只查詢中斷位,不想清理,可以使用 Thread.isInterrupted()。

總結

  1. 不推薦強制銷毀線程,會導致資源無法被釋放,進行中請求無法正常處理完,導致業務數據處於不可知的狀態。
  2. Java推薦優雅退出線程。
  3. 業務層可以使用欄位標記,定期檢查是否需要退出任務。
  4. Thread.interrupt中斷目標線程、isInterrupted查詢中斷位標記。
  5. 使用Thread.interrupt處理中斷也可以優雅退出,但需要上下層堆棧都要關註中斷,不得吞掉中斷。

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


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

-Advertisement-
Play Games
更多相關文章
  • 我們有時候也會看到一些博客看到或者聽到一些同事在說:這個業務有什麼難的,不就是CRUD麽?在軟體生命周期初期,我們通過CRUD這種方式我們可以快速的實現業務規則,交付項目,但隨著業務逐漸複雜,通過CRUD這種粗暴方式不可避免地會淹沒業務核心規則,產生很多祖傳(屎山)代碼,系統交接的時候我們經常會聽到... ...
  • 結構化查詢語言,簡稱SQL,它是與關係資料庫管理系統通信的黃金標準語言。今天就來一起快速認識一下什麼是SQL,您可以通過以下的文字內容學習,也可以通過文末的視頻學習,希望本文對您有所幫助。 您可能聽說過 MySQL、Postgres、Microsoft SQL Server 和 Oracle 等數據 ...
  • 一.Maven的介紹即相關概念 Maven是一款構建和管理Java項目的工具,它將項目開發和管理過程抽象成一個項目對象模型(POM),提供了一種統一的項目結構。 Maven官網 1.為什麼使用Maven/Maven的作用 (1)多模塊支持:當項目非常龐大的時候,就不適合使用package來劃分模塊, ...
  • 題目 給你一個非負整數數組 nums ,你最初位於數組的 第一個下標 。數組中的每個元素代表你在該位置可以跳躍的最大長度。 判斷你是否能夠到達最後一個下標,如果可以,返回 true ;否則,返回 false 。 示例 1: 輸入:nums = [2,3,1,1,4] 輸出:true 解釋:可以先跳 ...
  • 寫在前面 昨晚應該是睡的最好一天吧,最近一個月睡眠好差,睡不著不說,而且半夜總醒,搞的我第二天就會超沒精神。 昨天下午去姐姐家,我剛進屋,小外甥直接就問我說: 老舅,你都很長時間沒來啦,**(前女友)哪去了, 我們都好久沒出溜溜了! 我頓了下說,她不喜歡我們了,等以後天暖和,我們再去溜溜。 才發現, ...
  • JSON(JavaScript Object Notation)是一種輕量級的數據交換格式,它以易於閱讀和編寫的文本形式表示數據。JSON 是一種獨立於編程語言的數據格式,因此在不同的編程語言中都有對應的解析器和生成器。JSON 格式的設計目標是易於理解、支持複雜數據結構和具有良好的可擴展性。 ...
  • 常用操作文件目錄的函數 1. CreateDirectory 創建文件夾 原型: BOOL CreateDirectory( LPCTSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); 參數說明: lpPathName 要創建的 ...
  • developer-roadmap —— 提供最全的開發者技術路線指南。前端開發、後端開發、全棧開發、DevOps、Android 開發、AI、大數據、游戲開發等方向都有詳盡的學習路線思維導圖。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...