成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Thread類源碼解讀(3)——線程中斷interrupt

fevin / 3624人閱讀

摘要:現(xiàn)在終止一個線程,基本上只能靠曲線救國式的中斷來實現(xiàn)。中斷機制的核心在于中斷狀態(tài)和異常中斷狀態(tài)設(shè)置一個中斷狀態(tài)清除一個中斷狀態(tài)方法同時會返回線程原來的中斷的狀態(tài)。中斷異常中斷異常一般是線程被中斷后,在一些類型的方法如中拋出。

前言

系列文章目錄

線程中斷是一個很重要的概念,通常,取消一個任務(wù)的執(zhí)行,最好的,同時也是最合理的方法,就是通過中斷。

本篇我們主要還是通過源碼分析來看看中斷的概念。

本文的源碼基于JDK1.8

Interrupt status & InterruptedException

java線程的中斷機制為我們提供了一個契機,使被中斷的線程能夠有機會從當(dāng)前的任務(wù)中跳脫出來。而中斷機制的最核心的兩個概念就是interrupt statusInterruptedException

java中對于中斷的大部分操作無外乎以下兩點:

設(shè)置或者清除中斷標(biāo)志位

拋出InterruptedException

interrupt status

在java中,每一個線程都有一個中斷標(biāo)志位,表征了當(dāng)前線程是否處于被中斷狀態(tài),我們可以把這個標(biāo)識位理解成一個boolean類型的變量,當(dāng)我們中斷一個線程時,將該標(biāo)識位設(shè)為true,當(dāng)我們清除中斷狀態(tài)時,將其設(shè)置為false, 其偽代碼如下:

(注意,本文的偽代碼部分是我個人所寫,并不權(quán)威,只是幫助我自己理解寫的)

// 注意,這是偽代碼!??!
// 注意,這是偽代碼?。。?// 注意,這是偽代碼?。?!
public class Thread implements Runnable {
    private boolean interruptFlag; // 中斷標(biāo)志位
    
    public boolean getInterruptFlag() {
        return this.interruptFlag;
    }
    
    public void setInterruptFlag(boolean flag) {
        this.interruptFlag = flag;
    }
}

然而,在Thread線程類里面,并沒有類似中斷標(biāo)志位的屬性,但是提供了獲取中斷標(biāo)志位的接口:

/**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);

這是一個native方法,同時也是一個private方法,該方法除了能夠返回當(dāng)前線程的中斷狀態(tài),還能根據(jù)ClearInterrupted參數(shù)來決定要不要重置中斷標(biāo)志位(reset操作相當(dāng)于上面的interruptFlag = false)。

Thread類提供了兩個public方法來使用該native方法:

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

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

其中isInterrupted調(diào)用了isInterrupted(false), ClearInterrupted參數(shù)為false, 說明它僅僅返回線程實例的中斷狀態(tài),但是不會對現(xiàn)有的中斷狀態(tài)做任何改變,偽代碼可以是:

// 注意,這是偽代碼!??!
// 注意,這是偽代碼?。?!
// 注意,這是偽代碼?。?!
public boolean isInterrupted() {
    return interruptFlag; //直接返回Thread實例的中斷狀態(tài)
}

interrupted是一個靜態(tài)方法,所以它可以由Thread類直接調(diào)用,自然就是作用于當(dāng)前正在執(zhí)行的線程,所以函數(shù)內(nèi)部使用了currentThread()方法,與isInterrupted()方法不同的是,它的ClearInterrupted參數(shù)為true,在返回線程中斷狀態(tài)的同時,重置了中斷標(biāo)識位,偽代碼可以是:

// 注意,這是偽代碼?。?!
// 注意,這是偽代碼?。?!
// 注意,這是偽代碼?。?!
public static boolean interrupted() {
    Thread current = Thread.currentThread(); // 獲取當(dāng)前正在執(zhí)行的線程
    boolean interruptFlag = current.getInterruptFlag(); // 獲取線程的中斷狀態(tài)
    current.setInterruptFlag(false); // 清除線程的中斷狀態(tài)
    return interruptFlag; //返回線程的中斷狀態(tài)
}

可見,isInterrupted()interrupted() 方法只涉及到中斷狀態(tài)的查詢,最多是多加一步重置中斷狀態(tài),并不牽涉到InterruptedException。

不過值得一提的是,在我們能使用到的public方法中,interrupted()是我們清除中斷的唯一方法。

InterruptedException

我們直接來看的源碼:

/**
 * Thrown when a thread is waiting, sleeping, or otherwise occupied,
 * and the thread is interrupted, either before or during the activity.
 * Occasionally a method may wish to test whether the current
 * thread has been interrupted, and if so, to immediately throw
 * this exception.  The following code can be used to achieve
 * this effect:
 * 
 *  if (Thread.interrupted())  // Clears interrupted status!
 *      throw new InterruptedException();
 * 
* * @author Frank Yellin * @see java.lang.Object#wait() * @see java.lang.Object#wait(long) * @see java.lang.Object#wait(long, int) * @see java.lang.Thread#sleep(long) * @see java.lang.Thread#interrupt() * @see java.lang.Thread#interrupted() * @since JDK1.0 */ public class InterruptedException extends Exception { private static final long serialVersionUID = 6700697376100628473L; /** * Constructs an InterruptedException with no detail message. */ public InterruptedException() { super(); } /** * Constructs an InterruptedException with the * specified detail message. * * @param s the detail message. */ public InterruptedException(String s) { super(s); } }

上面的注釋是說,在線程處于“waiting, sleeping”甚至是正在運行的過程中,如果被中斷了,就可以拋出該異常,我們先來回顧一下我們前面遇到過的拋出InterruptedException異常的例子:

(1) wait(long timeout)方法中的InterruptedException

/* 
 *
 * @param      timeout   the maximum time to wait in milliseconds.
 * @throws  IllegalArgumentException      if the value of timeout is
 *               negative.
 * @throws  IllegalMonitorStateException  if the current thread is not
 *               the owner of the object"s monitor.
 * @throws  InterruptedException if any thread interrupted the
 *             current thread before or while the current thread
 *             was waiting for a notification.  The interrupted
 *             status of the current thread is cleared when
 *             this exception is thrown.
 * @see        java.lang.Object#notify()
 * @see        java.lang.Object#notifyAll()
 */
public final native void wait(long timeout) throws InterruptedException;

該方法的注釋中提到,如果在有別的線程在當(dāng)前線程進入waiting狀態(tài)之前或者已經(jīng)進入waiting狀態(tài)之后中斷了當(dāng)前線程,該方法就會拋出InterruptedException,同時,異常拋出后,當(dāng)前線程的中斷狀態(tài)也會被清除。

(2) sleep(long millis)方法中的InterruptedException

/* @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          interrupted status of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;

與上面的wait方法一致,如果當(dāng)前線程被中斷了,sleep方法會拋出InterruptedException,并且清除中斷狀態(tài)。

如果有其他方法直接或間接的調(diào)用了這兩個方法,那他們自然也會在線程被中斷的時候拋出InterruptedException,并且清除中斷狀態(tài)。例如:

wait()

wait(long timeout, int nanos)

sleep(long millis, int nanos)

join()

join(long millis)

join(long millis, int nanos)

這里值得注意的是,雖然這些方法會拋出InterruptedException,但是并不會終止當(dāng)前線程的執(zhí)行,當(dāng)前線程可以選擇忽略這個異常。

也就是說,無論是設(shè)置interrupt status 還是拋出InterruptedException,它們都是給當(dāng)前線程的建議,當(dāng)前線程可以選擇采納或者不采納,它們并不會影響當(dāng)前線程的執(zhí)行。

至于在收到這些中斷的建議后,當(dāng)前線程要怎么處理,也完全取決于當(dāng)前線程。

interrupt

上面我們說了怎么檢查(以及清除)一個線程的中斷狀態(tài),提到當(dāng)一個線程被中斷后,有一些方法會拋出InterruptedException。

下面我們就來看看怎么中斷一個線程。

要中斷一個線程,只需調(diào)用該線程的interrupt方法,其源碼如下:

/**
 * Interrupts this thread.
 *
 * 

Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. * *

If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. * *

If this thread is blocked in an I/O operation upon an {@link * java.nio.channels.InterruptibleChannel InterruptibleChannel} * then the channel will be closed, the thread"s interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. * *

If this thread is blocked in a {@link java.nio.channels.Selector} * then the thread"s interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector"s {@link * java.nio.channels.Selector#wakeup wakeup} method were invoked. * *

If none of the previous conditions hold then this thread"s interrupt * status will be set.

* *

Interrupting a thread that is not alive need not have any effect. * * @throws SecurityException * if the current thread cannot modify this thread * * @revised 6.0 * @spec JSR-51 */ public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }

上面的注釋很長,我們一段一段來看:

/**
 * Interrupts this thread.
 *
 * 

Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. ... */

上面這段首先說明了這個函數(shù)的目的是中斷這個線程,這個this thread,當(dāng)然指的就是該方法所屬的線程對象所代表的線程。

接著說明了,一個線程總是被允許中斷自己,但是我們?nèi)绻胍谝粋€線程中中斷另一個線程的執(zhí)行,就需要先通過checkAccess()檢查權(quán)限。這有可能拋出SecurityException異常, 這段話用代碼體現(xiàn)為:

if (this != Thread.currentThread())
    checkAccess();

我們接著往下看:

/*
 ...
 * 

If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. ... */

上面這段是說,如果線程因為以下方法的調(diào)用而處于阻塞中,那么(調(diào)用了interrupt方法之后),線程的中斷標(biāo)志會被清除,并且收到一個InterruptedException:

Object的方法

wait()

wait(long)

wait(long, int)

Thread的方法

join()

join(long)

join(long, int)

sleep(long)

sleep(long, int)

關(guān)于這一點,我們上面在分析InterruptedException的時候已經(jīng)分析過了。

這里插一句,由于上面這些方法在拋出InterruptedException異常后,會同時清除中斷標(biāo)識位,因此當(dāng)我們此時不想或者無法傳遞InterruptedException異常,也不對該異常做任何處理時,我們最好通過再次調(diào)用interrupt來恢復(fù)中斷的狀態(tài),以供上層調(diào)用者處理,這一點,我們在逐行分析AQS源碼(二): 鎖的釋放的最后就說明過這種用法。

接下來的兩段注釋是關(guān)于NIO的,我們暫時不看,直接看最后兩段:

/*
 ...
 * 

If none of the previous conditions hold then this thread"s interrupt * status will be set.

* *

Interrupting a thread that is not alive need not have any effect. */

這段話是說:

如果線程沒有因為上面的函數(shù)調(diào)用而進入阻塞狀態(tài)的話,那么中斷這個線程僅僅會設(shè)置它的中斷標(biāo)志位(而不會拋出InterruptedException)

中斷一個已經(jīng)終止的線程不會有任何影響。

注釋看完了之后我們再來看代碼部分,其實代碼部分很簡單,中間那段同步代碼塊是和NIO有關(guān)的,我們可以暫時不管,整個方法的核心調(diào)用就是interrupt0()方法,而它是一個native方法:

private native void interrupt0();

這個方法所做的事情很簡單:

Just to set the interrupt flag

所以,至此我們明白了,所謂“中斷一個線程”,其實并不是讓一個線程停止運行,僅僅是將線程的中斷標(biāo)志設(shè)為true, 或者在某些特定情況下拋出一個InterruptedException,它并不會直接將一個線程停掉,在被中斷的線程的角度看來,僅僅是自己的中斷標(biāo)志位被設(shè)為true了,或者自己所執(zhí)行的代碼中拋出了一個InterruptedException異常,僅此而已。

終止一個線程

既然上面我們提到了,中斷一個線程并不會使得該線程停止執(zhí)行,那么我們該怎樣終止一個線程的執(zhí)行呢。早期的java中提供了stop()方法來停止一個線程,但是這個方法是不安全的,所以已經(jīng)被廢棄了。現(xiàn)在終止一個線程,基本上只能靠“曲線救國”式的中斷來實現(xiàn)。

終止處于阻塞狀態(tài)的線程

前面我們說過,當(dāng)一個線程因為調(diào)用wait,sleep,join方法而進入阻塞狀態(tài)后,若在這時中斷這個線程,則這些方法將會拋出InterruptedException異常,我們可以利用這個異常,使線程跳出阻塞狀態(tài),從而終止線程。

@Override
public void run() {
    while(true) {
        try {
            // do some task
            // blocked by calling wait/sleep/join
        } catch (InterruptedException ie) {  
            // 如果該線程被中斷,則會拋出InterruptedException異常
            // 我們通過捕獲這個異常,使得線程從block狀態(tài)退出
            break; // 這里使用break, 可以使我們在線程中斷后退出死循環(huán),從而終止線程。
        }
    }
}
終止處于運行狀態(tài)的線程

與中斷一個處于阻塞狀態(tài)所不同的是,中斷一個處于運行狀態(tài)的線程只會將該線程的中斷標(biāo)志位設(shè)為true, 而并不會拋出InterruptedException異常,為了能在運行過程中感知到線程已經(jīng)被中斷了,我們只能通過不斷地檢查中斷標(biāo)志位來實現(xiàn):

@Override
public void run() {
    while (!isInterrupted()) {
        // do some task...
    }
}

這里,我們每次循環(huán)都會先檢查中斷標(biāo)志位,只要當(dāng)前線程被中斷了,isInterrupted()方法就會返回true,從而終止循環(huán)。

終止一個Alive的線程

上面我們分別介紹了怎樣終止一個處于阻塞狀態(tài)或運行狀態(tài)的線程,如果我們將這兩種方法結(jié)合起來,那么就可以同時應(yīng)對這兩種狀況,從而能夠終止任意一個存活的線程:

@Override
public void run() {
    try {
        // 1. isInterrupted() 用于終止一個正在運行的線程。
        while (!isInterrupted()) {
            // 執(zhí)行任務(wù)...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException異常用于終止一個處于阻塞狀態(tài)的線程
    }
}

不過使用這兩者的組合一定要注意,wait,sleep,join等方法拋出InterruptedException有一個副作用: 清除當(dāng)前的中斷標(biāo)志位,所以不要在異常拋出后不做任何處理,而寄望于用isInterrupted()方法來判斷,因為中標(biāo)志位已經(jīng)被重置了,所以下面這種寫法是不對的:

@Override
public void run() {
    //isInterrupted() 用于終止一個正在運行的線程。
    while (!isInterrupted()) {
        try {
                // 執(zhí)行任務(wù)...
            }
        } catch (InterruptedException ie) {  
            // 在這里不做任何處理,僅僅依靠isInterrupted檢測異常
        }
    }
}

這個方法中,在catch塊中我們檢測到異常后沒有使用break方法跳出循環(huán),而此時中斷狀態(tài)已經(jīng)被重置,當(dāng)我們再去調(diào)用isInterrupted,依舊會返回false, 故線程仍然會在while循環(huán)中執(zhí)行,無法被中斷。

總結(jié)

Java沒有提供一種安全直接的方法來停止某個線程,但是提供了中斷機制。對于被中斷的線程,中斷只是一個建議,至于收到這個建議后線程要采取什么措施,完全由線程自己決定。

中斷機制的核心在于中斷狀態(tài)和InterruptedException異常

中斷狀態(tài):

設(shè)置一個中斷狀態(tài): Thread#interrupt

清除一個中斷狀態(tài): Thread.interrupted

Thread.interrupted方法同時會返回線程原來的中斷的狀態(tài)。
如果僅僅想查看線程當(dāng)前的中斷狀態(tài)而不清除原來的狀態(tài),則應(yīng)該使用Thread#isInterrupted。

某些阻塞方法在拋出InterruptedException異常后,會同時清除中斷狀態(tài)。若不能對該異常做出處理也無法向上層拋出,則應(yīng)該通過再次調(diào)用interrupt方法恢復(fù)中斷狀態(tài),以供上層處理,通常情況下我們都不應(yīng)該屏蔽中斷請求。

中斷異常:

中斷異常一般是線程被中斷后,在一些block類型的方法(如wait,sleep,join)中拋出。

我們可以使用Thread#interrupt中斷一個線程,被中斷的線程所受的影響為以下兩種之一:

若被中斷前,該線程處于非阻塞狀態(tài),那么該線程的中斷狀態(tài)被設(shè)為true, 除此之外,不會發(fā)生任何事。

若被中斷前,該線程處于阻塞狀態(tài)(調(diào)用了wait,sleep,join等方法),那么該線程將會立即從阻塞狀態(tài)中退出,并拋出一個InterruptedException異常,同時,該線程的中斷狀態(tài)被設(shè)為false, 除此之外,不會發(fā)生任何事。

無論是中斷狀態(tài)的改變還是InterruptedException被拋出,這些都是當(dāng)前線程可以感知到的"建議",如果當(dāng)前線程選擇忽略這些建議(例如簡單地catch住異常繼續(xù)執(zhí)行),那么中斷機制對于當(dāng)前線程就沒有任何影響,就好像什么也沒有發(fā)生一樣。

所以,中斷一個線程,只是傳遞了請求中斷的消息,并不會直接阻止一個線程的運行。

(完)

查看更多系列文章:系列文章目錄

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/76808.html

相關(guān)文章

  • 逐行分析AQS源碼(2)——獨占鎖的釋放

    摘要:我們知道,這個函數(shù)將返回當(dāng)前正在執(zhí)行的線程的中斷狀態(tài),并清除它。注意,中斷對線程來說只是一個建議,一個線程被中斷只是其中斷狀態(tài)被設(shè)為線程可以選擇忽略這個中斷,中斷一個線程并不會影響線程的執(zhí)行。 前言 系列文章目錄 上一篇文章 我們逐行分析了獨占鎖的獲取操作, 本篇文章我們來看看獨占鎖的釋放。如果前面的鎖的獲取流程你已經(jīng)趟過一遍了, 那鎖的釋放部分就很簡單了, 這篇文章我們直接開始看...

    tinna 評論0 收藏0
  • 源碼的角度再學(xué)「Thread

    摘要:前言中的線程是使用類實現(xiàn)的,在初學(xué)的時候就學(xué)過了,也在實踐中用過,不過一直沒從源碼的角度去看過它的實現(xiàn),今天從源碼的角度出發(fā),再次學(xué)習(xí),愿此后對的實踐更加得心應(yīng)手。如果一個線程已經(jīng)啟動并且尚未死亡,則該線程處于活動狀態(tài)。 showImg(https://segmentfault.com/img/remote/1460000017963014?w=1080&h=720); 前言 Java...

    abson 評論0 收藏0
  • Java Executors 源碼分析

    摘要:表示一個異步任務(wù)的結(jié)果,就是向線程池提交一個任務(wù)后,它會返回對應(yīng)的對象。它們分別提供兩個重要的功能阻塞當(dāng)前線程等待一段時間直到完成或者異常終止取消任務(wù)。此時,線程從中返回,然后檢查當(dāng)前的狀態(tài)已經(jīng)被改變,隨后退出循環(huán)。 0 引言 前段時間需要把一個C++的項目port到Java中,因此時隔三年后重新熟悉了下Java。由于需要一個通用的線程池,自然而然就想到了Executors。 用了...

    itvincent 評論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續(xù)更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因為寫作的時候發(fā)現(xiàn),為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...

    lijy91 評論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續(xù)更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因為寫作的時候發(fā)現(xiàn),為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...

    Yumenokanata 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<