引言
Java中斷機制為我們提供了一種"試圖"停止一個線程的方法。設想我們有一個線程阻塞在一個耗時的I/O中眨攘,我們又不想一直等下去主慰,那么我們怎么樣才能停止這個線程呢?答案就是Java的中斷機制鲫售。
從Java線程的狀態(tài)說起
Java線程的狀態(tài)包括:NEW共螺,RUNNABLE,BLOCKED情竹,WAITING藐不,TIMED_WAITING和TERMINATED總共六種狀態(tài):
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
一個線程新建后,在調(diào)用該線程的start()方法之前秦效,該線程的狀態(tài)就是NEW雏蛮;當線程的start()方法被調(diào)用之后,該線程已經(jīng)準備被CPU調(diào)度阱州,此時線程的狀態(tài)就是RUNNABLE挑秉;如果一個對象的monitor lock被其他線程獲取了,這個時候我們再通過synchronized關鍵字去獲取改對象的monitor lock時苔货,線程就會進入BLOCKED狀態(tài)犀概;通過調(diào)用Thread#join()方法或者Object#wait()方法(不設置超時時間立哑,with no timeout)或者LockSupport#park()方法可以讓一個線程從RUNNABLE狀態(tài)轉(zhuǎn)為WAITING狀態(tài);TIMED_WAITING指線程處于等待中姻灶,但是這個等待是有期限的()铛绰,通過調(diào)用Thread#sleep(),Object#wait(long timeout)产喉,Thread#join(long timeout)至耻,LockSupport#parkNanos(),LockSupport#partUnitl()都可使線程切換到TIMED_WAITING狀態(tài)镊叁。最后一種狀態(tài)TERMINATED是指線程正常退出或者異常退出后的狀態(tài)尘颓。下面這張圖展示了這六種線程狀態(tài)之間的相互轉(zhuǎn)換關系:
當線程處于等待狀態(tài)或者有超時的等待狀態(tài)時(TIMED_WAITING,WAITING)我們可以通過調(diào)用線程的interrupt()方法來中斷線程的等待晦譬,此時線程會拋InterruptedException異常疤苹。例如Thread.sleep()方法:
public static native void sleep(long millis) throws InterruptedException;
此方法就會拋出這類異常。
但是當線程處于BLOCKED狀態(tài)或者RUNNABLE(RUNNING)狀態(tài)時敛腌,調(diào)用線程的interrupt()方法也只能將線程的狀態(tài)位設置為true卧土。停止線程的邏輯需要我們自己去實現(xiàn)。
中斷原理
查看java源碼可以看到Thread類中提供了跟中斷相關的一些方法:
public void interrupt() {
if (this != Thread.currentThread())
checkAccess(); 1
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) { 2
interrupt0(); // Just to set the interrupt flag 3
b.interrupt(this); 4
return;
}
}
interrupt0();
}
interrupt()方法用于中斷一個線程像樊,首先在第1處中斷方法會判斷當前caller線程是否具有中斷本線程的權限尤莺,如果沒有權限則拋出SecurityException異常。然后在2處方法會判斷阻塞當前線程的對象是否為空生棍,如果不為空颤霎,則在3處先設置線程的中斷flag為true,然后再由Interruptible對象(例如可以中斷的I/O操作)去中斷操作涂滴。從中我們也可以看到如果當前線程不處于WAITING或者TIMED_WAITING狀態(tài)友酱,則interrupt()方法也只是僅僅設置了該線程的中斷flag為true而已。
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
再看interrupted()方法柔纵,該方法是一個靜態(tài)的方法缔杉,最終會調(diào)用具體線程實例的isInterrupted()方法:
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
interrupted()方法第一次調(diào)用時會返回當前線程的中斷flag,然后就清除了這個狀態(tài)位搁料,但是isInterrupted()方法不會清除該狀態(tài)位或详。因此一個線程中斷后,第一次調(diào)用interrupted()方法會返回true郭计,第二次調(diào)用就返回false霸琴,因此第一次調(diào)用后該狀態(tài)位已經(jīng)被清除了。但是同樣一個線程中斷后拣宏,多次調(diào)用isInterrupted()方法都會返回true沈贝。這是兩者的不同之處杠人,但是兩者最終都會調(diào)用一個名為isInterrupted的native方法勋乾。
中斷的處理
java中一般方法申明了throws InterruptedException的就是可以中斷的方法宋下,比如:ReentrantLock#lockInterruptibly,BlockingQueue#put辑莫,Thread#sleep学歧,Object#wait等。當這些方法被中斷后會拋出InterruptedException異常各吨,我們可以捕獲這個異常枝笨,并實現(xiàn)自己的處理邏輯,也可以不處理揭蜒,繼續(xù)向上層拋出異常横浑。但是記住不要捕獲異常后什么都不做并且不向上層拋異常,也就是說我們不能"吞掉"異常屉更。
對于一些沒有拋出InterruptedException的方法的中斷邏輯只能由我們自己去實現(xiàn)了徙融。例如在一個大的循環(huán)中,我們可以自己判斷當前線程的中斷狀態(tài)然后選擇是否中斷當前操作:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 循環(huán)中檢測當前線程的中斷狀態(tài)
for (int i = 0; i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted(); i++) {
System.out.println(i);
}
}
});
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}