Java深入學習11:Lock鎖詳解
一、Lock鎖是什么
java.util.concurrent.locks包下常用的類與接口(lock是jdk 1.5后新增的)
Lock 接口支持那些語義不同(重入、公平等)的鎖規則,可以在非阻塞式結構的上下文(包括 hand-over-hand 和鎖重排算法)中使用這些規則。主要的實現是 ReentrantLock。
Lock 實現提供了比 synchronized 關鍵字 更廣泛的鎖操作,它能以更優雅的方式處理線程同步問題。也就是說,Lock提供了比synchronized更多的功能。
二、代碼分析
1-Lock類方法說明
public interface Lock { //獲取鎖方式1:最常用的方式。如果當前鎖不可用,當前線程無法調度並進入休眠狀態直到獲取到鎖 void lock(); //獲取鎖方式2:獲取鎖(除非當前線程被中斷)。如果當前鎖不可用,當前線程無法調度並進入休眠狀態直到當前線程獲取到鎖或者其它線程中斷了當前的線程。注意,當一個線程獲取了鎖之后,是不會被interrupt()方法中斷的。因為調用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程。 void lockInterruptibly() throws InterruptedException; //獲取鎖方式3:獲取鎖(僅當鎖在調用時處於空閑狀態時才獲取鎖)。如果成功獲取鎖,返回true,否則,返回false;無論如何都會立即返回。在拿不到鎖時不會一直在那等待。 boolean tryLock(); //獲取鎖方式4:獲取鎖(在規定的等待時間內且線程沒有被中斷,如果鎖處於空閑狀態時則獲取鎖)。如果成功獲取鎖,返回true,否則,返回false; //如果當前鎖不可用,當前線程無法調度並進入休眠狀態直到(1)當前線程獲取到鎖(2)當前線程被其它線程中中斷(3)等待時間結束 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //釋放鎖 void unlock(); //返回綁定到此 Lock 實例的新 Condition 實例 Condition newCondition(); }
2-Lock鎖四種方法的使用方法(ReentrantLock是 Lock 的實現類)
2-1- lock()
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockTest { public static void main(String[] args) { LockDemo lockDemo = new LockDemo(); new Thread(lockDemo,"thread 1").start(); new Thread(lockDemo,"thread 2").start(); new Thread(lockDemo,"thread 3").start(); } } class LockDemo implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { //上鎖 lock.lock(); try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" get the lock"); } catch (Exception e) { e.printStackTrace(); } finally { //釋放鎖 System.out.println(Thread.currentThread().getName()+" realse the lock"); lock.unlock(); } } } -----------------------------日志輸出---------------------------- thread 1 get the lock thread 1 realse the lock thread 2 get the lock thread 2 realse the lock thread 3 get the lock thread 3 realse the lock
2-2-tryLock()
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TryLockTest { public static void main(String[] args) { TryLockDemo lockDemo = new TryLockDemo(); new Thread(lockDemo,"thread 1").start(); new Thread(lockDemo,"thread 2").start(); new Thread(lockDemo,"thread 3").start(); } } class TryLockDemo implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { //上鎖 if (lock.tryLock()) { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" get lock success"); } catch (Exception e) { e.printStackTrace(); } finally { //釋放鎖 System.out.println(Thread.currentThread().getName()+" realse the lock"); lock.unlock(); } }else{ System.out.println(Thread.currentThread().getName() +" get lock fail"); } } } -------------------------------日志-------------------------------------- thread 2 get lock fail thread 3 get lock fail thread 1 get lock success thread 1 realse the lock
2-3- tryLock(long time, TimeUnit unit)
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TryLockParamTest { public static void main(String[] args) { TryLockParamDemo lockDemo = new TryLockParamDemo(); new Thread(lockDemo,"thread 1").start(); new Thread(lockDemo,"thread 2").start(); new Thread(lockDemo,"thread 3").start(); } } class TryLockParamDemo implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { try { //上鎖 if (lock.tryLock(3L,TimeUnit.SECONDS)) { try { Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+" get lock success"); } finally { //釋放鎖 System.out.println(Thread.currentThread().getName()+" realse the lock"); lock.unlock(); } }else{ System.out.println(Thread.currentThread().getName() +" get lock fail"); } } catch (InterruptedException e) { e.printStackTrace(); } } } --------------------------------日志-------------------------------------- thread 1 get lock success thread 1 realse the lock thread 3 get lock fail thread 2 get lock success thread 2 realse the lock
2-4- lockInterruptibly()
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockInterruptiblyTest { public static void main(String[] args) { //1-開啟第一個線程,期間會sleep 1秒 LockInterruptiblyDemo lockDemo = new LockInterruptiblyDemo(); new Thread(lockDemo,"thread 1").start(); //2-開啟第二個線程,需要等待第一個線程釋放鎖,才能進入 Thread thread2 = new Thread(lockDemo, "thread 2"); thread2.start(); //3-開啟第三個線程,用於中斷第二個線程 LockInterruptDemo lockInterruptDemo = new LockInterruptDemo(thread2); new Thread(lockInterruptDemo,"thread 3").start(); //預計結果,第一個線程成功獲取鎖並釋放鎖,但第二個線程會被中斷無法成功獲取鎖 } } //測試線程target class LockInterruptiblyDemo implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { try { lock.lockInterruptibly(); try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+ " get lock success"); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName()+ " release lock success"); lock.unlock(); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " has been interrupted and get lock unsuccessfully"); } } } //用於中斷其它線程的線程taget class LockInterruptDemo implements Runnable{ private Thread thread; public LockInterruptDemo(Thread thread) { this.thread = thread; } @Override public void run() { //中斷目標線程 thread.interrupt(); System.out.println(Thread.currentThread().getName() +" run success "); System.out.println(Thread.currentThread().getName() +" has interrupted " + thread.getName() +"successfully"); } } ------------------------------------日志----------------------------------- thread 3 run success thread 2 has been interrupted and get lock unsuccessfully thread 3 has interrupted thread 2successfully thread 1 get lock success thread 1 release lock success
3- Lock和synchronized對比
1)synchronized是Java語言的關鍵字,因此是內置特性,Lock不是Java語言內置的,Lock是一個接口,通過實現類可以實現同步訪問。
2)synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在代碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過代碼實現的,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中
3)在資源競爭不是很激烈的情況下,Synchronized的性能要優於ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常態。
三、ReadWriteLock接口
1- ReadWriteLock接口簡介
A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing.The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.
ReadWriteLock提供了一對關聯鎖——只讀鎖和寫入鎖。只要沒有寫入操作,只讀鎖可以同時被多個線程使用;但是寫入鎖是唯一排他的
2- ReadWriteLock接口源碼
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing */ Lock writeLock(); }
3- ReadWriteLock實現類是ReentrantReadWriteLock。其內容和ReentrantLock相似
4-使用示例代碼
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockTest { public static void main(String[] args) { ReadWriteLockDemo th = new ReadWriteLockDemo(); //write new Thread(new Runnable() { @Override public void run() { th.set((int)(Math.random()*100)); } }).start(); //read for(int i=1; i<10; i++){ new Thread(new Runnable() { @Override public void run() { th.get(); } }).start(); } } } class ReadWriteLockDemo{ private int number = 0; private ReadWriteLock lock = new ReentrantReadWriteLock(); //write public void get(){ //獲取只讀鎖 lock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + ": " + number); } finally { //釋放只讀鎖 lock.readLock().unlock(); } } //read public void set(int number){ //獲取寫入鎖 lock.writeLock().lock(); try { this.number = number; System.out.println(Thread.currentThread().getName() + " write: " +this.number); } finally { //釋放寫入鎖 lock.writeLock().unlock(); } } } -------------------------------日志------------------------------------------------ Thread-0 write: 6 Thread-1: 6 Thread-2: 6 Thread-4: 6 Thread-5: 6 Thread-3: 6 Thread-7: 6 Thread-6: 6 Thread-8: 6 Thread-9: 6
四、幾個概念
1- 可重入鎖
如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明了鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。舉個簡單的例子,當一個線程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。
2- 可中斷鎖
可中斷鎖:顧名思義,就是可以相應中斷的鎖。
在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
如果某一線程A正在執行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由於等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。
在前面演示lockInterruptibly()的用法時已經體現了Lock的可中斷性。
3- 公平鎖
公平鎖即盡量以請求鎖的順序來獲取鎖。比如同是有多個線程在等待一個鎖,當這個鎖被釋放時,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖。
非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進行的。這樣就可能導致某個或者一些線程永遠獲取不到鎖。
在Java中,synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。
而對於ReentrantLock和ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置為公平鎖。
4- interrupt相關
interrupt()方法是用於中斷線程的,調用該方法的線程的狀態將被置為"中斷"狀態。注意:線程中斷僅僅是設置線程的中斷狀態位,不會停止線程。所以當一個線程處於中斷狀態時,如果再由wait、sleep以及jion三個方法引起的阻塞,那么JVM會將線程的中斷標志重新設置為false,並拋出一個InterruptedException異常,然后開發人員可以中斷狀態位“的本質作用-----就是程序員根據try-catch功能塊捕捉jvm拋出的InterruptedException異常來做各種處理,比如如何退出線程。總之interrupt的作用就是需要用戶自己去監視線程的狀態位並做處理。”
END