Java 中斷線程方法

線程終止方法

Stop

這個方法就不介紹了,不推薦使用,已經被廢棄了。會強制停止線程,進程和虛擬機會發生不可預料的狀態。不優雅

   /**
     * Requests the receiver Thread to stop and throw ThreadDeath. The Thread is
     * resumed if it was suspended and awakened if it was sleeping, so that it
     * can proceed to throw ThreadDeath.
     *
     * @deprecated because stopping a thread in this manner is unsafe and can
     * leave your application and the VM in an unpredictable state.
     */
    @Deprecated
    public final void stop() {
        stop(new ThreadDeath());
    }

    /**
     * Throws {@code UnsupportedOperationException}.
     * @deprecated because stopping a thread in this manner is unsafe and can
     * leave your application and the VM in an unpredictable state.
     */
    @Deprecated
    public final synchronized void stop(Throwable throwable) {
        throw new UnsupportedOperationException();
    }

    /**
     * Throws {@code UnsupportedOperationException}.
     * @deprecated May cause deadlocks.
     */
    @Deprecated
    public final void suspend() {
        throw new UnsupportedOperationException();
    }

stopping a thread in this manner is unsafe and can leave your application and the VM in an unpredictable state.

Interrupts

原文地址: Java官方Interrupts介紹
An interrupt is an indication to a thread that it should stop what it is doing and do something else. It’s up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate. This is the usage emphasized in this lesson.

程序員來決定是否中斷進程

A thread sends an interrupt by invoking interrupt on the Thread object for the thread to be interrupted. For the interrupt mechanism to work correctly, the interrupted thread must support its own interruption.

Supporting Interruption

How does a thread support its own interruption? This depends on what it’s currently doing. If the thread is frequently invoking methods that throw InterruptedException, it simply returns from the run method after it catches that exception. For example, suppose the central message loop in the SleepMessages example were in the run method of a thread’s Runnable object. Then it might be modified as follows to support interrupts:

for (int i = 0; i < importantInfo.length; i++) {
    // Pause for 4 seconds
    try {
        Thread.sleep(4000);
    } catch (InterruptedException e) {
        // We've been interrupted: no more messages.
        return;
    }
    // Print a message
    System.out.println(importantInfo[i]);
}

Many methods that throw InterruptedException, such as sleep, are designed to cancel their current operation and return immediately when an interrupt is received.

當對象在阻塞狀態時,Object.wait()/Thread.sleep()/Thread.join(),那麼此時線程內部檢查到中斷flag爲true,那麼線程內部會拋出InterruptedException異常,然後可以在catch中捕獲到該異常
同時由程序員決定是否,終止進程,可以直接return,run結束。如果不處理,那麼是沒法終止線程的

What if a thread goes a long time without invoking a method that throws InterruptedException? Then it must periodically invoke Thread.interrupted, which returns true if an interrupt has been received. For example:

for (int i = 0; i < inputs.length; i++) {
    heavyCrunch(inputs[i]);
    if (Thread.interrupted()) {
        // We've been interrupted: no more crunching.
        return;
    }
}

In this simple example, the code simply tests for the interrupt and exits the thread if one has been received. In more complex applications, it might make more sense to throw an InterruptedException:

if (Thread.interrupted()) {
    throw new InterruptedException();
}

This allows interrupt handling code to be centralized in a catch clause.

另一方面,如果線程沒在阻塞狀態,在運行狀態,那麼就不會檢查中斷flag,自然也不會去拋出捕獲InterruptedException異常,所以程序不會終止。
解決方案可以通過Thread.interrupted()/isInterrupted方法(兩個方法區別見如下)來手動檢查中斷flag,來決定是否終止程序

The Interrupt Status Flag

The interrupt mechanism is implemented using an internal flag known as the interrupt status. Invoking Thread.interrupt sets this flag. When a thread checks for an interrupt by invoking the static method Thread.interrupted, interrupt status is cleared. The non-static isInterrupted method, which is used by one thread to query the interrupt status of another, does not change the interrupt status flag.

By convention, any method that exits by throwing an InterruptedException clears interrupt status when it does so. However, it’s always possible that interrupt status will immediately be set again, by another thread invoking interrupt.

Thread.interrupt設置中斷flag爲true,靜態方法Thread.interrupted會清空中斷falg,非靜態方法isInterrupted不會清空中斷flag
任何一個拋出InterruptedException異常的方法都會清空中斷flag

最後看看官方文檔介紹:

    /**
     * Posts an interrupt request to this {@code Thread}. The behavior depends on
     * the state of this {@code Thread}:
     * <ul>
     * <li>
     * {@code Thread}s blocked in one of {@code Object}'s {@code wait()} methods
     * or one of {@code Thread}'s {@code join()} or {@code sleep()} methods will
     * be woken up, their interrupt status will be cleared, and they receive an
     * {@link InterruptedException}.
     * <li>
     * {@code Thread}s blocked in an I/O operation of an
     * {@link java.nio.channels.InterruptibleChannel} will have their interrupt
     * status set and receive an
     * {@link java.nio.channels.ClosedByInterruptException}. Also, the channel
     * will be closed.
     * <li>
     * {@code Thread}s blocked in a {@link java.nio.channels.Selector} will have
     * their interrupt status set and return immediately. They don't receive an
     * exception in this case.
     * <ul>
     *
     * @see Thread#interrupted
     * @see Thread#isInterrupted
     */
    public void interrupt()
  1. wait/join/sleep 狀態時會清空中斷flag,同時接收到InterruptedException異常
  2. 阻塞在I/O operation(java.nio.channels.InterruptibleChannel)接觸的很少,設置自己的中斷狀態同時接收到ClosedByInterruptException,注意這個I/O是特殊的IO,不是我們常用的IO,我們的IO是java.io這個包,同時這個中斷狀態也是他們自己的,而不是上述的中斷flag
  3. 阻塞在java.nio.channels.Selector,這玩意是啥,沒接觸過。設置自己的中斷狀態,直接return,不接收任何異常

代碼示例

public class ThreadCon extends Thread  
{  
    public void run()  
    {  

        for(int i = 0; i < Integer.MAX_VALUE; i++)  
        {  
            System.out.println(i);  
        }  

    }  

    public static void main(String[] args)  
    {  
        ThreadCon thread = new ThreadCon();  
        thread.start();  
        System.out.println("main thread");  
        try   
        {  
            TimeUnit.SECONDS.sleep(2);  
        }   
        catch(InterruptedException e)   
        {  
            e.printStackTrace();  
        }  
        thread.interrupt();  
    }  
}  

這是一段比較簡單的示例,我們在開始的時候啓動一個子線程,這個線程打印從0到Integer.MAX_VALUE 的值。而主線程先sleep 2秒鐘。然後再嘗試去中斷子線程。如果我們去運行前面這一段代碼,會發現子線程會一直在輸出數字結果,它根本就不會停下來,說明,我們對一個子線程發interrupt的消息時,如果線程是在運行的狀態之下,它會忽略這個請求而繼續執行的。

最佳實踐

源碼

Thread.sleep方法

    public static void sleep(long millis, int nanos) throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("millis < 0: " + millis);
        }
        if (nanos < 0) {
            throw new IllegalArgumentException("nanos < 0: " + nanos);
        }
        if (nanos > 999999) {
            throw new IllegalArgumentException("nanos > 999999: " + nanos);
        }

        // The JLS 3rd edition, section 17.9 says: "...sleep for zero
        // time...need not have observable effects."
        if (millis == 0 && nanos == 0) {
            // ...but we still have to handle being interrupted.
            if (Thread.interrupted()) {
              throw new InterruptedException();
            }
            return;
        }

        long start = System.nanoTime();
        long duration = (millis * NANOS_PER_MILLI) + nanos;

        Object lock = currentThread().lock;

        // Wait may return early, so loop until sleep duration passes.
        synchronized (lock) {
            while (true) {
                sleep(lock, millis, nanos);

                long now = System.nanoTime();
                long elapsed = now - start;

                if (elapsed >= duration) {
                    break;
                }

                duration -= elapsed;
                start = now;
                millis = duration / NANOS_PER_MILLI;
                nanos = (int) (duration % NANOS_PER_MILLI);
            }
        }
    }

if (millis == 0 && nanos == 0) 我猜測是從sleep中醒過來,然後執行Thread.interrupted()方法去檢查中斷flag並清空中斷flag,如果爲true,那麼就拋出InterruptedException異常
調用Thread的interrupt會把中斷flag置爲true

Object.wait方法

public final native void wait() throws InterruptedException;

native表示調用了本地方法C/C++方法,參考sleep,可以猜測出內部其實肯定也調用了interrupted方法,檢查中斷flag,然後清空flag

優雅停止線程方案

MyThread提供一個quit接口供外部終止線程,本例中模擬線程一直在運行態,一直沒被阻塞。也可以把Thread.sleep(1000);取消註釋也是完全可以的,Thread.sleep(1000)如果調用了Thread.interrupt,那麼會主動拋出InterruptedException異常而不需要手動去拋出啦

public class Test {
    public static void main(String args[]) {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread.quit();
        //myThread.interrupt();
        //myThread.stop();
        System.out.println("main thread after 1s sleep");
    }
};

class MyThread extends Thread {
    private int count;
    private boolean mQuit;

    @Override
    public void run() {
        while (count < Integer.MAX_VALUE) {
            try {
                if (interrupted()) {
                    throw new InterruptedException();
                }
                count++;
                System.out.println("count: " + count);
                // Thread.sleep(1000);
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

    public void quit() {
        interrupt();
        mQuit = true;
    }
}

輸出結果: 1s後,MyThread線程打印到113328就結束啦。。
……..
count: 113328
main thread after 1s sleep

注意這裏必須調用myThread.quit();而不能調用myThread.interrupt();否則一直到打印到Integer.MAX_VALUE值
因爲捕獲到InterruptedException異常,如果mQuit爲false,那麼continue只會終止本次循環,而不會跳出本次循環

如果執行myThread.stop();
打印結果:
……
count: 105041
count: 105042
count: 105043count: 105043main thread after 1s sleep

count: 105043打印了兩次,線程發生了不可預料的狀態

發佈了78 篇原創文章 · 獲贊 9 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章