線程中斷理解

無法中斷的線程

package objective1.action2;

public class InterruptHandler {

    public static void main(String[] args) {
        Runnable runnable = new InterruptRunner();
        Thread thread = new Thread(runnable);
        thread.start();
        thread.interrupt();
    }

}

class InterruptRunner implements Runnable {

    @Override
    public void run() {
        int i = 0;
        while (true) {
            System.out.println("i=" + i++);
        }
    }
}

該例子嘗試使用用interrupt中斷線程,但interrupt方法並不像break方法一樣,馬上停止循環,而是持續對i增加,不會停止。

中斷可以理解爲線程的一個標識位屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作。中斷好比其他線程對該線程打了個招呼,其他線程通過調用該線程的interrupt()
方法對其進行中斷操作。

 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();
    }

從上面源碼中也可與看出,interrupt只是在當前線程中打了個停止標誌。
那麼如何才能停止線程呢?
可以在該線程中觀察中斷標誌狀態,如果中斷標誌狀態顯示中斷,就可以在該線程中執行中斷。

判斷線程中斷的狀態

Thread提供了兩種方法判斷線程中斷狀態。

 public static boolean interrupted()
 public boolean isInterrupted()

兩個方法有什麼區別呢?
interrupted方法: 測試當前線程是否已經是中斷狀態,並清除標誌位
isInterrupted方法:測試當前線程是否已經是中斷狀態,不清除標誌位

  • isInterrupted()方法
public class InterruptHandler2 {

    public static void main(String[] args) {
        Thread thread = new InterruptThread();
        thread.start();
        thread.interrupt();
    }

}

class InterruptThread extends Thread {
    @Override
    public void run() {
        System.out.println("停止狀態1:" + Thread.currentThread().isInterrupted());
        System.out.println("停止狀態2:" + Thread.currentThread().isInterrupted());
        System.out.println("停止狀態3:" + Thread.currentThread().isInterrupted());
    }
}
停止狀態1true
停止狀態2true
停止狀態3true

可以看出isInterrupted()判斷狀態後,再次判斷中斷狀態仍然是true,沒有清除中斷標誌位。

  • interrupted方法
public class InterruptHandler2 {

    public static void main(String[] args) {
        Thread thread = new InterruptThread1();
        thread.start();
        thread.interrupt();

    }
}

class InterruptThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("停止狀態1:" + Thread.interrupted());
        System.out.println("停止狀態2:" + Thread.interrupted());
        System.out.println("停止狀態3:" + Thread.interrupted());
    }
}
停止狀態1true
停止狀態2false
停止狀態3false

可以看出interrupted()判斷狀態後,再次判斷中斷狀態全是false,說明將標誌位恢復了。

  • 源碼分析

觀察Thread源碼,發現這兩個方法最終都是調用 isInterrupted(boolean ClearInterrupted),不同的只是interrupted()默認將清除中斷標誌位設成true.

   public boolean isInterrupted() {
        return isInterrupted(false);
    }
   public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
 private native boolean isInterrupted(boolean ClearInterrupted);

停止線程的方法

使用異常停止

使用break停止

判斷中斷標誌位,當標誌位被置爲中斷時,使用break中斷。雖然中斷了while循環但是,還是繼續往下執行了,也就是說線程沒有立即停止。

public class InterruptHandler {

    public static void main(String[] args) {
        System.out.println("start");
        Runnable runnable = new InterruptRunner();
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
        System.out.println("end");
    }

}

class InterruptRunner implements Runnable {

    @Override
    public void run() {
        int i = 0;
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("已經是中斷狀態了,我要退出了");
                break;
            }
            System.out.println("我還沒中斷 i=" + i++);

        }
        System.out.println("我在while下面!我被執行說明break後代碼繼續運行,線程沒有立即停止");
    }
}
....
我還沒中斷 i=3
end
已經是中斷狀態了,我要退出了
我在while下面!我被執行說明break後代碼繼續運行,線程沒有立即停止

使用在sleep中catch異常停止

public class InterruptHandler {

    public static void main(String[] args) {
        System.out.println("start");
        Runnable runnable = new InterruptRunner();
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
        System.out.println("end");
    }

}

class InterruptRunner implements Runnable {

    @Override
    public void run() {
        int i = 0;
        try {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("已經是中斷狀態了,我要退出了");
                    Thread.sleep(100);
                }
                System.out.println("我還沒中斷 i=" + i++);

            }
        } catch (InterruptedException e) {
            System.out.println("中斷標誌" + Thread.currentThread().isInterrupted());
            System.out.println("進入" + Thread.currentThread().getName() + "的exception方法,然後中斷");
            System.out.println(e);
            Thread.currentThread().interrupt();
        }

    }
}
我還沒中斷 i=19
end
已經是中斷狀態了,我要退出了
catch exception後中斷標誌false
進入Thread-0exception方法,然後中斷
java.lang.InterruptedException: sleep interrupted

可以看出在線程sleep時候,存在中斷操作會拋出InterruptedException ,並且將清除中斷標誌位停止狀態。而這時若catch住異常,並對線程執行interrupt操作,就可以達到中斷線程的效果。

Q:那麼爲什麼在線程sleep的時候,執行中斷操作(或中斷時候執行sleep)會拋出異常呢?
當thread被阻塞的時候,比如被Object.wait,Thread.join和Thread.sleep三種方法之一阻塞時。調用它的interrput()方法。 可想而知,沒有佔用CPU運行的線程是不可能給自己的中斷狀態置位的。這就會產生一個InterruptedException異常。

使用拋出異常在外面catch停止

public class InterruptHandler {

    public static void main(String[] args) {
        System.out.println("start");
        Runnable runnable = new InterruptRunner();
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
        System.out.println("end");
    }

}

class InterruptRunner implements Runnable {

    @Override
    public void run() {
        int i = 0;
        try {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("已經是中斷狀態了,我要退出了");
                    throw new InterruptedException();
                }
                System.out.println("我還沒中斷 i=" + i++);

            }
        } catch (InterruptedException e) {
            System.out.println("中斷標誌" + Thread.currentThread().isInterrupted());
            System.out.println("進入" + Thread.currentThread().getName() + "的exception方法,然後中斷");
            System.out.println(e);
        }

    }
}
我還沒中斷 i=57
end
已經是中斷狀態了,我要退出了
中斷標誌true
進入Thread-0exception方法,然後中斷
java.lang.InterruptedException

如上例子判斷中斷標誌位爲true時,立即將異常拋出,並在外層處理達到線程停止。這樣做的好處是,還可以將異常往外拋,使線程停止的事件得以傳播。

使用return的方法停止

public class InterruptHandler {

    public static void main(String[] args) {
        System.out.println("start");
        Runnable runnable = new InterruptRunner();
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
        System.out.println("end");
    }

}

class InterruptRunner implements Runnable {

    @Override
    public void run() {
        int i = 0;
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("已經是中斷狀態了,我要退出了");
                return;
            }
            System.out.println("我還沒中斷 i=" + i++);
        }
    }
}
start
我還沒中斷 i=0
end
已經是中斷狀態了,我要退出了

可以看出,其實和用異常方法停止線程沒有本質區別,都是在判斷中斷標誌位被設置爲true時,進行退出操作。

使用過期的stop方法停止

stop()方法在終結一個線程時不會保證線程的資源正常釋放,通常是沒有給予線程完成資源釋放工作的機會,因此會導致程序可能工作在不確定狀態下。不建議使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章