接下來介紹比synchronized功能上更豐富的關鍵字:重入鎖
-
靈活性:
public class ReentrantLockTest implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int flag = 0; @Override public void run() { for (int i = 0; i < 10000000; i++) { lock.lock(); try { flag++; } finally { lock.unlock(); } } } public static void main(String args[]) throws InterruptedException { ReentrantLockTest test = new ReentrantLockTest(); Thread first = new Thread(test); Thread second = new Thread(test); first.start(); second.start(); first.join(); second.join(); System.out.println(flag); } } 複製程式碼
在
lock.lock();
這裡,通過重入鎖保護臨界區安全,以免發生執行緒安全問題。在
lock.unlock();
這裡,必須手動指示釋放鎖的操作,否則其他執行緒將無法獲得。在這段程式碼裡,我們能見到重入鎖靈活的特點。但為什麼叫“重入”呢?
看下段程式碼:
@Override public void run() { for (int i = 0; i < 10000000; i++) { lock.lock(); lock.lock(); try { flag++; } finally { lock.unlock(); lock.unlock(); } } } 複製程式碼
因為該鎖能反覆進進出出。但要注意一下:
在上段程式碼中,鎖是可以重複獲取的。如果不允許,則該執行緒在第二次獲取鎖時會和自己產生死鎖問題。同時也要注意,執行緒獲取多少次鎖就要釋放多少此鎖。當獲取鎖的次數大於釋放鎖的次數、相當於該執行緒還持有鎖。當獲取鎖的次數少於釋放鎖的次數、則會得到一個
java.lang.IllegalMonitorStateException
異常。 -
中斷響應:
二話不說貼程式碼:
public class ReentrantLockTest implements Runnable{ public static ReentrantLock producer = new ReentrantLock(); public static ReentrantLock consumer = new ReentrantLock(); public int flag = 0; public ReentrantLockTest(int flag){ this.flag = flag; } @Override public void run() { try { if (flag == 0) { producer.lockInterruptibly(); try { Thread.sleep(500); } catch (InterruptedException e) { } consumer.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + "完成工作"); } else { consumer.lockInterruptibly(); try { Thread.sleep(500); } catch (InterruptedException e) { } producer.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + "完成工作"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (producer.isHeldByCurrentThread()) { producer.unlock(); } if (consumer.isHeldByCurrentThread()) { consumer.unlock(); } System.out.println(Thread.currentThread().getName() + ": 執行緒退出"); } } public static void main(String args[]) throws InterruptedException { ReentrantLockTest producerThread = new ReentrantLockTest(1); ReentrantLockTest consumerThread = new ReentrantLockTest(0); Thread first = new Thread(producerThread); Thread second = new Thread(consumerThread); first.setName("producer"); second.setName("consumer"); first.start(); second.start(); Thread.sleep(1000); second.interrupt(); } } 複製程式碼
這是一段容易造成死鎖的程式碼,具體原因大家應該懂。當執行到
second.interrupt();
時,second執行緒在等待鎖時被中斷,故second執行緒會放棄對鎖的申請、並對已持有資源進行釋放。first執行緒則能夠正常獲取所等待的鎖並繼續執行下去。結果如下:
producer完成工作 java.lang.InterruptedException consumer: 執行緒退出 producer: 執行緒退出 at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at blog.ReentrantLockTest.run(ReentrantLockTest.java:22) at java.lang.Thread.run(Thread.java:748) 複製程式碼
真正完成工作的只有producer執行緒。
-
限時等待:
除了用中斷避免死鎖問題外,還可以用限時等待鎖來避免。限時等待鎖有點像是系統自動完成執行緒中斷的感覺。先展示下限時等待鎖的使用:
public class showWait implements Runnable { public static ReentrantLock lock = new ReentrantLock(); @Override public void run() { try { if (lock.tryLock(5, TimeUnit.SECONDS)) { Thread.sleep(6000); } else { System.out.println(Thread.currentThread().getName() + " get lock failed"); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String args[]) { showWait test = new showWait(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("producer"); t2.setName("consumer"); t1.start(); t2.start(); } } 複製程式碼
上述程式碼展示了
lock.tryLock(5,TimeUnit.SECONDS);
的使用,在這裡,該方法接收兩個引數,分別是時長和計時單位。該方法也可以不帶引數,當不帶引數時,當前執行緒會嘗試獲取鎖,如果鎖未被其他執行緒佔有則會申請成功並立即返回true。如果鎖被其他執行緒佔用則立即返回false。這種方法不會引起執行緒等待,所以不會產生死鎖問題。
-
公平鎖:
在多大數情況下,鎖的申請都是非公平性的,有時會造成執行緒飢餓問題。當我們使用synchronized時產生的鎖是非公平性的,但我們使用ReentrantLock時可以通過建構函式進行指定其公平性。
public ReentrantLock(boolean fair)
當引數為true時為公平鎖,預設為非公平鎖。公平鎖看起來挺優美的,但其必然要維護一個等待佇列,其效能必然會降低
-
整理:
- lock()
- lockInterruptibly()
- tryLock()
- tryLock(long time,TimeUnit unit)
- unlock()
大家回顧下這幾個方法吧。