Java中断机制
引言
Java中斷機制為我們提供了一種"試圖"停止一個線程的方法。設想我們有一個線程阻塞在一個耗時的I/O中,我們又不想一直等下去,那么我們怎么樣才能停止這個線程呢?答案就是Java的中斷機制。
從Java線程的狀態說起
Java線程的狀態包括:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING和TERMINATED總共六種狀態:
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;} 復制代碼一個線程新建后,在調用該線程的start()方法之前,該線程的狀態就是NEW;當線程的start()方法被調用之后,該線程已經準備被CPU調度,此時線程的狀態就是RUNNABLE;如果一個對象的monitor lock被其他線程獲取了,這個時候我們再通過synchronized關鍵字去獲取改對象的monitor lock時,線程就會進入BLOCKED狀態;通過調用Thread#join()方法或者Object#wait()方法(不設置超時時間,with no timeout)或者LockSupport#park()方法可以讓一個線程從RUNNABLE狀態轉為WAITING狀態;TIMED_WAITING指線程處于等待中,但是這個等待是有期限的(),通過調用Thread#sleep(),Object#wait(long timeout),Thread#join(long timeout),LockSupport#parkNanos(),LockSupport#partUnitl()都可使線程切換到TIMED_WAITING狀態。最后一種狀態TERMINATED是指線程正常退出或者異常退出后的狀態。下面這張圖展示了這六種線程狀態之間的相互轉換關系:
當線程處于等待狀態或者有超時的等待狀態時(TIMED_WAITING,WAITING)我們可以通過調用線程的interrupt()方法來中斷線程的等待,此時線程會拋InterruptedException異常。例如Thread.sleep()方法: public static native void sleep(long millis) throws InterruptedException; 此方法就會拋出這類異常。 但是當線程處于BLOCKED狀態或者RUNNABLE(RUNNING)狀態時,調用線程的interrupt()方法也只能將線程的狀態位設置為true。停止線程的邏輯需要我們自己去實現。
中斷原理
查看java源碼可以看到Thread類中提供了跟中斷相關的一些方法:
public void interrupt() {if (this != Thread.currentThread())checkAccess(); 1synchronized (blockerLock) {Interruptible b = blocker;if (b != null) { 2interrupt0(); // Just to set the interrupt flag 3b.interrupt(this); 4return;}}interrupt0();} 復制代碼interrupt()方法用于中斷一個線程,首先在第1處中斷方法會判斷當前caller線程是否具有中斷本線程的權限,如果沒有權限則拋出SecurityException異常。然后在2處方法會判斷阻塞當前線程的對象是否為空,如果不為空,則在3處先設置線程的中斷flag為true,然后再由Interruptible對象(例如可以中斷的I/O操作)去中斷操作。從中我們也可以看到如果當前線程不處于WAITING或者TIMED_WAITING狀態,則interrupt()方法也只是僅僅設置了該線程的中斷flag為true而已。
public static boolean interrupted() {return currentThread().isInterrupted(true);} 復制代碼再看interrupted()方法,該方法是一個靜態的方法,最終會調用具體線程實例的isInterrupted()方法:
public boolean isInterrupted() {return isInterrupted(false);}private native boolean isInterrupted(boolean ClearInterrupted); 復制代碼interrupted()方法第一次調用時會返回當前線程的中斷flag,然后就清除了這個狀態位,但是isInterrupted()方法不會清除該狀態位。因此一個線程中斷后,第一次調用interrupted()方法會返回true,第二次調用就返回false,因此第一次調用后該狀態位已經被清除了。但是同樣一個線程中斷后,多次調用isInterrupted()方法都會返回true。這是兩者的不同之處,但是兩者最終都會調用一個名為isInterrupted的native方法。
中斷的處理
java中一般方法申明了throws InterruptedException的就是可以中斷的方法,比如:ReentrantLock#lockInterruptibly,BlockingQueue#put,Thread#sleep,Object#wait等。當這些方法被中斷后會拋出InterruptedException異常,我們可以捕獲這個異常,并實現自己的處理邏輯,也可以不處理,繼續向上層拋出異常。但是記住不要捕獲異常后什么都不做并且不向上層拋異常,也就是說我們不能"吞掉"異常。 對于一些沒有拋出InterruptedException的方法的中斷邏輯只能由我們自己去實現了。例如在一個大的循環中,我們可以自己判斷當前線程的中斷狀態然后選擇是否中斷當前操作:
public class InterruptTest {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// 循環中檢測當前線程的中斷狀態 for (int i = 0; i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted(); i++) {System.out.println(i);}}});thread.start();Thread.sleep(100);thread.interrupt();} } 復制代碼總結
- 上一篇: 刷leetcode第705题- 设计哈希
- 下一篇: java美元兑换,(Java实现) 美元