1. 程式人生 > >Java 執行緒阻塞、中斷及優雅退出

Java 執行緒阻塞、中斷及優雅退出

執行緒阻塞

一個執行緒進入阻塞狀態的原因可能如下(已排除Deprecated方法):

sleep()

sleep()使當前執行緒進入停滯狀態(阻塞當前執行緒),讓出CUP的使用、目的是不讓當前執行緒獨自霸佔該程序所獲的CPU資源,以留一定時間給其他執行緒執行的機會;

當在一個Synchronized塊中呼叫Sleep()方法是,執行緒雖然休眠了,但是物件鎖並沒有被釋放,其他執行緒無法訪問這個物件(即使睡著也持有物件鎖)。

wait()

呼叫wait()/1.5中的condition.await()使執行緒掛起,直到執行緒獲取notify()/notifyAll()訊息,(或者在Java SE5中java.util.concurrent類庫中等價的signal()/signalAll()

訊息),執行緒才會進入就緒狀態;

wait()呼叫會釋放當前物件鎖(monitor),這樣其他執行緒可以繼續進入物件的同步方法。參見上一篇文章執行緒間協作——wait & notify & notifyAll

另外,呼叫join()也會導致執行緒阻塞,因為原始碼中join()就是通過wait()實現的;

等待I/O;

class Demo3 implements Runnable throws InterruptedException{
     private InputStream in;
     public void run(){
          in.read();
     }
}

無法持有鎖進入同步程式碼

進入同步程式碼前無法獲取鎖,比如試圖呼叫synchronized方法,或者顯示鎖物件的上鎖行為ReentrantLock.lock(),而對應鎖已被其他執行緒獲取的情況下都將導致執行緒進入阻塞狀態;

注意:yield()並不會導致執行緒轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致執行緒從執行狀態轉到可執行狀態,但有可能沒有效果。

執行緒中斷

執行緒中斷可以線上程內部設定一箇中斷標識,同時讓處於(可中斷)阻塞的執行緒丟擲InterruptedException中斷異常,使執行緒跳出阻塞狀態。相比其他語言,Java執行緒中斷比較特殊,經常會引起開發人員的誤解。因為中斷聽起來高深複雜,實質原理上非常簡單。

中斷原理

Java中斷機制是一種協作機制,也就是說通過中斷並不能直接終止另一個執行緒,而需要被中斷的執行緒自己處理中斷。這好比是家裡的父母叮囑在外的子女要注意身體,但子女是否注意身體,怎麼注意身體則完全取決於自己。

Java中斷模型也是這麼簡單,每個執行緒物件裡都有一個boolean型別的標識(不一定就要是Thread類的欄位,實際上也的確不是,這幾個方法最終都是通過native方法來完成的),代表著是否有中斷請求(該請求可以來自所有執行緒,包括被中斷的執行緒本身)。例如,當執行緒t1想中斷執行緒t2,只需要線上程t1中將執行緒t2物件的中斷標識置為true,然後執行緒2可以選擇在合適的時候處理該中斷請求,甚至可以不理會該請求,就像這個執行緒沒有被中斷一樣。

中斷相關的方法

方法 解釋
public static boolean interrupted() 測試當前執行緒是否已經中斷。執行緒的中斷狀態 由該方法清除。換句話說,如果連續兩次呼叫該方法,則第二次呼叫將返回 false(在第一次呼叫已清除了其中斷狀態之後,且第二次呼叫檢驗完中斷狀態前,當前執行緒再次中斷的情況除外)。
public boolean isInterrupted() 測試執行緒是否已經中斷。執行緒的中斷狀態不受該方法的影響。
public void interrupt() 中斷執行緒,設定中斷標識為為true。

其中,interrupt方法是唯一能將中斷狀態設定為true的方法。靜態方法interrupted會將當前執行緒的中斷狀態清除,但這個方法的命名極不直觀,很容易造成誤解,需要特別注意。

此外,類庫中的有些類的方法也可能會呼叫中斷,如FutureTask中的cancel方法,如果傳入的引數為true,它將會在正在執行非同步任務的執行緒上呼叫interrupt方法,如果正在執行的非同步任務中的程式碼沒有對中斷做出響應,那麼cancel方法中的引數將不會起到什麼效果;

ExecutorService exec = Executors.newCachedThreadPool();
Futrue<?> f = exec.submit(new TaskThread());
f.interrupt();

又如ThreadPoolExecutor中的shutdownNow方法會遍歷執行緒池中的工作執行緒並呼叫執行緒的interrupt方法來中斷執行緒,所以如果工作執行緒中正在執行的任務沒有對中斷做出響應,任務將一直執行直到正常結束。

ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
     exec.execute(new TaskThread())
exec.shutdownNow();

中斷的處理

中斷一個執行緒只是為了引起該執行緒的注意,被中斷執行緒可以決定如何應對中斷。某些執行緒非常重要,以至於它們應該不理會中斷,而是在處理完丟擲的異常之後繼續執行,但是更普遍的情況是,一個執行緒將把中斷看作一個終止請求,這種執行緒的run方法遵循如下形式:

public void run() {
    try {
        ...
        /*
         * 不管迴圈裡是否呼叫過執行緒阻塞的方法如sleep、join、wait,這裡還是需要加上
         * !Thread.currentThread().isInterrupted()條件,雖然丟擲異常後退出了迴圈,顯
         * 得用阻塞的情況下是多餘的,但如果呼叫了阻塞方法但沒有阻塞時,這樣會更安全、更及時。
         */
        while (!Thread.currentThread().isInterrupted()&& more work to do) {
            do more work 
        }
    } catch (InterruptedException e) {
        //執行緒在wait或sleep期間被中斷了
    } finally {
        //執行緒結束前做一些清理工作
    }
}

上面是while迴圈在try塊裡,如果try在while迴圈裡時,因該在catch塊裡重新設定一下中斷標示,因為丟擲InterruptedException異常後,中斷標示位會自動清除,此時應該這樣:

public void run() {
    while (!Thread.currentThread().isInterrupted()&& more work to do) {
        try {
            ...
            sleep(delay);
            //wait(delay);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();   //重新設定中斷標示
        }
    }
}

可中斷阻塞

對於處於sleep,join等操作的執行緒,如果被呼叫interrupt()後,會丟擲InterruptedException,然後執行緒的中斷標誌位會由true重置為false,因為執行緒為了處理異常已經重新處於就緒狀態。

不可中斷的操作,包括進入synchronized段以及Lock.lock(),inputSteam.read()等,呼叫interrupt()對於這幾個問題無效,因為它們都不丟擲中斷異常。如果拿不到資源,它們會無限期阻塞下去。

對於Lock.lock(),可以改用Lock.lockInterruptibly(),可被中斷的加鎖操作,它可以丟擲中斷異常。等同於等待時間無限長的Lock.tryLock(long time, TimeUnit unit)。

對於inputStream等資源,有些(實現了interruptibleChannel介面)可以通過close()方法將資源關閉,對應的阻塞也會被放開。

但是,你可能正使用Java1.0之前就存在的傳統的I/O,Thread.interrupt()將不起作用,因為執行緒將不會退出被阻塞狀態。

很幸運,Java平臺為這種情形提供了一項解決方案,即呼叫阻塞該執行緒的套接字的close()方法。在這種情形下,如果執行緒被I/O操作阻塞,當呼叫該套接字的close方法時,該執行緒在呼叫accept地方法將接收到一個SocketException(SocketException為IOException的子異常)異常,這與使用interrupt()方法引起一個InterruptedException異常被丟擲非常相似。

java.nio類庫提供了更加人性化的I/O中斷,被阻塞的nio通道會自動地響應中斷,不需要關閉底層資源;

執行緒優雅退出

一般情況下,執行緒退出可以使用while迴圈判斷共享變數條件的方式,當執行緒內有阻塞操作時,可能導致執行緒無法執行到條件判斷的地方而導致一直阻塞下去,這個時候就需要中斷來幫助執行緒脫離阻塞。因此比較優雅的退出執行緒方式是結合共享變數和中斷。

thread = new Thread(new Runnable() {
    @Override
    public void run() {
        /*
         * 在這裡為一個迴圈,條件是判斷執行緒的中斷標誌位是否中斷
         */
        while (flag&&(!Thread.currentThread().isInterrupted())) {
            try {
                Log.i("tag","執行緒執行中"+Thread.currentThread().getId());
                // 每執行一次暫停40毫秒
                //當sleep方法丟擲InterruptedException  中斷狀態也會被清掉
                Thread.sleep(40);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //如果丟擲異常則再次設定中斷請求
                Thread.currentThread().interrupt();
            }
        }
    }
});
thread.start();

參考

相關推薦

Java 執行阻塞中斷優雅退出

執行緒阻塞 一個執行緒進入阻塞狀態的原因可能如下(已排除Deprecated方法): sleep() sleep()使當前執行緒進入停滯狀態(阻塞當前執行緒),讓出CUP的使用、目的是不讓當前執行緒獨自霸佔該程序所獲的CPU資源,以留一定時間給其他執行

java執行等待/通知機制中斷

一、等待/通知機制  線上程互動中經常需要對其進行一些控制,希望人為地能夠讓執行緒按理想路線發展,在滿足某條件時進行執行操作而發生變化時,停止等待。 1、 使用sleep  在 if ( ) { } else { }  中使用sleep 對執行緒進行停止等待一段時間。  

Java執行狀態執行停止執行阻塞

執行緒狀態(五種狀態) Java 執行緒的生命週期包括建立,就緒,執行,阻塞,死亡5 個狀態。一個 Java 執行緒總是處於這 5 個生命週期狀態之一,並在一定條件下可以在不同狀態之間進行轉換 。

執行阻塞中斷(sleepwaitio鎖)四種恢復方式

1、執行緒阻塞 一個執行緒進入阻塞狀態可能的原因: ①通過呼叫sleep(millseconds)使任務進入休眠狀態; class Demo1 implements Runnable thr

java執行阻塞喚醒interrupt測試

執行緒阻塞可以採用Object.wait()、Object.notify()來控制執行緒的阻塞喚醒。 另一種方式是呼叫Unsafe.park()、Unsafe.unpark()。 在主動呼叫執行緒interrupt方法之後,目標執行緒如果正在block狀態就會被喚醒,

jstack簡單使用,定位死迴圈執行阻塞死鎖等問題

當我們執行java程式時,發現程式不動,但又不知道是哪裡出問題時,可以使用JDK自帶的jstack工具去定位; 廢話不說,直接上例子吧,在window平臺上的; 一、死迴圈 package software.architect.OtherAnalyzer.main; public

【第22天】Java執行基礎併發集合

1 概述 2 生命週期(五狀態圖) 3 如何定義一個執行緒 4 控制執行緒執行的方法 1 概述        程式當中一條獨立的執行線索。Java中的主執行緒是一個J

執行執行引用volatile與synchronized的區別

執行緒池 corePoolSize 當前執行緒數=corePoolSize 阻塞,阻塞佇列滿時建立新執行緒,直至macPoolSize,再來任務時,執行reject()。 初始化執行緒池 newFixedThreadPool() 初始化指定大小,即使沒有

Java執行池詳解例項

前言 多執行緒的非同步執行方式,雖然能夠最大限度發揮多核計算機的計算能力,但是如果不加控制,反而會對系統造成負擔。執行緒本身也要佔用記憶體空間,大量的執行緒會佔用記憶體資源並且可能會導致Out of Memory。即便沒有這樣的情況,大量的執行緒回收也會給GC帶來很大的壓力

Java執行阻塞方法sleep()和wait()精煉詳解

一、前期基礎知識儲備 sleep()和wait()方法都是Java中造成執行緒阻塞的方法。感興趣的讀者可以參見筆者之前的文章《Java中什麼方法導致執行緒阻塞》,裡面詳細講述了為什麼Java要造成執行緒阻塞和Java中造成執行緒阻塞的幾種方法。 執行緒的生命週期 這

java執行池引數說明佇列拒絕策略

 java.util.concurrent.ThreadPoolExecutor,其構造方法1: public ThreadPoolExecutor(int corePoolSize, int maximumP

JVM調優(9)jstack定位死迴圈執行阻塞死鎖等問題

當我們執行java程式時,可能會出現死迴圈,IO阻塞,執行緒死鎖等問題,導致程式無法進行下去,但從程式碼上有無法確定問題出現的具體原因或者地方。可以使用JDK自帶的jstack工具去簡單定位; 死迴圈 程式如下: /** * @Author Ralph * 死迴圈定位

java 執行池詳解四種執行池用法介紹

java 執行緒池詳解      Executor框架是一種將執行緒的建立和執行分離的機制。它基於Executor和ExecutorService介面,及這兩個介面的實現類ThreadPoolExecutor展開,Executor有一個內部執行緒池,並提供了將任務傳遞到池中

java執行阻塞喚醒的四種方式

java在多執行緒情況下,經常會使用到執行緒的阻塞與喚醒,這裡就為大家簡單介紹一下以下幾種阻塞/喚醒方式與區別,不做詳細的介紹與程式碼分析 suspend與resume Java廢棄 sus

Java 執行同步死鎖

執行緒同步(保證執行緒安全:沒有鎖的執行緒只能等)獲取鎖 釋放鎖 執行慢 public class ThreadDemo { //買門票例子 public static void main(String[] args) { MyTicket mt= new MyTicket(); //建立任務 /MyT

java執行池ThreadPoolExecutor原理使用

其構造方法為public class ThreadPoolExecutor extends AbstractExecutorService{<span style="white-space:pre"> </span><div class="li

Java執行池原始碼解析高質量程式碼案例

引言 本文為Java高階程式設計中的一些知識總結,其中第一章對Jdk 1.7.0_25中的多執行緒架構中的執行緒池ThreadPoolExecutor原始碼進行架構原理介紹以及原始碼解析。第二章則分析了幾個違反Java高質量程式碼案例以及相應解決辦法。如有總結

Java 執行池詳解例項程式碼

這篇文章主要介紹了Java 執行緒池的相關資料,並符例項程式碼,幫助大家學習參考,需要的朋友可以參考下執行緒池的技術背景在面向物件程式設計中,建立和銷燬物件是很費時間的,因為建立一個物件要獲取記憶體資源或者其它更多資源。在Java中更是如此,虛擬機器將試圖跟蹤每一個物件,以便

執行實現啟動區分方式

①實現方式 1、通過繼承Thread類實現一個執行緒 2、通過實現Runnable介面實現一個執行緒 繼承擴充套件性不強,java總只支援單繼承,如果一個類繼承Thread就不能繼承其他的類了。 ②怎麼啟動? Thread thr

Java執行池詳解常用方法

前言 最近被問到了執行緒池的相關問題。於是準備開始寫一些多執行緒相關的文章。這篇將介紹一下執行緒池的基本使用。 Executors Executors是concurrent包下的一個類,為我們提供了建立執行緒池的簡便方法。 Executors可以建立我們常用的四種執行緒池: (1)newCachedThrea