java?ReentrantLock并發(fā)鎖使用詳解
一、ReentrantLock是什么
ReentrantLock是一種基于AQS框架的應(yīng)用實(shí)現(xiàn),是JDK中的一種線程并發(fā)訪問的同步手段,它的功能類似于synchronized是一種互斥鎖,可以保證線程安全。
相對于 synchronized, ReentrantLock具備如下特點(diǎn):
- 可中斷
- 可以設(shè)置超時時間
- 可以設(shè)置為公平鎖
- 支持多個條件變量
- 與 synchronized 一樣,都支持可重入
進(jìn)入源碼可以看到,其實(shí)現(xiàn)了公平鎖和非公平鎖
內(nèi)部實(shí)現(xiàn)了加鎖的操作,并且支持重入鎖。不用我們再重寫
解鎖操作
1-1、ReentrantLock和synchronized區(qū)別
synchronized和ReentrantLock的區(qū)別:
- synchronized是JVM層次的鎖實(shí)現(xiàn),ReentrantLock是JDK層次的鎖實(shí)現(xiàn);
- synchronized的鎖狀態(tài)是無法在代碼中直接判斷的,但是ReentrantLock可以通過ReentrantLock#isLocked判斷;
- synchronized是非公平鎖,ReentrantLock是可以是公平也可以是非公平的;
- synchronized是不可以被中斷的,而ReentrantLock#lockInterruptibly方法是可以被中斷的;
- 在發(fā)生異常時synchronized會自動釋放鎖,而ReentrantLock需要開發(fā)者在finally塊中顯示釋放鎖;
- ReentrantLock獲取鎖的形式有多種:如立即返回是否成功的tryLock(),以及等待指定時長的獲取,更加靈活;
- synchronized在特定的情況下對于已經(jīng)在等待的線程是后來的線程先獲得鎖(回顧一下sychronized的喚醒策略),而ReentrantLock對于已經(jīng)在等待的線程是先來的線程先獲得鎖;
1-2、ReentrantLock的使用
1-2-1、ReentrantLock同步執(zhí)行,類似synchronized
使用ReentrantLock需要注意的是:一定要在finally中進(jìn)行解鎖,方式業(yè)務(wù)拋出異常,無法解鎖
public class ReentrantLockDemo { private static int sum = 0; private static ReentrantLock lock=new ReentrantLock(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 3; i++) { Thread thread = new Thread(()->{ //加鎖 lock.lock(); try { // 臨界區(qū)代碼 // TODO 業(yè)務(wù)邏輯:讀寫操作不能保證線程安全 for (int j = 0; j < 10000; j++) { sum++; } } finally { // 解鎖--一定要在finally中解鎖,防止業(yè)務(wù)代碼異常,無法釋放鎖 lock.unlock(); } }); thread.start(); } Thread.sleep(2000); System.out.println(sum); } }
測試結(jié)果:
1-2-2、可重入鎖
可重入鎖就是 A(加鎖)-->調(diào)用--->B(加鎖)-->調(diào)用-->C(加鎖),從A到C即使B/C都有加鎖,也可以進(jìn)入
@Slf4j public class ReentrantLockDemo2 { public static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { method1(); } public static void method1() { lock.lock(); try { log.debug("execute method1"); method2(); } finally { lock.unlock(); } } public static void method2() { lock.lock(); try { log.debug("execute method2"); method3(); } finally { lock.unlock(); } } public static void method3() { lock.lock(); try { log.debug("execute method3"); } finally { lock.unlock(); } } }
執(zhí)行結(jié)果:
1-2-3、鎖中斷
可以使用lockInterruptibly
來進(jìn)行鎖中斷
lockInterruptibly()方法能夠中斷等待獲取鎖的線程。當(dāng)兩個線程同時通過lock.lockInterruptibly()
獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有等待,那么對線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過程。
public class ReentrantLockDemo3 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { log.debug("t1啟動..."); try { lock.lockInterruptibly(); try { log.debug("t1獲得了鎖"); } finally { lock.unlock(); } } catch (InterruptedException e) { e.printStackTrace(); log.debug("t1等鎖的過程中被中斷"); } }, "t1"); lock.lock(); try { log.debug("main線程獲得了鎖"); t1.start(); //先讓線程t1執(zhí)行 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t1.interrupt(); log.debug("線程t1執(zhí)行中斷"); } finally { lock.unlock(); } } }
執(zhí)行結(jié)果:
1-2-4、獲得鎖超時失敗
可以讓線程等待指定的時間,如果還未獲取鎖則進(jìn)行失敗處理。
如下代碼,首先讓主線程獲得鎖,然后讓子線程啟動嘗試獲取鎖,但是由于主線程獲取鎖之后,讓線程等待了2秒,而子線程獲得鎖的超時時間只有1秒,如果未獲得鎖,則進(jìn)行return失敗處理
public class ReentrantLockDemo4 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { log.debug("t1啟動..."); //超時 try { if (!lock.tryLock(1, TimeUnit.SECONDS)) { log.debug("等待 1s 后獲取鎖失敗,返回"); return; } } catch (InterruptedException e) { e.printStackTrace(); return; } try { log.debug("t1獲得了鎖"); } finally { lock.unlock(); } }, "t1"); lock.lock(); try { log.debug("main線程獲得了鎖"); t1.start(); //先讓線程t1執(zhí)行 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock(); } } }
執(zhí)行結(jié)果:
1-2-5、公平鎖
ReentrantLock 默認(rèn)是不公平的
首先啟動500次for循環(huán)創(chuàng)建500個線程,然后進(jìn)行加鎖操作,并同時啟動了。這樣這500個線程就依次排隊(duì)等待加鎖的處理
下面500個線程也是等待加鎖操作
如果使用公平鎖,下面500的線程只有等上面500個線程運(yùn)行完成之后才能獲得鎖。
@Slf4j public class ReentrantLockDemo5 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(true); //公平鎖 for (int i = 0; i < 500; i++) { new Thread(() -> { lock.lock(); try { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } log.debug(Thread.currentThread().getName() + " running..."); } finally { lock.unlock(); } }, "t" + i).start(); } // 1s 之后去爭搶鎖 Thread.sleep(1000); for (int i = 0; i < 500; i++) { new Thread(() -> { lock.lock(); try { log.debug(Thread.currentThread().getName() + " running..."); } finally { lock.unlock(); } }, "強(qiáng)行插入" + i).start(); } } }
測試結(jié)果(后進(jìn)入的線程都在等待排隊(duì))
使用非公平鎖的情況下,就可以看到下面500線程有些線程就可以搶占鎖了
那ReentrantLock為什么默認(rèn)使用非公平鎖呢?實(shí)際上就是為了提高性能,如果使用公平鎖,當(dāng)前鎖對象釋放之后,還需要去隊(duì)列中獲取第一個排隊(duì)的線程,然后進(jìn)行加鎖處理。而非公平鎖,可能再當(dāng)前對象釋放鎖之后,正好有新的線程在獲取鎖,這樣就可以直接進(jìn)行加鎖操作,不必再去隊(duì)列中讀取。
以上就是java ReentrantLock并發(fā)鎖使用詳解的詳細(xì)內(nèi)容,更多關(guān)于java ReentrantLock并發(fā)鎖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaGUI界面實(shí)現(xiàn)頁面跳轉(zhuǎn)方法
這篇文章主要給大家介紹了關(guān)于JavaGUI界面實(shí)現(xiàn)頁面跳轉(zhuǎn)的相關(guān)資料, GUI是指圖形用戶界面,指采用圖形方式顯示的計(jì)算機(jī)操作用戶界面,需要的朋友可以參考下2023-07-07在java中使用SPI創(chuàng)建可擴(kuò)展的應(yīng)用程序操作
這篇文章主要介紹了在java中使用SPI創(chuàng)建可擴(kuò)展的應(yīng)用程序操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09java實(shí)現(xiàn)gif動畫效果(java顯示動態(tài)圖片)
這篇文章主要介紹了java實(shí)現(xiàn)gif動畫效果示例(java顯示動態(tài)圖片),需要的朋友可以參考下2014-04-04springBoot?啟動指定配置文件環(huán)境多種方案(最新推薦)
springBoot?啟動指定配置文件環(huán)境理論上是有多種方案的,一般都是結(jié)合我們的實(shí)際業(yè)務(wù)選擇不同的方案,比如,有pom.xml文件指定、maven命令行指定、配置文件指定、啟動jar包時指定等方案,今天我們一一分享一下,需要的朋友可以參考下2023-09-09SpringBoot整合POI實(shí)現(xiàn)Excel文件讀寫操作
EasyExcel是一個基于Java的、快速、簡潔、解決大文件內(nèi)存溢出的Excel處理工具,這篇文章主要介紹了SpringBoot整合POI實(shí)現(xiàn)Excel文件讀寫操作,首先準(zhǔn)備環(huán)境進(jìn)行一系列操作,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-10-10