日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ReentrantLock学习

發布時間:2024/7/5 编程问答 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ReentrantLock学习 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  

  對于并發工作,你需要某種方式來防止兩個任務訪問相同的資源,至少在關鍵階段不能出現這種沖突情況。防止這種沖突的方法就是當資源被一個任務使用時,在其上加鎖。在前面的文章--synchronized學習中,我們學習了Java中內建的同步機制synchronized的基本用法,在本文中,我們來學習Java中另一種鎖ReentrantLock。

?ReentrantLock介紹

  ReentrantLock,通常譯為再入鎖,是Java 5中新加入的鎖實現,它與synchronized基本語義相同。再入鎖通過代碼直接調用lock()方法獲取,代碼書寫更加靈活。與此同時,ReentrantLock提供了很多實用的方法,能夠實現很多synchronized無法做到的細節控制,比如可以控制fairness,也就是公平性,或者利用定義條件等。但是,編碼中也需要注意,必須要明確調用unlock()方法釋放鎖,不然就會一直持有該鎖。

?

  我們先看一個簡單例子體驗一下:

public class ReLockTest {private Lock lock = new ReentrantLock();public int shareState;public void add() {shareState ++ ;}public void printState() {System.out.println("shareState-->" + shareState); }public static void main(String[] args) throws Exception{ReLockTest reLockTest = new ReLockTest();Thread t1 = new Thread(()->{for(int i = 0; i<10000 ; i++)reLockTest.add();});Thread t2 = new Thread(()->{for(int i = 0; i<10000 ; i++)reLockTest.add();});t1.start();t2.start();t1.join();t2.join();Thread.sleep(2000);reLockTest.printState();} }

/** 輸出結果
* shareState-->14012
*/

  我們看到輸出結果小于20000,這就是線程不安全,我們加上同步之后再看結果:

?

public class ReLockTest {private Lock lock = new ReentrantLock();public int shareState;public void add() {lock.lock();try {shareState ++ ;}catch(Exception e) {e.printStackTrace();}finally {lock.unlock();}}public void printState() {System.out.println("shareState-->" + shareState); }public static void main(String[] args) throws Exception{ReLockTest reLockTest = new ReLockTest();Thread t1 = new Thread(()->{for(int i = 0; i<10000 ; i++)reLockTest.add();});Thread t2 = new Thread(()->{for(int i = 0; i<10000 ; i++)reLockTest.add();});t1.start();t2.start();t1.join();t2.join();Thread.sleep(2000);reLockTest.printState();} }
/** 輸出結果
* shareState-->20000
*/

  

  我們看到在將自增操作上鎖之后,輸出結果就達到預期了。這就是ReentrantLock的基本用法,為了保證鎖釋放,每個lock()動作都對應一個try-catch-finally,這可以說是一個慣用法:

ReentrantLock lock = new ReentrantLock(); lock.lock(); try {// do something } finally {lock.unlock(); }

ReentrantLock用法

  ReentrantLock相比synchronized,雖然所需的代碼比synchronized關鍵字要多,但也是因為可以像普通對象一樣使用,所以可以利用其提供的各種便利方法,進行精細的同步操作,甚至是synchronized難以表達的用例,如:

帶超時的獲取鎖嘗試

  通過tryLock(long timeout, TimeUnit unit)方法實現,這是一個具有超時參數的嘗試申請鎖的方法,阻塞時間不會超過給定的值;如果成功則返回true。

可以指定公平性

  在創建ReentrantLock對象時往構造器中傳入true即指定創建公平鎖,這里所謂的公平是指在競爭場景中,當公平性為真時,會傾向于將鎖賦予等待時間最久的線程。公平性是減少線程“饑餓”(個別線程長期等待鎖,但始終無法獲取)情況的發生的一個辦法。

  如果使用synchronized,我們根本無法進行公平性的選擇,其永遠是不公平的,這也是主流操作系統線程調度的選擇。通用場景中,公平性未必有想象中的那么重要,Java默認的調度策略很少會導致“饑餓”發生。與此同時,若要保證公平性則會引入額外開銷,自然會導致一定的吞吐量下將。所以,只有當程序確實有公平性需要的時候,才有必要指定它。

可以響應中斷請求

  通過lockInterruptibly()獲得鎖,但是會不確定地發生阻塞。如果線程被中斷,拋出一個InterruptedException異常。

可以創建條件變量,將復雜晦澀的同步操作轉變為直觀可控的對象行為

  如果說ReentrantLock是synchronized的替代選擇,Conition則是將wait、notify、notifyAll等操作轉化為相應的對象,將復雜而晦澀的同步操作轉變為直觀可控的對象行為。

  可以通過Condition上調用await()來掛起一個任務,當外部條件發生變化,你可以通過調用signal()來通知這個任務,從而喚醒一個任務,或者調用signAll()來喚醒所有在這個Condition上被其自身掛起的任務,這是Condition最常見的用法。一個典型的應用場景就是標準類庫中的ArrayBlockingQueue,下面我們結合部分其源碼來分析一下:

  首先,在構造函數中初始化lock,在從lock中創建兩個條件變量Condition分別是notEmpty和notFull。

/** Main lock guarding all access */ final ReentrantLock lock;/** Condition for waiting takes */ private final Condition notEmpty;/** Condition for waiting puts */ private final Condition notFull;public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity <= 0)throw new IllegalArgumentException();this.items = new Object[capacity];lock = new ReentrantLock(fair);notEmpty = lock.newCondition();notFull = lock.newCondition(); }

  然后在take方法中,判斷和等待條件滿足:

public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();} }

  在take方法中,如果隊列為空,ArrayBlockingQueue的本義是獲取對象的線程會阻塞,等待入隊的發生,而不是直接返回,代碼中是通過調用Condition的await方法來實現的,而當有元素入隊之后又是如何觸發被阻塞的take操作的呢?我們看看enqueue:

?

private void enqueue(E x) {// assert lock.getHoldCount() == 1;// assert items[putIndex] == null;final Object[] items = this.items;items[putIndex] = x;if (++putIndex == items.length)putIndex = 0;count++;notEmpty.signal();  // 通知等待的線程 }

  最后一行代碼中就是通知等待的線程,這行執行結束后,被take阻塞的線程就會繼續執行了。

  通過signal/await的組合,完成了條件判斷和通知等待線程,非常順暢就完成了狀態流轉。signal和await成對調用非常重要,不然假設只有await動作,線程會一直等待直到被打斷(interrupt)。

ReentrantLock與Synchronized

  使用synchronized關鍵字時,需要寫的代碼量更少,而ReentrantLock的使用往往和try-catch-finally一起配套使用,代碼量增加了。

  從性能角度,synchronized早期的實現比較低效,對比ReentrantLock,大多數場景性能都相差較大。但是在Java 6中對其進行了非常多的改進(可以參考synchronized底層實現學習),在高競爭情況下,ReentrantLock仍然有一定優勢。

  我們知道synchronized獲取的鎖是monitor對象,而ReentrantLock獲取的鎖是什么呢,是否也是同一把鎖呢?下文中,我們會深入源碼去探究ReentrantLock獲取鎖的實現細節。

轉載于:https://www.cnblogs.com/volcano-liu/p/10262183.html

總結

以上是生活随笔為你收集整理的ReentrantLock学习的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。