Synchronized之三:Synchronized與線程中斷、線程wait


線程中斷

見《Thread之八:interrupt中斷

 

正如中斷二字所表達的意義,在線程運行(run方法)中間打斷它,在Java中,提供了以下3個有關線程中斷的方法

//中斷線程(實例方法)
public void Thread.interrupt();

//判斷線程是否被中斷(實例方法)
public boolean Thread.isInterrupted();

//判斷是否被中斷並清除當前中斷狀態(靜態方法)
public static boolean Thread.interrupted();

當一個線程處於被阻塞狀態或者試圖執行一個阻塞操作時,使用Thread.interrupt()方式中斷該線程,注意此時將會拋出一個InterruptedException的異常,同時中斷狀態將會被復位(由中斷狀態改為非中斷狀態),如下代碼將演示該過程:

package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class InterruputSleepThread3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                // while在try中,通過異常中斷就可以退出run循環
                try {
                    while (true) {
                        // 當前線程處於阻塞狀態,異常必須捕捉處理,無法往外拋出
                        TimeUnit.SECONDS.sleep(2);
                    }
                } catch (InterruptedException e) {
                    System.out.println("Interruted When Sleep");
                    boolean interrupt = this.isInterrupted();
                    // 中斷狀態被復位
                    System.out.println("interrupt:" + interrupt);
                }
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        // 中斷處於阻塞狀態的線程
        t1.interrupt();
    }
}

結果:

Interruted When Sleep
interrupt:false

如上述代碼所示,我們創建一個線程,並在線程中調用了sleep方法從而使用線程進入阻塞狀態,啟動線程后,調用線程實例對象的interrupt方法中斷阻塞狀態,並拋出InterruptedException異常,此時中斷狀態也將被復位。這里有些人可能會詫異,為什么不用Thread.sleep(2000);而是用TimeUnit.SECONDS.sleep(2);其實原因很簡單,前者使用時並沒有明確的單位說明,而后者非常明確表達秒的單位,事實上后者的內部實現最終還是調用了Thread.sleep(2000);,但為了編寫的代碼語義更清晰,建議使用TimeUnit.SECONDS.sleep(2);的方式,注意TimeUnit是個枚舉類型。

除了阻塞中斷的情景,我們還可能會遇到處於運行期且非阻塞的狀態的線程,這種情況下,直接調用Thread.interrupt()中斷線程是不會得到任響應的,如下代碼,將無法中斷非阻塞狀態下的線程:

package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("未被中斷");
                }
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();

    }
}

結果:

未被中斷
未被中斷
未被中斷
...

雖然我們調用了interrupt方法,但線程t1並未被中斷,因為處於非阻塞狀態的線程需要我們手動進行中斷檢測並結束程序,改進后代碼如下:

package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    // 判斷當前線程是否被中斷
                    if (this.isInterrupted()) {
                        System.out.println("線程中斷");
                        break;
                    }
                }

                System.out.println("已跳出循環,線程中斷!");
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();
    }
}

結果:

線程中斷
已跳出循環,線程中斷!

是的,我們在代碼中使用了實例方法isInterrupted判斷線程是否已被中斷,如果被中斷將跳出循環以此結束線程。綜合所述,可以簡單總結一下中斷兩種情況:

  • 一種是當線程處於阻塞狀態或者試圖執行一個阻塞操作時,我們可以使用實例方法interrupt()進行線程中斷,執行中斷操作后將會拋出interruptException異常(該異常必須捕捉無法向外拋出)並將中斷狀態復位;
  • 另外一種是當線程處於運行狀態時,我們也可調用實例方法interrupt()進行線程中斷,但同時必須手動判斷中斷狀態,並編寫中斷線程的代碼(其實就是結束run方法體的代碼)。有時我們在編碼時可能需要兼顧以上兩種情況,那么就可以如下編寫:
    package com.dxz.synchronize;
    
    import java.util.concurrent.TimeUnit;
    
    public class InterruputThread3 {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    try {
                        // 判斷當前線程是否已中斷,注意interrupted方法是靜態的,執行后會對中斷狀態進行復位
                        while (!Thread.interrupted()) {
                            System.out.println("服務中...");
                            TimeUnit.SECONDS.sleep(2);
                        }
                    } catch (InterruptedException e) {
                        System.out.println("中斷");
                        // 中斷狀態被復位
                        System.out.println("interrupt:" + this.isInterrupted());
                    }
                }
            };
            t1.start();
            TimeUnit.SECONDS.sleep(2);
            t1.interrupt();
        }
    }

結果:

服務中...
服務中...
中斷
interrupt:false

 

中斷與synchronized

事實上線程的中斷操作對於正在等待獲取的鎖對象的synchronized方法或者代碼塊並不起作用,也就是對於synchronized來說,如果一個線程在等待鎖,那么結果只有兩種,要么它獲得這把鎖繼續執行,要么它就保存等待,即使調用中斷線程的方法,也不會生效。演示代碼如下

package com.dxz.synchronize;

import java.util.concurrent.TimeUnit;

public class SynchronizedBlocked implements Runnable {

    public synchronized void f() {
        System.out.println("Trying to call f()");
        while (true) // Never releases lock
            Thread.yield();
    }

    /**
     * 在構造器中創建新線程並啟動獲取對象鎖
     */
    public SynchronizedBlocked() {
        // 該線程已持有當前實例鎖
        new Thread() {
            public void run() {
                f(); // Lock acquired by this thread
            }
        }.start();
    }

    public void run() {
        // 中斷判斷
        while (true) {
            if (Thread.interrupted()) {
                System.out.println("中斷線程!!");
                break;
            } else {
                f();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedBlocked sync = new SynchronizedBlocked();
        Thread t = new Thread(sync);
        // 啟動后調用f()方法,無法獲取當前實例鎖處於等待狀態
        t.start();
        TimeUnit.SECONDS.sleep(1);
        // 中斷線程,無法生效
        t.interrupt();
    }
}

結果:

Trying to call f()

我們在SynchronizedBlocked構造函數中創建一個新線程並啟動獲取調用f()獲取到當前實例鎖,由於SynchronizedBlocked自身也是線程,啟動后在其run方法中也調用了f(),但由於對象鎖被其他線程占用,導致t線程只能等到鎖,此時我們調用了t.interrupt();但並不能中斷線程。

等待喚醒機制與synchronized

所謂等待喚醒機制本篇主要指的是notify/notifyAll和wait方法,在使用這3個方法時,必須處於synchronized代碼塊或者synchronized方法中,否則就會拋出IllegalMonitorStateException異常,這是因為調用這幾個方法前必須拿到當前對象的監視器monitor對象,也就是說notify/notifyAll和wait方法依賴於monitor對象,在前面的分析中,我們知道monitor 存在於對象頭的Mark Word 中(存儲monitor引用指針),而synchronized關鍵字可以獲取 monitor ,這也就是為什么notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調用的原因。

synchronized (obj) {
       obj.wait();
       obj.notify();
       obj.notifyAll();         
 }

 

需要特別理解的一點是,與sleep方法不同的是wait方法調用完成后,線程將被暫停,但wait方法將會釋放當前持有的監視器鎖(monitor),直到有線程調用notify/notifyAll方法后方能繼續執行,而sleep方法只讓線程休眠並不釋放鎖。同時notify/notifyAll方法調用后,並不會馬上釋放監視器鎖,而是在相應的synchronized(){}/synchronized方法執行結束后才自動釋放鎖。

 

轉自:

http://blog.csdn.net/javazejian/article/details/72828483?locationNum=5&fps=1


免責聲明!

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



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