Java並發編程原理與實戰四:線程如何中斷


如果你使用過殺毒軟件,可能會發現全盤殺毒太耗時間了,這時你如果點擊取消殺毒按鈕,那么此時你正在中斷一個運行的線程。

java為我們提供了一種調用interrupt()方法來請求終止線程的方法,下面我們就一起來學習一下線程的中斷。

 

每一個線程都有一個boolean類型標志,用來表明當前線程是否請求中斷,當一個線程調用interrupt() 方法時,線程的中斷標志將被設置為true。

我們可以通過調用Thread.currentThread().isInterrupted()或者Thread.interrupted()來檢測線程的中斷標志是否被置位。這兩個方法的區別是

Thread.currentThread().isInterrupted()是線程對象的方法,調用它后不清除線程中斷標志位;而Thread.interrupted()是一個靜態方法,調用它會清除

線程中斷標志位。

 

Thread.currentThread().isInterrupted():        對象方法        不清除中斷標志位

Thread.interrupted():                                        靜態方法         清除中斷標志位(設置為false)

 

所以說調用線程的interrupt() 方法不會中斷一個正在運行的線程,這個機制只是設置了一個線程中斷標志位,如果在程序中你不檢測線程中斷標志位,那么即使

設置了中斷標志位為true,線程也一樣照常運行。

 

一般來說中斷線程分為三種情況:

(一) :中斷非阻塞線程

(二):中斷阻塞線程

(三):不可中斷線程

 

(一) :中斷非阻塞線程

中斷非阻塞線程通常有兩種方式:

(1)采用線程共享變量

這種方式比較簡單可行,需要注意的一點是共享變量必須設置為volatile,這樣才能保證修改后其他線程立即可見。

public class InterruptThreadTest extends Thread{
 
    // 設置線程共享變量
    volatile boolean isStop = false;
    
    public void run() {
        while(!isStop) {
            long beginTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "is running");
            // 當前線程每隔一秒鍾檢測一次線程共享變量是否得到通知
            while (System.currentTimeMillis() - beginTime < 1000) {}
        }
        if (isStop) {
            System.out.println(Thread.currentThread().getName() + "is interrupted");
        }
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        InterruptThreadTest itt = new InterruptThreadTest();
        itt.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 線程共享變量設置為true
        itt.isStop = true;
    }
 
}

 

 (2) 采用中斷機制 

代碼如下:

public class InterruptThreadTest2 extends Thread{
    public void run() {
        // 這里調用的是非清除中斷標志位的isInterrupted方法
        while(!Thread.currentThread().isInterrupted()) {
            long beginTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "is running");
            // 當前線程每隔一秒鍾檢測線程中斷標志位是否被置位
            while (System.currentTimeMillis() - beginTime < 1000) {}
        }
        if (Thread.currentThread().isInterrupted()) {
            System.out.println(Thread.currentThread().getName() + "is interrupted");
        }
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        InterruptThreadTest2 itt = new InterruptThreadTest2();
        itt.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 設置線程的中斷標志位
        itt.interrupt();
    }
}

 

(二):中斷阻塞線程

當線程調用Thread.sleep()、Thread.join()、object.wait()再或者調用阻塞的i/o操作方法時,都會使得當前線程進入阻塞狀態。那么此時如果在線程處於阻塞狀態是調用

interrupt() 方法設置線程中斷標志位時會出現什么情況呢! 此時處於阻塞狀態的線程會拋出一個異常,並且會清除線程中斷標志位(設置為false)。這樣一來線程就能退出

阻塞狀態。當然拋出異常的方法就是造成線程處於阻塞狀態的Thread.sleep()、Thread.join()、object.wait()這些方法。

代碼實例如下:

 

public class InterruptThreadTest3 extends Thread{
    
    public void run() {
        // 這里調用的是非清除中斷標志位的isInterrupted方法
        while(!Thread.currentThread().isInterrupted()) {
            System.out.println(Thread.currentThread().getName() + " is running");
            try {
                System.out.println(Thread.currentThread().getName() + " Thread.sleep begin");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " Thread.sleep end");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                //由於調用sleep()方法清除狀態標志位 所以這里需要再次重置中斷標志位 否則線程會繼續運行下去
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }
        if (Thread.currentThread().isInterrupted()) {
            System.out.println(Thread.currentThread().getName() + "is interrupted");
        }
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        InterruptThreadTest3 itt = new InterruptThreadTest3();
        itt.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 設置線程的中斷標志位
        itt.interrupt();
    }
}

需要注意的地方就是 Thread.sleep()、Thread.join()、object.wait()這些方法,會檢測線程中斷標志位,如果發現中斷標志位為true則拋出異常並且將中斷標志位設置為false。

所以while循環之后每次調用阻塞方法后 都要在捕獲異常之后,調用Thread.currentThread().interrupt()重置狀態標志位。

 

(三):不可中斷線程

有一種情況是線程不能被中斷的,就是調用synchronized關鍵字和reentrantLock.lock()獲取鎖的過程。

但是如果調用帶超時的tryLock方法reentrantLock.tryLock(longtimeout, TimeUnit unit),那么如果線程在等待時被中斷,將拋出一個InterruptedException異常,這是一個非常

有用的特性,因為它允許程序打破死鎖。你也可以調用reentrantLock.lockInterruptibly()方法,它就相當於一個超時設為無限的tryLock方法。

public class InterruptThreadTest5 {
    
    public void deathLock(Object lock1, Object lock2) {
        try {
            synchronized (lock1) {
                System.out.println(Thread.currentThread().getName()+ " is running");
                // 讓另外一個線程獲得另一個鎖
                Thread.sleep(10);
                // 造成死鎖
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+ " is interrupted");
            e.printStackTrace();
        }
    }
    
    public static void main(String [] args) { 
        
        final InterruptThreadTest5 itt = new InterruptThreadTest5();
        final Object lock1 = new Object();
        final Object lock2 = new Object();
        Thread t1 = new Thread(new Runnable(){
            public void run() {
                itt.deathLock(lock1, lock2);
            }
        },"A"); 
        Thread t2 = new Thread(new Runnable(){
            public void run() {
                itt.deathLock(lock2, lock1);
            }
        },"B"); 
        
        t1.start();
        t2.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 中斷線程t1、t2
        t1.interrupt();
        t2.interrupt();
    }
}

 

其它學習文章參考:

如何正確地停止一個線程?

Java線程中斷機制-如何中斷線程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM