什么是可中斷鎖?有什么用?怎么實現(xiàn)?
作者 | 王磊
來源 | Java中文社群(ID:javacn666)
轉(zhuǎn)載請聯(lián)系授權(quán)(微信ID:GG_Stone
在 Java 中有兩種鎖,一種是內(nèi)置鎖 synchronized,一種是顯示鎖 Lock,其中 Lock 鎖是可中斷鎖,而 synchronized 則為不可中斷鎖。
所謂的中斷鎖指的是鎖在執(zhí)行時可被中斷,也就是在執(zhí)行時可以接收 interrupt 的通知,從而中斷鎖執(zhí)行。
PS:默認情況下 Lock 也是不可中斷鎖,但是可以通過特殊的“手段”,可以讓其變?yōu)榭芍袛噫i,接下來我們一起來看。
為什么需要可中斷鎖?
不可中斷鎖的問題是,當出現(xiàn)“異常”時,只能一直阻塞等待,別無其他辦法,比如下面這個程序。下面的這個程序中有兩個線程,其中線程 1 先獲取到鎖資源執(zhí)行相應(yīng)代碼,而線程 2 在 0.5s 之后開始嘗試獲取鎖資源,但線程 1 執(zhí)行時忘記釋放鎖了,這就造成線程 2 一直阻塞等待的情況,實現(xiàn)代碼如下:
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- publicclass InterruptiblyExample {
- public static void main(String[] args) {
- Lock lock = new ReentrantLock();
- // 創(chuàng)建線程 1
- Thread t1 = new Thread(new Runnable() {
- @Override
- public void run() {
- lock.lock();
- System.out.println("線程 1:獲取到鎖.");
- // 線程 1 未釋放鎖
- }
- });
- t1.start();
- // 創(chuàng)建線程 2
- Thread t2 = new Thread(new Runnable() {
- @Override
- public void run() {
- // 先休眠 0.5s,讓線程 1 先執(zhí)行
- 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();
- }
- }
以上代碼執(zhí)行的結(jié)果如下:
從上述結(jié)果可以看出,此時線程 2 在等待獲取鎖的操作,然而經(jīng)歷了 N 久之后...再次查看結(jié)果,依然是熟悉的畫面:
線程 2 還在阻塞等待獲取線程 1 釋放鎖資源,此時的線程 2 除了等之外,并無其他方法。
并且,但我們熟練的拿出了 JConsole,試圖得到一個死鎖的具體信息時,卻得到了這樣的結(jié)果:
并沒有檢測到任何死鎖信息,從上圖我們可以看出,當只有一個鎖資源的時候,系統(tǒng)并不會把這種情況判定為死鎖,當然也沒有阻塞等待的具體信息嘍,此時只剩下線程 2 孤單地等待著它的“鎖兒”。
使用中斷鎖
然而,中斷鎖的出現(xiàn),就可以打破這一僵局,它可以在等待一定時間之后,主動的中斷線程 2,以解決線程阻塞等待的問題。
中斷鎖的核心實現(xiàn)代碼是 lock.lockInterruptibly() 方法,它和 lock.lock() 方法作用類似,只不過使用 lockInterruptibly 方法可以優(yōu)先接收中斷的請求,中斷鎖的具體實現(xiàn)如下:
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- publicclass InterruptiblyExample {
- public static void main(String[] args) throws InterruptedException {
- Lock lock = new ReentrantLock();
- // 創(chuàng)建線程 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();
- // 創(chuàng)建線程 2
- Thread t2 = new Thread(new Runnable() {
- @Override
- public void run() {
- // 先休眠 0.5s,讓線程 1 先執(zhí)行
- 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:執(zhí)行已被中斷.");
- }
- }
- });
- t2.start();
- // 等待 2s 后,終止線程 2
- Thread.sleep(2000);
- if (t2.isAlive()) { // 線程 2 還在執(zhí)行
- System.out.println("執(zhí)行線程的中斷.");
- t2.interrupt();
- } else {
- System.out.println("線程 2:執(zhí)行完成.");
- }
- }
- }
以上代碼執(zhí)行結(jié)果如下:
從上述結(jié)果可以看出,當我們使用了 lockInterruptibly 方法就可以在一段時間之后,判斷它是否還在阻塞等待,如果結(jié)果為真,就可以直接將他中斷,如上圖效果所示。
但當我們嘗試將 lockInterruptibly 方法換成 lock 方法之后(其他代碼都不變),執(zhí)行的結(jié)果就完全不一樣了,實現(xiàn)代碼如下:
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- publicclass InterruptiblyExample {
- public static void main(String[] args) throws InterruptedException {
- Lock lock = new ReentrantLock();
- // 創(chuàng)建線程 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();
- // 創(chuàng)建線程 2
- Thread t2 = new Thread(new Runnable() {
- @Override
- public void run() {
- // 先休眠 0.5s,讓線程 1 先執(zhí)行
- 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:執(zhí)行已被中斷.");
- }
- }
- });
- t2.start();
- // 等待 2s 后,終止線程 2
- Thread.sleep(2000);
- if (t2.isAlive()) { // 線程 2 還在執(zhí)行
- System.out.println("執(zhí)行線程的中斷.");
- t2.interrupt();
- } else {
- System.out.println("線程 2:執(zhí)行完成.");
- }
- }
- }
以上程序執(zhí)行結(jié)果如下:
從上圖可以看出,當使用 lock 方法時,即使調(diào)用了 interrupt 方法依然不能將線程 2 進行中斷。
總結(jié)
本文介紹了中斷鎖的實現(xiàn),通過顯示鎖 Lock 的 lockInterruptibly 方法來完成,它和 lock 方法作用類似,但 lockInterruptibly 可以優(yōu)先接收到中斷的通知,而 lock 方法只能“死等”鎖資源的釋放,同時這兩個方法的區(qū)別也是常見的面試題,希望本文對你有用。