什麼是可中斷鎖?有什麼用?怎麼實現?

在 Java 中有兩種鎖,一種是內置鎖 synchronized,一種是顯示鎖 Lock,其中 Lock 鎖是可中斷鎖,而 synchronized 則爲不可中斷鎖。

所謂的中斷鎖指的是鎖在執行時可被中斷,也就是在執行時可以接收 interrupt 的通知,從而中斷鎖執行

PS:默認情況下 Lock 也是不可中斷鎖,但是可以通過特殊的“手段”,可以讓其變爲可中斷鎖,接下來我們一起來看。

爲什麼需要可中斷鎖?

不可中斷鎖的問題是,當出現“異常”時,只能一直阻塞等待,別無其他辦法,比如下面這個程序。下面的這個程序中有兩個線程,其中線程 1 先獲取到鎖資源執行相應代碼,而線程 2 在 0.5s 之後開始嘗試獲取鎖資源,但線程 1 執行時忘記釋放鎖了,這就造成線程 2 一直阻塞等待的情況,實現代碼如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class InterruptiblyExample {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();

        // 創建線程 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("線程 1:獲取到鎖.");
                // 線程 1 未釋放鎖
            }
        });
        t1.start();

        // 創建線程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 先休眠 0.5s,讓線程 1 先執行
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 獲取鎖
                System.out.println("線程 2:等待獲取鎖.");
                lock.lock();
                try {
                    System.out.println("線程 2:獲取鎖成功.");
                } finally {
                    lock.unlock();
                }
            }
        });
        t2.start();
    }
}

以上代碼執行的結果如下:
image.png
從上述結果可以看出,此時線程 2 在等待獲取鎖的操作,然而經歷了 N 久之後...
image.png
再次查看結果,依然是熟悉的畫面:
image.png
線程 2 還在阻塞等待獲取線程 1 釋放鎖資源,此時的線程 2 除了等之外,並無其他方法。

並且,但我們熟練的拿出了 JConsole,試圖得到一個死鎖的具體信息時,卻得到了這樣的結果:
image.png
並沒有檢測到任何死鎖信息,從上圖我們可以看出,當只有一個鎖資源的時候,系統並不會把這種情況判定爲死鎖,當然也沒有阻塞等待的具體信息嘍,此時只剩下線程 2 孤單地等待着它的“鎖兒”。

使用中斷鎖

然而,中斷鎖的出現,就可以打破這一僵局,它可以在等待一定時間之後,主動的中斷線程 2,以解決線程阻塞等待的問題。

中斷鎖的核心實現代碼是 lock.lockInterruptibly() 方法,它和 lock.lock() 方法作用類似,只不過使用 lockInterruptibly 方法可以優先接收中斷的請求,中斷鎖的具體實現如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class InterruptiblyExample {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();

        // 創建線程 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 加鎖操作
                    lock.lock();
                    System.out.println("線程 1:獲取到鎖.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 線程 1 未釋放鎖
            }
        });
        t1.start();

        // 創建線程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 先休眠 0.5s,讓線程 1 先執行
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 獲取鎖
                try {
                    System.out.println("線程 2:嘗試獲取鎖.");
                    lock.lockInterruptibly(); // 可中斷鎖
                    System.out.println("線程 2:獲取鎖成功.");
                } catch (InterruptedException e) {
                    System.out.println("線程 2:執行已被中斷.");
                }
            }
        });
        t2.start();

        // 等待 2s 後,終止線程 2
        Thread.sleep(2000);
        if (t2.isAlive()) { // 線程 2 還在執行
            System.out.println("執行線程的中斷.");
            t2.interrupt();
        } else {
            System.out.println("線程 2:執行完成.");
        }
    }
}

以上代碼執行結果如下:
image.png
從上述結果可以看出,當我們使用了 lockInterruptibly 方法就可以在一段時間之後,判斷它是否還在阻塞等待,如果結果爲真,就可以直接將他中斷,如上圖效果所示。

但當我們嘗試將 lockInterruptibly 方法換成 lock 方法之後(其他代碼都不變),執行的結果就完全不一樣了,實現代碼如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class InterruptiblyExample {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();

        // 創建線程 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 加鎖操作
                    lock.lockInterruptibly();
                    System.out.println("線程 1:獲取到鎖.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 線程 1 未釋放鎖
            }
        });
        t1.start();

        // 創建線程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 先休眠 0.5s,讓線程 1 先執行
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 獲取鎖
                try {
                    System.out.println("線程 2:嘗試獲取鎖.");
                    lock.lock();
                    System.out.println("線程 2:獲取鎖成功.");
                } catch (Exception e) {
                    System.out.println("線程 2:執行已被中斷.");
                }
            }
        });
        t2.start();

        // 等待 2s 後,終止線程 2
        Thread.sleep(2000);
        if (t2.isAlive()) { // 線程 2 還在執行
            System.out.println("執行線程的中斷.");
            t2.interrupt();
        } else {
            System.out.println("線程 2:執行完成.");
        }
    }
}

以上程序執行結果如下:
image.png
從上圖可以看出,當使用 lock 方法時,即使調用了 interrupt 方法依然不能將線程 2 進行中斷。

總結

本文介紹了中斷鎖的實現,通過顯示鎖 Lock 的 lockInterruptibly 方法來完成,它和 lock 方法作用類似,但 lockInterruptibly 可以優先接收到中斷的通知,而 lock 方法只能“死等”鎖資源的釋放,同時這兩個方法的區別也是常見的面試題,希望本文對你有用。

併發原創文章推薦

  1. 線程的 4 種創建方法和使用詳解!
  2. Java中用戶線程和守護線程區別這麼大?
  3. 深入理解線程池 ThreadPool
  4. 線程池的7種創建方式,強烈推薦你用它...
  5. 池化技術到達有多牛?看了線程和線程池的對比嚇我一跳!
  6. 併發中的線程同步與鎖
  7. synchronized 加鎖 this 和 class 的區別!
  8. volatile 和 synchronized 的區別
  9. 輕量級鎖一定比重量級鎖快嗎?
  10. 這樣終止線程,竟然會導致服務宕機?
  11. SimpleDateFormat線程不安全的5種解決方案!
  12. ThreadLocal不好用?那是你沒用對!
  13. ThreadLocal內存溢出代碼演示和原因分析!
  14. Semaphore自白:限流器用我就對了!
  15. CountDownLatch:別浪,等人齊再團!
  16. CyclicBarrier:人齊了,司機就可以發車了!
  17. synchronized 優化手段之鎖膨脹機制!
  18. synchronized 中的 4 個優化,你知道幾個?
  19. ReentrantLock 中的 4 個坑!
  20. 圖解:爲什麼非公平鎖的性能更高?
  21. 死鎖的 4 種排查工具!
  22. 死鎖終結者:順序鎖和輪詢鎖!
  23. 輪詢鎖在使用時遇到的問題與解決方案!

關注公號「Java中文社羣」查看更多有意思、漲知識的 Java 併發文章。

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