在JDK5.0版本之前,重入鎖的性能遠遠好於synchronized關鍵字,JDK6.0版本之後synchronized 得到了大量的優化,二者性能也不分伯仲,但是重入鎖是可以完全替代synchronized關鍵字的。除此之外,重入鎖又自帶一系列高逼格UBFF:可中斷響應、鎖申請等待限時、公平鎖。另外可以結合Condition來使用,使其更是逼格滿滿。
先來盤花生米:
package somhu;import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockTest implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; @Override public void run() { for (int j = 0; j < 10000; j++) { lock.lock(); // 看這裏就可以 //lock.lock(); ① try { i++; } finally { lock.unlock(); // 看這裏就可以 //lock.unlock();② } } } public static void main(String[] args) throws InterruptedException { ReentrantLockTest test = new ReentrantLockTest(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.start();t2.start(); t1.join(); t2.join(); // main線程會等待t1和t2都運行完再執行以後的流程 System.err.println(i); } }
從上可以看出,使用重入鎖進行加鎖是一種顯式操作,通過何時加鎖與釋放鎖使重入鎖對邏輯控制的靈活性遠遠大於synchronized關鍵字。同時,需要注意,有加鎖就必須有釋放鎖,而且加鎖與釋放鎖的分數要相同,這裏就引出了“重”字的概念,如上邊代碼演示,放開①、②處的註釋,與原來效果一致。
硬菜來了:
1、中斷響應
對於synchronized塊來說,要麼獲取到鎖執行,要麼持續等待。而重入鎖的中斷響應功能就合理地避免了這樣的情況。比如,一個正在等待獲取鎖的線程被“告知”無須繼續等待下去,就可以停止工作了。直接上代碼,來演示使用重入鎖如何解決死鎖:12
package somhu;import java.util.concurrent.locks.ReentrantLock;public class KillDeadlock implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public KillDeadlock(int lock) { this.lock = lock; } @Override public void run() { try { if (lock == 1) { lock1.lockInterruptibly(); // 以可以響應中斷的方式加鎖 try { Thread.sleep(500); } catch (InterruptedException e) {} lock2.lockInterruptibly(); } else { lock2.lockInterruptibly(); // 以可以響應中斷的方式加鎖 try { Thread.sleep(500); } catch (InterruptedException e) {} lock1.lockInterruptibly(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) lock1.unlock(); // 注意判斷方式 if (lock2.isHeldByCurrentThread()) lock2.unlock(); System.err.println(Thread.currentThread().getId() + "退出!"); } } public static void main(String[] args) throws InterruptedException { KillDeadlock deadLock1 = new KillDeadlock(1); KillDeadlock deadLock2 = new KillDeadlock(2); Thread t1 = new Thread(deadLock1); Thread t2 = new Thread(deadLock2); t1.start();t2.start(); Thread.sleep(1000); t2.interrupt(); // ③ } }
t1、t2線程開始運行時,會分別持有lock1和lock2而請求lock2和lock1,這樣就發生了死鎖。但是,在③處給t2線程狀態標記爲中斷後,持有重入鎖lock2的線程t2會響應中斷,並不再繼續等待lock1,同時釋放了其原本持有的lock2,這樣t1獲取到了lock2,正常執行完成。t2也會退出,但只是釋放了資源並沒有完成工作。
2、鎖申請等待限時
可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法進行一次限時的鎖等待。
前者不帶參數,這時線程嘗試獲取鎖,如果獲取到鎖則繼續執行,如果鎖被其他線程持有,則立即返回 false ,也就是不會使當前線程等待,所以不會產生死鎖。
後者帶有參數,表示在指定時長內獲取到鎖則繼續執行,如果等待指定時長後還沒有獲取到鎖則返回false。
上代碼:
package somhu;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;public class TryLockTest implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); @Override public void run() { try { if (lock.tryLock(1, TimeUnit.SECONDS)) { // 等待1秒 Thread.sleep(2000); //休眠2秒 } else { System.err.println(Thread.currentThread().getName() + "獲取鎖失敗!"); } } catch (Exception e) { if (lock.isHeldByCurrentThread()) lock.unlock(); } } public static void main(String[] args) throws InterruptedException { TryLockTest test = new TryLockTest(); Thread t1 = new Thread(test); t1.setName("線程1"); Thread t2 = new Thread(test); t1.setName("線程2"); t1.start();t2.start(); } }/** * 運行結果: * 線程2獲取鎖失敗! */
上述示例中,t1先獲取到鎖,並休眠2秒,這時t2開始等待,等待1秒後依然沒有獲取到鎖,就不再繼續等待,符合預期結果。
3、公平鎖
所謂公平鎖,就是按照時間先後順序,使先等待的線程先得到鎖,而且,公平鎖不會產生飢餓鎖,也就是隻要排隊等待,最終能等待到獲取鎖的機會。使用重入鎖(默認是非公平鎖)創建公平鎖:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }123
上代碼:
package somhu;import java.util.concurrent.locks.ReentrantLock;public class FairLockTest implements Runnable{ public static ReentrantLock lock = new ReentrantLock(true); @Override public void run() { while (true) { try { lock.lock(); System.err.println(Thread.currentThread().getName() + "獲取到了鎖!"); } finally { lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { FairLockTest test = new FairLockTest(); Thread t1 = new Thread(test, "線程1"); Thread t2 = new Thread(test, "線程2"); t1.start();t2.start(); } }/** * 運行結果: * 線程1獲取到了鎖! * 線程2獲取到了鎖! * 線程1獲取到了鎖! * 線程2獲取到了鎖! * 線程1獲取到了鎖! * 線程2獲取到了鎖! * 線程1獲取到了鎖! * 線程2獲取到了鎖! * 線程1獲取到了鎖! * 線程2獲取到了鎖! * 線程1獲取到了鎖! * 線程2獲取到了鎖! * 線程1獲取到了鎖! * 線程2獲取到了鎖! * 線程1獲取到了鎖! * 線程2獲取到了鎖! * ......(上邊是截取的一段) */
可以發現,t1和t2交替獲取到鎖。如果是非公平鎖,會發生t1運行了許多遍後t2纔開始運行的情況。
ReentrantLock 配合 Conditond 使用
配合關鍵字synchronized使用的方法如:await()、notify()、notifyAll(),同樣配合ReentrantLock 使用的Conditon提供了以下方法:
public interface Condition { void await() throws InterruptedException; // 類似於Object.wait() void awaitUninterruptibly(); // 與await()相同,但不會再等待過程中響應中斷 long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUntil(Date deadline) throws InterruptedException; void signal(); // 類似於Obejct.notify() void signalAll(); }123456789
ReentrantLock 實現了Lock接口,可以通過該接口提供的newCondition()方法創建Condition對象:
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }12345678
上代碼:
package somhu;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockWithConditon implements Runnable{ public static ReentrantLock lock = new ReentrantLock(true); public static Condition condition = lock.newCondition(); @Override public void run() { lock.newCondition(); try { lock.lock(); System.err.println(Thread.currentThread().getName() + "-線程開始等待..."); condition.await(); System.err.println(Thread.currentThread().getName() + "-線程繼續進行了"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { ReentrantLockWithConditon test = new ReentrantLockWithConditon(); Thread t = new Thread(test, "線程ABC"); t.start(); Thread.sleep(1000); System.err.println("過了1秒後..."); lock.lock(); condition.signal(); // 調用該方法前需要獲取到創建該對象的鎖否則會產生 // java.lang.IllegalMonitorStateException異常 lock.unlock(); } }
好了,到這裏重入鎖ReentrantLock的基本使用方法就介紹完成了!