深入理解Java顯式鎖的相關知識
一、顯式鎖
什么是顯式鎖?
由自己手動獲取鎖,然后手動釋放的鎖。
有了 synchronized(內(nèi)置鎖) 為什么還要 Lock(顯示鎖)?
使用 synchronized 關鍵字實現(xiàn)了鎖功能的,使用 synchronized 關鍵字將會隱式地獲取鎖,但是它將鎖的獲取和釋放固化了,也就是先獲取再釋放。
與內(nèi)置加鎖機制不同的是,Lock 提供了一種無條件的、可輪詢的、定時的以及可中斷的鎖獲取操作,所有加鎖和解鎖的方法都是顯式的。
二、Lock的常用api
方法名稱 | 描述 |
---|---|
void lock() | 獲取鎖 |
void lockInterruptibly() throws InterruptedException | 可中斷的獲取鎖,和lock()方法的不同之處在于該方法會響應中斷,即在鎖的獲取中可以中斷當前線程 |
boolean tryLock() | 嘗試非阻塞的獲取鎖,調(diào)用該方法后立刻返回,如果能夠獲取則返回true,否則返回false |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException | 超時獲取鎖,當前線程會在以下三種情況下會返回: 1. 當前線程在超時時間內(nèi)獲得了鎖 2.當前線程在超市時間內(nèi)被中斷 3. 超時時間結束,返回false |
void unlock(); | 釋放鎖 |
三、Lock的標準用法
lock.lock(); try { // 業(yè)務邏輯 } finally { lock.unlock(); }
- 在 finally 塊中釋放鎖,目的是保證在獲取到鎖之后,最終能夠被釋放。
- 不要將獲取鎖的過程寫在 try 塊中,因為如果在獲取鎖(自定義鎖的實現(xiàn))時發(fā)生了異常,異常拋出的同時,也會導致鎖無故釋放。
四、ReentrantLock(可重入鎖)
Lock接口常用的實現(xiàn)類是 ReentrantLock。
示例代碼:主線程100000次減,子線程10萬次加。
public class ReentrantLockTest { private Lock lock = new ReentrantLock(); private int count = 0; public int getCount() { return count; } private static class ReentrantLockThread extends Thread { private ReentrantLockTest reentrantLockTest; public ReentrantLockThread(ReentrantLockTest reentrantLockTest) { this.reentrantLockTest = reentrantLockTest; } @Override public void run() { for (int i = 0; i < 100000; i++) { reentrantLockTest.incr(); } System.out.println(Thread.currentThread().getName() + " end, count = " + reentrantLockTest.getCount()); } } private void incr() { lock.lock(); try { count++; } finally { lock.unlock(); } } private void decr() { lock.lock(); try { count--; } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { ReentrantLockTest reentrantLockTest = new ReentrantLockTest(); new ReentrantLockThread(reentrantLockTest).start(); for (int i = 0; i < 100000; i++) { // 遞減100000 reentrantLockTest.decr(); } System.out.println(Thread.currentThread().getName() + " count = " + reentrantLockTest.getCount()); } }
1. 鎖的可重入性
簡單地講就是:“同一個線程對于已經(jīng)獲得到的鎖,可以多次繼續(xù)申請到該鎖的使用權”。而 synchronized 關鍵字隱式的支持重進入,比如一個 synchronized 修飾的遞歸方法,在方法執(zhí)行時,執(zhí)行線程在獲取了鎖之后仍能連續(xù)多次地獲得該鎖
同樣,ReentrantLock 在調(diào)用 lock()方法時,已經(jīng)獲取到鎖的線程,能夠再次調(diào)用lock()方法獲取鎖而不被阻塞
2. 公平鎖和非公平鎖
- 如果在時間上,先對鎖進行獲取的請求一定先被滿足,那么這個鎖是公平的,反之,是不公平的。公平的獲取鎖,也就是等待時間最長的線程最優(yōu)先獲取鎖,也可以說鎖獲取是順序的。
- ReentrantLock 提供了一個構造函數(shù),能夠控制鎖是否是公平的(缺省為不公平鎖)。事實上,公平的鎖機制往往沒有非公平的效率高。
- 在激烈競爭的情況下,非公平鎖的性能高于公平鎖的性能的一個原因是:在恢復一個被掛起的線程與該線程真正開始運行之間存在著嚴重的延遲。
- 假設線程 A 持有一個鎖,并且線程 B 請求這個鎖,由于這個鎖已被線程 A 持有,因此 B 將被掛起。當 A 釋放鎖時,B 將被喚醒,因此會再次嘗試獲取鎖。與此同時,如果 C 也請求這個鎖,那么 C 很可能會在 B 被完全喚醒之前獲得、使用以及釋放這個鎖,這樣的情況是一種“雙贏”的局面:B 獲得鎖的時刻并沒有推遲,C 更早地獲得了鎖,完成了自己的任務,然后釋放了鎖,并且吞吐量也獲得了提高。
五、ReentrantReadWriteLock(讀寫鎖)
ReentrantReadWriteLock 是 ReadWriteLock 的實現(xiàn)類。
之前提到鎖基本都是排他鎖,這些鎖在同一時刻只允許一個線程進行訪問,而讀寫鎖在同一時刻可以允許多個讀線程訪問但是在寫線程訪問時,所有的讀線程和其他寫線程均被阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖,使得并發(fā)性相比一般的排他鎖有了很大提升。
讀鎖不排斥讀鎖,但是排斥寫鎖;寫鎖即排斥讀鎖也排斥寫鎖。
private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock getLock = lock.readLock(); // 讀鎖 private final Lock setLock = lock.writeLock(); // 寫鎖
至于上鎖、解鎖與 ReentrantLock 使用方式一致。
六、Condition
- 任意一個 Java 對象,都擁有一組監(jiān)視器方法(定義在 java.lang.Object 上),主要包括 wait()、wait(long timeout)、notify()以及 notifyAll()方法,這些方法與 synchronized 同步關鍵字配合,可以實現(xiàn)等待/通知模式。
- Condition 接口也提供了類似 Object 的監(jiān)視器方法,與 Lock 配合可以實現(xiàn)等待/通知模式。
常用api
方法名稱 | 描述 |
---|---|
void await() throws InterruptedException | 使當前線程進入等待狀態(tài)直到被通知(signal)或中斷 |
void signal() | 喚醒一個等待的線程 |
void signalAll() | 喚醒所有等待的線程 |
示例代碼,主線程調(diào)用方法喚醒兩個子線程。
public class ConditionTest { private volatile boolean flag = false; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private void task1() { lock.lock(); try { try { System.out.println(Thread.currentThread().getName() + " 等待中"); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 等待結束"); System.out.println("發(fā)送郵件"); } finally { lock.unlock(); } } private void task2() { lock.lock(); try { try { System.out.println(Thread.currentThread().getName() + " 等待中"); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 等待結束"); System.out.println("發(fā)送短信"); } finally { lock.unlock(); } } private void updateFlag() { lock.lock(); try { this.flag = true; this.condition.signalAll(); }finally { lock.unlock(); } } private static class ConditionThread1 extends Thread { private ConditionTest conditionTest; public ConditionThread1(ConditionTest conditionTest) { this.conditionTest = conditionTest; } @Override public void run() { if (!conditionTest.flag) { conditionTest.task1(); } } } private static class ConditionThread2 extends Thread { private ConditionTest conditionTest; public ConditionThread2(ConditionTest conditionTest) { this.conditionTest = conditionTest; } @Override public void run() { if (!conditionTest.flag) { conditionTest.task2(); } } } public static void main(String[] args) throws InterruptedException { ConditionTest conditionTest = new ConditionTest(); new ConditionThread1(conditionTest).start(); new ConditionThread2(conditionTest).start(); Thread.sleep(1000); System.out.println("flag 改變。。。"); conditionTest.updateFlag(); } }
到此這篇關于深入理解Java顯式鎖的相關知識的文章就介紹到這了,更多相關Java顯式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot 啟動時初始化數(shù)據(jù)庫的步驟
這篇文章主要介紹了springboot 啟動時初始化數(shù)據(jù)庫的步驟,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下2021-01-01Java獲取Prometheus監(jiān)控數(shù)據(jù)的方法實現(xiàn)
本文主要介紹了Java獲取Prometheus監(jiān)控數(shù)據(jù)的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-12-12java Volatile與Synchronized的區(qū)別
這篇文章主要介紹了java Volatile與Synchronized的區(qū)別,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12Spring?Boot?整合JPA?數(shù)據(jù)模型關聯(lián)使用操作(一對一、一對多、多對多)
這篇文章主要介紹了Spring?Boot?整合JPA?數(shù)據(jù)模型關聯(lián)操作(一對一、一對多、多對多),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07SpringBoot實現(xiàn)yml配置文件為變量賦值
這篇文章主要介紹了SpringBoot實現(xiàn)yml配置文件為變量賦值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02