1. 程式人生 > >多執行緒進階--執行緒的中斷

多執行緒進階--執行緒的中斷

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

關於reentrantLock.lock()在獲取鎖的過程中為什麼不會被中斷,先看個例子

public class Service {

    private ReentrantLock lock = new ReentrantLock();

    public void methodA(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"AAA");

            while(!Thread.currentThread().isInterrupted()){

            }
            System.out.println(Thread.currentThread().getName()+"isInterrupted:"+Thread.currentThread().isInterrupted());
        } finally{
            lock.unlock();
        }

    }

    public void methodB(){
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"isInterrupted:"+Thread.currentThread().isInterrupted());
        System.out.println(Thread.currentThread().getName()+"BBB");

        lock.unlock();
    }
}
public class Run {
    public static void main(String[] args) {
        final Service s = new Service();
        Thread ta = new Thread(new Runnable() {

            public void run() {
                s.methodA();
            }
        },"執行緒A");
        ta.start();

        Thread tb = new Thread(new Runnable() {

            public void run() {
                s.methodB();
            }
        },"執行緒B");

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        tb.start();
//      ta.interrupt();
        tb.interrupt();

    }
}

輸出:
執行緒AAAA

可以看出來interrupt並不會中斷reentrantLock.lock()的阻塞。我們知道reentrantLock.lock()阻塞執行緒實際上是使用LockSupport.park()來使得執行緒B進入阻塞狀態的,但是LockSupport.park是可以被interrupt中斷的,那reentrantLock.lock()卻為什麼不被中斷呢?那是因為reentrantLock.lock()在LockSupport.park被中斷以後,又再次迴圈使其進入阻塞狀態了,也就是後面又再一次執行了LockSupport.park,使得執行緒進入阻塞狀態

分析一下原始碼:

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();//若當前執行緒在獲取鎖的過程中被其他執行緒中斷並且最終獲取到鎖之後,則設定
            //當前中斷標誌位為true。
}
private static void selfInterrupt() {
        Thread.currentThread().interrupt();//設定中斷標誌位為true
    }
 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;  //若當前執行緒被中斷,則該方法會返回true
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }


 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);   //當鎖被其他執行緒佔有時,當前執行緒獲取鎖的時候獲取不到。便被阻塞在
        //這裡了,當其他執行緒釋放鎖的時候,會unpark這個被阻塞的執行緒,該方法就繼續這裡執行,
        return Thread.interrupted();//並且返回true,並且清除中斷標誌位
    }

可見當執行緒在reentrantLock.lock()的阻塞的時候,若被其他執行緒interrupt,被阻塞執行緒並不被中斷,只是設定了被阻塞執行緒的中斷標誌位為true而已,當被阻塞執行緒獲取到鎖的時候,就可以根據Thread.currentThread().isInterrupted()判斷來執行自己的邏輯,雖然不知道synchronized的內部原理是怎麼樣的,但是當執行緒被interrupt的時候,其結果也是和reentrantLock.lock()一樣的,被阻塞執行緒並不會被中斷阻塞,只是中斷標誌位會被設定為true。

下面給出例子:

public class Service {

    private ReentrantLock lock = new ReentrantLock();

    public synchronized void methodA(){
//      lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"AAA");

            while(!Thread.currentThread().isInterrupted()){

            }
            System.out.println(Thread.currentThread().getName()+"isInterrupted:"+Thread.currentThread().isInterrupted());
        } finally{
//          lock.unlock();
        }

    }

    public synchronized void methodB(){
//      lock.lock();
        System.out.println(Thread.currentThread().getName()+"isInterrupted:"+Thread.currentThread().isInterrupted());
        System.out.println(Thread.currentThread().getName()+"BBB");

//      lock.unlock();
    }
}
public class Run {
    public static void main(String[] args) {
        final Service s = new Service();
        Thread ta = new Thread(new Runnable() {

            public void run() {
                s.methodA();
            }
        },"執行緒A");
        ta.start();

        Thread tb = new Thread(new Runnable() {

            public void run() {
                s.methodB();
            }
        },"執行緒B");

        System.out.println("main sleep 1000");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        tb.start();

        System.out.println("執行緒B interrupt");
        tb.interrupt();

        System.out.println("main sleep 1000");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("執行緒A interrupt");
        ta.interrupt();
    }
}

輸出結果:
執行緒AAAA
main sleep 1000
執行緒B interrupt
main sleep 1000
執行緒A interrupt
執行緒AisInterrupted:true
執行緒BisInterrupted:true
執行緒BBBB

相關推薦

執行--執行中斷

java為我們提供了一種呼叫interrupt()方法來請求終止執行緒的方法,下面我們就一起來學習一下執行緒的中斷。 每一個執行緒都有一個boolean型別標誌,用來表明當前執行緒是否請求中斷,當一個執行緒呼叫interrupt() 方法時,執行緒的中斷標誌將

c#執行

一、多執行緒傳遞引數的方法 整理自:https://www.cnblogs.com/shi5588/p/6130536.html 1 、帶引數的委託方法來傳遞引數 static void Main(string[] args) {

Java執行(三七)—— J.U.C之collections框架:LinkedBlockingDeque

一、LinkedBlockingDeque簡介 LinkedBlockingDeque和ConcurrentLinkedDeque類似,都是一種雙端佇列的結構,只不過LinkedBlockingDeque同時也是一種阻塞佇列,它是在JDK1.5時隨著J.U.C包引

Java執行(三八)—— J.U.C之collections框架:LinkedTransferQueue

一、LinkedTransferQueue簡介 LinkedTransferQueue是在JDK1.7時,J.U.C包新增的一種比較特殊的阻塞佇列,它除了具備阻塞佇列的常用功能外,還有一個比較特殊的transfer方法。 我們知道,在普通阻塞佇列中,當佇列為空時,

掌握java執行

一、執行緒的實現 繼承thread類重寫run()方法和實現Runnable介面實現run()方法 注意點:1、new一個執行緒例項時建議都要加個執行緒名方便監控和排查問題; 如new Thread("thread name")或thread.setName("

執行工處理(17)——Java中的鎖(Unsafe基礎)

1. 概述 本專題在之前的文章中詳細介紹了Java中最常使用的一種鎖機制——同步鎖。但是同步鎖肯定是不適合在所有應用場景中使用的。所以從本文開始,筆者將試圖通過兩到三篇文章的篇幅向讀者介紹Java中鎖的分類、原理和底層實現。以便大家在實際工作中根據應用場景進行

12 認識程序與執行 ()

認識程序與執行緒(python)   一段時間沒有更新部落格了,今天和大家講講關於 python 程序和執行緒的知識點。(個人心得,多多指教!) 階段一:併發與並行的深入理解 ​ 並行一定是併發,但併發不一定是並行。 ​ 並行是相對的,並行是絕對的。 1、關於並行與併發的問題引入: 問題一: 計算

執行執行佇列、執行池和協程

本節目錄: 1.執行緒佇列 2.執行緒池 3.協程   一、執行緒佇列   執行緒之間的通訊我們列表行不行呢,當然行,那麼佇列和列表有什麼區別呢?   queue佇列 :使用import queue,用法與程序Queue一樣   queue is especially useful

【.NET執行--(一)】--執行方法詳解

        上篇部落格從執行緒的基本概況開始著重討論了執行緒,程序,程式之間的區別,然後討論了執行緒操作的幾個類,並通過例項來說明了執行緒的建立方法。本篇部落格將會帶大家更深入的瞭解執行緒,介紹執行緒的基本方法,並通過一個Demo使用委託來呼叫執行緒之外的物件。

java 執行池 join用法總結:thread4.join();方法,就表明thread4.join();這個執行受到貴客待遇,直到這個執行執行完,被插入這個方法的載體執行才可以執行

那個執行緒呼叫join 舉例 thread4.join();方法,就表明thread4.join();這個執行緒受到貴客待遇,直到這個執行緒執行完,被插入這個方法的載體執行緒才可以執行。 package javajinjie.char29.threadpool; pu

前端-執行時函式

一級函式 函式是一級函式 在 JavaScript 中,函式是一級函式。這意味著,就像物件一樣,你可以像處理其他元素(如數字、字串、陣列等)一樣來處理函式。JavaScript 函式可以: 儲存在變數中 從一個函式返回 作為引數傳遞給另一個函式

線程

通道 exceptio 創建 自帶 核心內容 沒有 api locking 靜態成員變量 主題:多線程進階 多線程的一些核心內容以及要關註的細節。 一、線程的實現 繼承thread類重寫run()方法和實現Runnable接口實現run()方法 註意點:new線程

小白自學量化交易之萬礦課程筆記---股票因子策略

小白自學量化交易之萬礦課程筆記—股票多因子策略進階 函式列表 wa.score_indicators() 打分法函式(ind_ret_data,scor_method,ind_direction,ic_window)

線程之等待喚醒機制

err read spa zed bject vat stat 算法 pre /* * 線程間通訊實例 * 多個線程在處理同一資源,但是任務不同 */ //資源 class Resource { String name; String sex; }

[]-執行程序、非同步IO實用例子

在編寫爬蟲時,效能的消耗主要在IO請求中,當單程序單執行緒模式下請求URL時必然會引起等待,從而使得請求整體變慢。以下程式碼預設執行環境為python3。 目錄 一、多執行緒、多程序 1.同步執行 2.多執行緒執行 3.多執行緒+回撥函式執行 4.多程序執行 5.多程

Python(二十六)-執行實現同步的四種方式

分享一下我的偶像大神的人工智慧教程!http://blog.csdn.net/jiangjunshow 也歡迎轉載我的文章,轉載請註明出處 https://blog.csdn.net/mm2zzyzzp Python進階(二十六)-多執行緒實現同步的四種方式

JAVA(06)執行

一、三個概念 1、程式 程式(Program)是一個靜態的概念,一般對應於作業系統中的一個可執行檔案 2、程序 (1)執行中的程式叫做程序(Process),是一個動態的概念 (2)特點: 程序是程式的一次動態執行過程, 佔用特定的地址空間 每個程序由3

ArrayBlockingQueue詳解 - 執行安全性

1.介紹 ArrayBlockingQueue是一個阻塞式的佇列,繼承自AbstractBlockingQueue,間接的實現了Queue介面和Collection介面。底層以陣列的形式儲存資料(實際上可看作一個迴圈陣列)。常用的操作包括 add ,offer,put,remove,poll,t

一位10年Java程式設計師總結中的你懂執行和jvm優化嗎?

感謝朋友們的認可和指正。本文是有感而發,因為看過了太多坑人的部落格和書籍,感慨自己走過的彎路,不希望其他初學者被網上互相抄襲的部落格和東拼西湊的書籍浪費時間,想以一個相對巨集觀的視野來描述一個概念,力求通俗易懂,所以沒有深入太多細節,簡化了很多模型,給部分朋友造成了疑惑,說聲抱歉。也沒有配圖,都是抽

Java ——執行優化之執行池 ThreadPoolExecutor的核心容器阻塞佇列詳解(一)

#引言 多執行緒我想無論是後端開發,還是對於App開發者來說都不會陌生,何況Android強制要求不能在主執行緒中做網路請求,於是乎,在很多初學者或者App的原始碼中會出現會多的new Thread…的方式,這樣的程式碼是不優雅而且存在很多的隱患,假如說在使用者