亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術文章
文章詳情頁

java并發編程專題(五)----詳解(JUC)ReentrantLock

瀏覽:79日期:2022-08-30 08:51:29

上一節我們了解了Lock接口的一些簡單的說明,知道Lock鎖的常用形式,那么這節我們正式開始進入JUC鎖(java.util.concurrent包下的鎖,簡稱JUC鎖)。下面我們來看一下Lock最常用的實現類ReentrantLock。

1.ReentrantLock簡介

由單詞意思我們可以知道這是可重入的意思。那么可重入對于鎖而言到底意味著什么呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者后續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。

1.1公平鎖與非公平鎖

我們查看ReentrantLock的源碼可以看到無參構造函數是這樣的:

public ReentrantLock() { sync = new NonfairSync();}

NonfairSync()方法為一個非公平鎖的實現方法,另外Reentrantlock還有一個有參的構造方法:

public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}

它允許您選擇想要一個 公平(fair)鎖,還是一個 不公平(unfair)鎖。公平鎖使線程按照請求鎖的順序依次獲得鎖;而不公平鎖則允許直接獲取鎖,在這種情況下,線程有時可以比先請求鎖的其他線程先得到鎖。

為什么我們不讓所有的鎖都公平呢?畢竟,公平是好事,不公平是不好的,不是嗎?(當孩子們想要一個決定時,總會叫嚷“這不公平”。我們認為公平非常重要,孩子們也知道。)在現實中,公平保證了鎖是非常健壯的鎖,有很大的性能成本。要確保公平所需要的記帳(bookkeeping)和同步,就意味著被爭奪的公平鎖要比不公平鎖的吞吐率更低。作為默認設置,應當把公平設置為 false ,除非公平對您的算法至關重要,需要嚴格按照線程排隊的順序對其進行服務。

下面我們先來看一個例子:

public class TestReentrantLock implements Runnable{ ReentrantLock lock = new ReentrantLock(); public void get() { lock.lock(); System.out.println(Thread.currentThread().getId()); set(); lock.unlock(); } public void set() { lock.lock(); System.out.println(Thread.currentThread().getId()); lock.unlock(); } @Override public void run() { get(); } public static void main(String[] args) { TestReentrantLock ss = new TestReentrantLock(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); } }

運行結果:

101012121111

Process finished with exit code 0

由結果我們可以看出同一個線程進入了同一個ReentrantLock鎖兩次。

2.condition條件變量

我們知道根類 Object 包含某些特殊的方法,用來在線程的 wait() 、 notify() 和 notifyAll() 之間進行通信。那么為了在對象上 wait 或 notify ,您必須持有該對象的鎖。就像 Lock 是同步的概括一樣, Lock 框架包含了對 wait 和 notify 的概括,這個概括叫作 條件(Condition)。 Condition 的方法與 wait 、 notify 和 notifyAll 方法類似,分別命名為 await 、 signal 和signalAll ,因為它們不能覆蓋 Object 上的對應方法。

首先我們來計算一道題:我們要打印1到9這9個數字,由A線程先打印1,2,3,然后由B線程打印4,5,6,然后再由A線程打印7,8,9. 這道題有很多種解法,我們先用Object的wait,notify方法來實現:

public class WaitNotifyDemo { private volatile int val = 1; private synchronized void printAndIncrease() { System.out.println(Thread.currentThread().getName() +'prints ' + val); val++; } // print 1,2,3 7,8,9 public class PrinterA implements Runnable { @Override public void run() { while (val <= 3) { printAndIncrease(); } // print 1,2,3 then notify printerB synchronized (WaitNotifyDemo.this) { System.out.println('PrinterA printed 1,2,3; notify PrinterB'); WaitNotifyDemo.this.notify(); } try { while (val <= 6) { synchronized (WaitNotifyDemo.this) { System.out.println('wait in printerA'); WaitNotifyDemo.this.wait(); } } System.out.println('wait end printerA'); } catch (InterruptedException e) { e.printStackTrace(); } while (val <= 9) { printAndIncrease(); } System.out.println('PrinterA exits'); } } // print 4,5,6 after printA print 1,2,3 public class PrinterB implements Runnable { @Override public void run() { while (val < 3) { synchronized (WaitNotifyDemo.this) { try { System.out .println('printerB wait for printerA printed 1,2,3'); WaitNotifyDemo.this.wait(); System.out .println('printerB waited for printerA printed 1,2,3'); } catch (InterruptedException e) { e.printStackTrace(); } } } while (val <= 6) { printAndIncrease(); } System.out.println('notify in printerB'); synchronized (WaitNotifyDemo.this) { WaitNotifyDemo.this.notify(); } System.out.println('notify end printerB'); System.out.println('PrinterB exits.'); } } public static void main(String[] args) { WaitNotifyDemo demo = new WaitNotifyDemo(); demo.doPrint(); } private void doPrint() { PrinterA pa = new PrinterA(); PrinterB pb = new PrinterB(); Thread a = new Thread(pa); a.setName('printerA'); Thread b = new Thread(pb); b.setName('printerB'); // 必須讓b線程先執行,否則b線程有可能得不到鎖,執行不了wait,而a線程一直持有鎖,會先notify了 b.start(); a.start(); } }

運行結果為:

printerB wait for printerA printed 1,2,3printerA prints 1printerA prints 2printerA prints 3PrinterA printed 1,2,3; notify PrinterBwait in printerAprinterB waited for printerA printed 1,2,3printerB prints 4printerB prints 5printerB prints 6notify in printerBnotify end printerBwait end printerAprinterA prints 7printerA prints 8printerA prints 9PrinterA exitsPrinterB exits.

Process finished with exit code 0

我們來分析一下上面的程序:

首先在main方法中我們看到是先啟動了B線程,因為B線程持有wait()對象,而A線程則持有notify(),如果先啟動A有可能會造成死鎖的狀態。B線程啟動以后進入run()方法:

while (val < 3) { synchronized (WaitNotifyDemo.this) { try { System.out.println('printerB wait for printerA printed 1,2,3'); WaitNotifyDemo.this.wait(); System.out.println('printerB waited for printerA printed 1,2,3'); } catch (InterruptedException e) { e.printStackTrace(); } } } while (val <= 6) { printAndIncrease(); }

這里有一個while循環,如果val的值小于3,那么在WaitNotifyDemo的實例的同步塊中調用WaitNotifyDemo.this.wait()方法,這里要注意無論是wait,還是notify,notifyAll方法都需要在其實例對象的同步塊中執行,這樣當前線程才能獲得同步實例的同步控制權,如果不在同步塊中執行wait或者notify方法會出java.lang.IllegalMonitorStateException異常。另外還要注意在wait方法兩邊的同步塊會在wait執行完畢之后釋放對象鎖。

這樣PrinterB就進入了等待狀態,我們再看下PrinterA的run方法:

while (val <= 3) { printAndIncrease(); }// print 1,2,3 then notify printerBsynchronized (WaitNotifyDemo.this) { System.out.println('PrinterA printed 1,2,3; notify PrinterB'); WaitNotifyDemo.this.notify();}try { while (val <= 6) { synchronized (WaitNotifyDemo.this) { System.out.println('wait in printerA'); WaitNotifyDemo.this.wait(); } } System.out.println('wait end printerA');} catch (InterruptedException e) { e.printStackTrace();}

這里首先打印了1、2、3,然后在同步塊中調用了WaitNotifyDemo實例的notify方法,這樣PrinterB就得到了繼續執行的通知,然后PrinterA進入等待狀態,等待PrinterB通知。

我們再看下PrinterB run方法剩下的代碼:

while (val <= 6) { printAndIncrease();}System.out.println('notify in printerB');synchronized (WaitNotifyDemo.this) { WaitNotifyDemo.this.notify();}System.out.println('notify end printerB');System.out.println('PrinterB exits.');

PrinterB首先打印了4、5、6,然后在同步塊中調用了notify方法,通知PrinterA開始執行。

PrinterA得到通知后,停止等待,打印剩下的7、8、9三個數字,如下是PrinterA run方法中剩下的代碼:

while (val <= 9) { printAndIncrease();}

整個程序就分析完了,下面我們再來使用Condition來做這道題:

public class TestCondition { static class NumberWrapper { public int value = 1; } public static void main(String[] args) { //初始化可重入鎖 final Lock lock = new ReentrantLock(); //第一個條件當屏幕上輸出到3 final Condition reachThreeCondition = lock.newCondition(); //第二個條件當屏幕上輸出到6 final Condition reachSixCondition = lock.newCondition(); //NumberWrapper只是為了封裝一個數字,一邊可以將數字對象共享,并可以設置為final //注意這里不要用Integer, Integer 是不可變對象 final NumberWrapper num = new NumberWrapper(); //初始化A線程 Thread threadA = new Thread(new Runnable() { @Override public void run() { //需要先獲得鎖 lock.lock(); try { System.out.println('threadA start write'); //A線程先輸出前3個數 while (num.value <= 3) { System.out.println(num.value); num.value++; } //輸出到3時要signal,告訴B線程可以開始了 reachThreeCondition.signal(); } finally { lock.unlock(); } lock.lock(); try { //等待輸出6的條件 reachSixCondition.await(); System.out.println('threadA start write'); //輸出剩余數字 while (num.value <= 9) { System.out.println(num.value); num.value++; } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { try { lock.lock(); while (num.value <= 3) { //等待3輸出完畢的信號 reachThreeCondition.await(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } try { lock.lock(); //已經收到信號,開始輸出4,5,6 System.out.println('threadB start write'); while (num.value <= 6) { System.out.println(num.value); num.value++; } //4,5,6輸出完畢,告訴A線程6輸出完了 reachSixCondition.signal(); } finally { lock.unlock(); } } }); //啟動兩個線程 threadB.start(); threadA.start(); }}

基本思路就是首先要A線程先寫1,2,3,這時候B線程應該等待reachThredCondition信號,而當A線程寫完3之后就通過signal告訴B線程“我寫到3了,該你了”,這時候A線程要等嗲reachSixCondition信號,同時B線程得到通知,開始寫4,5,6,寫完4,5,6之后B線程通知A線程reachSixCondition條件成立了,這時候A線程就開始寫剩下的7,8,9了。

我們可以看到上例中我們創建了兩個Condition,在不同的情況下可以使用不同的Condition,與wait和notify相比提供了更細致的控制。

3.線程阻塞原語?LockSupport

我們一再提線程、鎖等概念,但鎖是如果實現的呢?又是如何知道當前阻塞線程的又是哪個對象呢?LockSupport是JDK中比較底層的類,用來創建鎖和其他同步工具類的基本線程阻塞原語。

java鎖和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通過調用 LockSupport .park()和 LockSupport .unpark()實現線程的阻塞和喚醒 的。 LockSupport 很類似于二元信號量(只有1個許可證可供使用),如果這個許可還沒有被占用,當前線程獲取許可并繼 續 執行;如果許可已經被占用,當前線 程阻塞,等待獲取許可。LockSupport是針對特定線程來進行阻塞和解除阻塞操作的;而Object的wait()/notify()/notifyAll()是用來操作特定對象的等待集合的。LockSupport的兩個主要方法是park()和Unpark(),我們來看一下他們的實現:

public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); unsafe.park(false, 0L); setBlocker(t, null); }public static void park() { unsafe.park(false, 0L); }public static void unpark(Thread thread) { if (thread != null) unsafe.unpark(thread); }

由源碼我們可見在park方法內部首先獲得當前線程然后阻塞當前線程,unpark方法傳入一個可配置的線程來為該線程解鎖。以“線程”作為方法的參數, 語義更清晰,使用起來也更方便。而wait/notify的實現使得“線程”的阻塞/喚醒對線程本身來說是被動的,要準確的控制哪個線程、什么時候阻塞/喚醒很困難, 要不隨機喚醒一個線程(notify)要不喚醒所有的(notifyAll)。

下面我們來看一個例子:

public class TestLockSupport { public static Object u = new Object(); static ChangeObjectThread t1 = new ChangeObjectThread('t1'); static ChangeObjectThread t2 = new ChangeObjectThread('t2'); public static class ChangeObjectThread extends Thread { public ChangeObjectThread(String name) { super.setName(name); } public void run() { synchronized (u) { System.out.println('in' + getName()); LockSupport.park(); } } } public static void main(String[] args) throws InterruptedException { t1.start(); Thread.sleep(2000); t2.start(); LockSupport.unpark(t1); LockSupport.unpark(t2); t1.join(); t2.join(); }}

當我們把”LockSupport.unpark(t1);”這一句注掉的話我們會發現程序陷入死鎖。而且我們看到再main方法中unpark是在t1和t2啟動之后才執行,但是為什么t1啟動之后,t2也啟動了呢?注意,**unpark函數可以先于park調用。比如線程B調用unpark函數,給線程A發了一個“許可”,那么當線程A調用park時,它發現已經有“許可”了,那么它會馬上再繼續運行。**unpark函數為線程提供“許可(permit)”,線程調用park函數則等待“許可”。這個有點像信號量,但是這個“許可”是不能疊加的,“許可”是一次性的。比如線程B連續調用了三次unpark函數,當線程A調用park函數就使用掉這個“許可”,如果線程A再次調用park,則進入等待狀態。

除了有定時阻塞的功能外,還支持中斷影響,但是和其他接收中斷函數不一樣,他不會拋出InterruptedException異常,他只會默默的返回,但是我們可以從Thread.Interrupted()等方法獲得中斷標記.我們來看一個例子:

public class TestLockSupport { public static Object u = new Object(); static ChangeObjectThread t1 = new ChangeObjectThread('t1'); static ChangeObjectThread t2 = new ChangeObjectThread('t2'); public static class ChangeObjectThread extends Thread { public ChangeObjectThread(String name) { super.setName(name); } public void run() { synchronized (u) { System.out.println('in ' + getName()); LockSupport.park(); if (Thread.interrupted()) { System.out.println(getName() + ' 被中斷了!'); } } System.out.println(getName() + ' 執行結束'); } } public static void main(String[] args) throws InterruptedException { t1.start(); Thread.sleep(100); t2.start(); t1.interrupt(); LockSupport.unpark(t2); }}

輸出:

in t1t1 被中斷了!t1 執行結束in t2t2 執行結束

Process finished with exit code 0

由run方法中的終端異常捕獲我們可以看到線程在中斷時并沒有拋出異常而是正常執行下去了。

關于LockSupport其實要介紹的東西還是很多,因為這個類實現了底層的一些方法,各種的鎖實現都是這個基礎上發展而來的。以后會專門用一個篇章來學習jdk內部的阻塞機制。說前面我們講到Object的wait和notify,講到Condition條件,講到jdk中不對外部暴露的LockSupport阻塞原語,那么在JUC包中還有另外一個阻塞機制—信號量機制(Semaphore),下一節我們一起探討一下。

以上就是java并發編程專題(五)----詳解(JUC)ReentrantLock的詳細內容,更多關于java ReentrantLock的資料請關注好吧啦網其它相關文章!

標簽: Java
相關文章:
主站蜘蛛池模板: 视频一区二区国产无限在线观看 | 欧美中文字幕在线视频 | 全免费a级毛片免费看不卡 全免费a级毛片免费看视频免 | 黄色免费网址在线观看 | 窝窝午夜精品一区二区 | 亚洲二区在线播放 | 欧美视频一区 | 国产精品日本欧美一区二区 | 午夜久久久久久亚洲国产精品 | 手机看片日韩国产 | 中国精品一级毛片免费播放 | 精品久久久久久国产91 | 好看的一级毛片 | 91视频国产高清 | 国产美女精品在线 | 国产精品免费aⅴ片在线观看 | 色综合亚洲天天综合网站 | 日韩视频在线观看中字 | 中文字幕免费在线观看动作大片 | 欧美日本一区二区三区生 | 免费国产黄网站在线观看视频 | 亚洲精品中文字幕乱码影院 | 日本黄色一级网站 | 一级片aa| 综合亚洲精品一区二区三区 | 麻豆精品视频在线观看 | 欧美卡1卡2卡三卡网站入口 | 男女草| 色婷婷六月丁香七月婷婷 | 麻豆爱爱视频 | 欧美激情大尺度做爰叫床声 | 国产69精品久久久久9牛牛 | 欧美人拘一级毛片 | 久久精品视频1 | 日本一级特黄大一片免 | 久久三级精品 | 91国在线观看 | 性爽交免费视频 | 亚洲成本人网亚洲视频大全 | 成人性生交大片免费看软件 | 国产亚洲一区二区三区啪 |