java并發編程實戰:第十四章----構建自定義的同步工具

一、狀態依賴性管理

  • 對于單線程程序,某個條件為假,那么這個條件將永遠無法成真
  • 在并發程序中,基于狀態的條件可能會由于其他線程的操作而改變
     1 可阻塞的狀態依賴操作的結構
     2 
     3 acquire lock on object state
     4 while (precondition does not hold) 
     5 {
     6     release lock
     7     wait until precondition might hold
     8     optionally fail if interrupted or timeout expires
     9     reacquire lock
    10 }
    11 perform action
    12 release lock

    ?

 1 //有界緩存實現的基類2 public abstract class BaseBoundedBuffer<V> {3     private final V[] buf;4     private int tail;5     private int head;6     private int count;7     8     protected BaseBoundedBuffer(int capacity){9         this.buf = (V[]) new Object[capacity];
10     }
11     
12     protected synchronized final void doPut(V v){
13         buf[tail] = v;
14         if (++tail == buf.length){
15             tail = 0;
16         }
17         ++count;
18     }
19     
20     protected synchronized final V doTake(){
21         V v = buf[head];
22         buf[head] = null; //let gc collect
23         if (++head == buf.length){
24             head = 0;
25         }
26         --count;
27         return v;
28     }
29     
30     public synchronized final boolean isFull(){
31         return count == buf.length;
32     }
33     
34     public synchronized final boolean isEmpty(){
35         return count == 0;
36     }
37 }

1、示例:將前提條件的失敗傳遞給調用者?

 1 public class GrumyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
 2     public GrumyBoundedBuffer(int size){
 3         super(size);
 4     }
 5     
 6     public synchronized void put(V v){
 7         if (isFull()){
 8             throw new BufferFullException();
 9         }
10         doPut(v);
11     }
12     
13     public synchronized V take(){
14         if (isEmpty())
15             throw new BufferEmptyExeption();
16         return doTake();
17     }
18 }
19 
20 當不滿足前提條件時,有界緩存不會執行相應的操作

?

缺點:已滿情況不應為異常;調用者自行處理失敗;sleep:降低響應性;自旋等待:浪費CPU;yield讓出CPU

2、示例:通過輪詢與休眠來實現簡單的阻塞

 1 public class SleepyBounedBuffer<V> extends BaseBoundedBuffer<V> {
 2     private static long SLEEP_TIME;
 3     public SleepyBounedBuffer(int size) {
 4         super(size);
 5     }
 6 
 7     public void put(V v) throws InterruptedException{
 8         while (true){
 9             synchronized(this){
10                 if (!isFull()){
11                     doPut(v);
12                     return;
13                 }
14             }
15             Thread.sleep(SLEEP_TIME);
16         }
17     }
18     
19     public V take() throws InterruptedException{
20         while (true){
21             synchronized(this){
22                 if (!isEmpty()){
23                     return doTake();
24                 }
25             }
26             Thread.sleep(SLEEP_TIME);
27         }
28     }
29 }
30 
31 “輪詢與休眠“重試機制

?

優點:對于調用者,無需處理失敗與異常,操作可阻塞,可中斷(休眠時候不要持有鎖)

缺點:對于休眠時間設置的權衡(響應性與CPU資源)

3、條件隊列——使得一組線程(稱之為等待線程集合)能夠通過某種方式來等待特定的條件變成真(元素是一個個正在等待相關條件的線程)

  • 每個對象都可以作為一個條件隊列(API:wait、notify和notifyAll)
    • Object.wait會自動釋放鎖,并請求操作系統掛起當前線程,從而使其他線程能夠獲得這個鎖并且修改對象的狀態
    • Object.notify/notifyAll通知被掛起的線程可以重新請求資源執行
  • 只有能對狀態進行檢查時,才能在某個條件上等待,并且只有能修改狀態時,才能從條件等待中釋放另一個線程
  • 條件隊列在CPU效率、上下文切換開銷和響應性等進行了優化
  • 如果某個功能無法通過“輪詢和休眠”來實現,那么使用條件隊列也無法實現
 1 public class BoundedBuffer<V> extends BaseBoundedBuffer<V> {2 3     public BoundedBuffer(int capacity) {4         super(capacity);5     }6     7     public synchronized void put(V v) throws InterruptedException{8         while (isFull()){9             wait();
10         }
11         doPut(v);
12         notifyAll();
13     }
14     
15     public synchronized V take() throws InterruptedException{
16         while (isEmpty()){
17             wait();
18         }
19         V v = doTake();
20         notifyAll();
21         return v;
22     }
23 }

?

二、使用條件隊列

1、條件謂詞

  • 條件等待中存在一種重要的三元關系,包括加鎖、wait方法和一個條件謂詞
  • 條件謂詞是由類中各個狀態變量構成的表達式(while)
  • 在測試條件謂詞之前必須先持有這個鎖
  • 鎖對象與條件隊列對象(即調用wait和notify等方法所在的對象)必須是同一個對象
  • wait被喚醒后需要重新獲得鎖,并重新檢查條件謂詞

2、過早喚醒——一個條件隊列與多個條件謂詞相關時,wait方法返回不一定線程所等待的條件謂詞就變為真了

1 void stateDependentMethod() throws InterruptedException
2 {
3   synchronized(lock)  // 必須通過一個鎖來保護條件謂詞
4     {
5         while(!condietionPredicate()) 
6             lock.wait();
7     }
8 }

當使用條件等待時(如Object.wait(), 或Condition.await()):

  • 通常都有一個條件謂詞--包括一些對象狀態的測試,線程在執行前必須首先通過這些測試
  • 在調用wait之前測試條件謂詞,并且從wait中返回時再次進行測試
  • 在一個循環中調用wait
  • 確保使用與條件隊列相關的鎖來保護構成條件謂詞的各個狀態變量
  • 當調用wait,?notify或notifyAll等方法時,一定要持有與條件隊列相關的鎖
  • 在檢查條件謂詞之后以及開始執行相應的操作之前,不要釋放鎖。

3、丟失信號量——線程必須等待一個已經為真的條件,但在開始等待之前沒有檢查條件謂詞

如果線程A通知了一個條件隊列,而線程B隨后在這個條件隊列上等待,那么線程B將不會立即醒來,而是需要另一個通知來喚醒它(導致活躍性下降)

4、通知——確保在條件謂詞變為真時通過某種方式發出通知掛起的線程

  • 發出通知的線程持有鎖調用notify和notifyAll,發出通知后應盡快釋放鎖
  • 多個線程可以基于不同的條件謂詞在同一個條件隊列上等待,使用notify單一的通知很容易導致類似于信號丟失的問題
  • 可以使用notify:同一條件謂詞并且單進單出

使用notifyAll有時是低效的:喚醒的所有線程都需要競爭鎖,并重新檢驗,而有時最終只有一個線程能執行

優化:條件通知

1 public synchronized void put(V v) throws InterruptedException
2 {
3     while(isFull())
4         wait();
5     boolean wasEmpty = isEmpty();
6     doPut(v);
7     if(wasEmpty)
8         notifyAll();
9 }

?5、示例:閥門類

 1 public class ThreadGate {
 2        private boolean isOpen;
 3        private int generation;
 4 
 5        public synchronized void close() {
 6               isOpen = false;
 7        }
 8 
 9        public synchronized void open() {
10               ++generation;
11               isOpen = true;
12               notifyAll();
13        }
14 
15        public synchronized void await() throws InterruptedException {
16               int arrivalGeneration = generation;
17               while (!isOpen && arrivalGeneration == generation)
18                      wait();
19        }
20 }
21 
22 可重新關閉的閥門

arrivalGeneration == generation為了保證在閥門打開時又立即關閉時,在打開時通知的線程都可以通過閥門

6、子類的安全問題

  • 如果在實施子類化時違背了條件通知或單詞通知的某個需求,那么在子類中可以增加合適的通知機制來代表基類
  • 對于狀態依賴的類,要么將其等待和通知等協議完全向子類公開(并且寫入正式文檔),要么完全阻止子類參與到等待和通知等過程中
  • 完全禁止子類化

7、封裝條件隊列

8、入口協議和出口協議

  • 入口協議:該操作的條件謂詞
  • 出口協議:檢查被該操作修改的所有狀態變量,并確認它們是否使某個其他的條件謂詞變為真,如果是,則通知相關的條件隊列

?

三、顯示的Condition對象

內置條件隊列的缺點:每個內置鎖都只能有一個相關聯的條件隊列,而多個線程可能在同一條件隊列上等待不同的條件謂詞,調用notifyAll通知的線程非等待同意謂詞

Condition <-> Lock,內置條件隊列 <-> 內置鎖

  • Lock.newCondition()
  • 在每個鎖上可存在多個等待、條件等待可以是可中斷的或不可中斷的、基于時限的等待,以及公平的或非公平的隊列操作
  • Condition對象繼承了相關的Lock對象的公平性
  • 與wait、notify和notifyAll方法對應的分別是await、signal和signalAll
  • 將多個條件謂詞分開并放到多個等待線程集,Condition使其更容易滿足單次通知的需求(signal比signalAll更高效)
  • 鎖、條件謂詞和條件變量:件謂詞中包含的變量必須由Lock來保護,并且在檢查條件謂詞以及調用await和signal時,必須持有Lock對象

?

 1 public class ConditionBoundedBuffer<T> {2     protected final Lock lock = new ReentrantLock();3     private final Condition notFull    = lock.newCondition();//條件:count < items.length4     private final Condition notEmpty  = lock.newCondition();//條件:count > 05     private final T[] items = (T[]) new Object[100];6     private int tail, head, count;7 8     public void put(T x) throws InterruptedException {9         lock.lock();
10         try {
11             while (count == items.length)
12                 notFull.await();//等到條件count < items.length滿足
13             items[tail] = x;
14             if (++tail == items.length)
15                 tail = 0;
16             ++count;
17             notEmpty.signal();//通知讀取等待線程
18         } finally {
19             lock.unlock();
20         }
21     }
22 
23     public T take() throws InterruptedException {
24         lock.lock();
25         try {
26             while (count == 0)
27                 notEmpty.await();//等到條件count > 0滿足
28             T x = items[head];
29             items[head] = null;
30             if (++head == items.length)
31                 head = 0;
32             --count;
33             notFull.signal();//通知寫入等待線程
34             return x;
35         } finally {
36             lock.unlock();
37         }
38     }
39 }

??

四、Synchronizer解析

  在ReentrantLock和Semaphore這兩個接口之間存在許多共同點。兩個類都可以用作一個”閥門“,即每次只允許一定數量的線程通過,并當線程到達閥門時,可以通過(在調用lock或acquire時成功返回),也可以等待(在調用lock或acquire時阻塞),還可以取消(在調用tryLock或tryAcquire時返回”假“,表示在指定的時間內鎖是不可用的或者無法獲取許可)。而且,這兩個接口都支持中斷不可中斷的以及限時的獲取操作,并且也都支持等待線程執行公平或非公平的隊列操作。

原因:都實現了同一個基類AbstractQueuedSynchronizer(AQS)

?

 1 public class SemaphoreOnLock {//基于Lock的Semaphore實現
 2        private final Lock lock = new ReentrantLock();
 3        //條件:permits > 0
 4        private final Condition permitsAvailable = lock.newCondition();
 5        private int permits;//許可數
 6 
 7        SemaphoreOnLock(int initialPermits) {
 8               lock.lock();
 9               try {
10                      permits = initialPermits;
11               } finally {
12                      lock.unlock();
13               }
14        }
15 
16        //頒發許可,條件是:permits > 0
17        public void acquire() throws InterruptedException {
18               lock.lock();
19               try {
20                      while (permits <= 0)//如果沒有許可,則等待
21                             permitsAvailable.await();
22                      --permits;//用一個少一個
23               } finally {
24                      lock.unlock();
25               }
26        }
27 
28        //歸還許可
29        public void release() {
30               lock.lock();
31               try {
32                      ++permits;
33                      permitsAvailable.signal();
34               } finally {
35                      lock.unlock();
36               }
37        }
38 }
39 
40 使用Lock實現信號量

?

 1 public class LockOnSemaphore {//基于Semaphore的Lock實現
 2        //具有一個信號量的Semaphore就相當于Lock
 3        private final Semaphore s = new Semaphore(1);
 4 
 5        //獲取鎖
 6        public void lock() throws InterruptedException {
 7               s.acquire();
 8        }
 9 
10        //釋放鎖
11        public void unLock() {
12               s.release();
13        }
14 }
15 
16 使用信號量實現Lock

?

五、AbstractQueuedSynchronizer

最基本的操作:

  • 獲取操作是一種依賴狀態的操作,并且通常會阻塞(同步器判斷當前狀態是否允許獲得操作,更新同步器的狀態)
  • 釋放并不是一個可阻塞的操作時,當執行“釋放”操作時,所有在請求時被阻塞的線程都會開始執行

狀態管理(一個整數狀態):

  • 通過getState,setState以及compareAndSetState等protected類型方法來進行操作
  • 這個整數在不同子類表示任意狀態。例:剩余的許可數量,任務狀態
  • 子類可以添加額外狀態

?

六、java.util.concurrent 同步器類中的AQS

1、ReentrantLock

  ReentrantLock只支持獨占方式的獲取操作,因此它實現了tryAcquire、tryRelease和isHeldExclusively

  ReentrantLock將同步狀態用于保存鎖獲取操作的次數,或者正要釋放鎖的時候,才會修改這個變量

2、Semaphore與CountDownLatch

  Semaphore將AQS的同步狀態用于保存當前可用許可的數量;CountDownLatch使用AQS的方式與Semaphore很相似,在同步狀態中保存的是當前的計數值

3、FutureTask

  在FutureTask中,AQS同步狀態被用來保存任務的狀態

  FutureTask還維護一些額外的狀態變量,用來保存計算結果或者拋出的異常

4、ReentrantReadWriteLock

  • 單個AQS子類將同時管理讀取加鎖和寫入加鎖
  • ReentrantReadWriteLock使用了一個16位的狀態來表示寫入鎖的計數,并且使用了另一個16位的狀態來表示讀取鎖的計數
  • 在讀取鎖上的操作將使用共享的獲取方法與釋放方法,在寫入鎖上的操作將使用獨占的獲取方法與釋放方法
  • AQS在內部維護了一個等待線程隊列,其中記錄了某個線程請求的是獨占訪問還是共享訪問:寫操作獨占獲取;讀操作可使第一個寫之前的讀都獲取

轉載于:https://www.cnblogs.com/linghu-java/p/9029011.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/389658.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/389658.shtml
英文地址,請注明出處:http://en.pswp.cn/news/389658.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關于之前的函數式編程

之前寫的函數式編程是我從 JavaScript ES6 函數式編程入門經典這本書里面整理的&#xff0c;然后只在第一篇里專門提到了&#xff0c;后面的話沒有專門提到&#xff0c;而且引用了書中大量的文字&#xff0c;所以我把掘金這里的文章都刪除了&#xff0c;然后在 CSDN 上面每一篇…

袋裝決策樹_袋裝樹是每個數據科學家需要的機器學習算法

袋裝決策樹袋裝樹木介紹 (Introduction to Bagged Trees) Without diving into the specifics just yet, it’s important that you have some foundation understanding of decision trees.尚未深入研究細節&#xff0c;對決策樹有一定基礎了解就很重要。 From the evaluatio…

[JS 分析] 天_眼_查 字體文件

0. 參考 js分析 貓_眼_電_影 字體文件 font-face 1. 分析 1.1 定位目標元素 1.2 查看網頁源代碼 1.3 requests 請求提取得到大量錯誤信息 對比貓_眼_電_影抓取到unicode編碼&#xff0c;天_眼_查混合使用正常字體和自定義字體&#xff0c;難點在于如何從 紅 轉化為 美。 一開始…

深入學習Redis(4):哨兵

前言在 深入學習Redis&#xff08;3&#xff09;&#xff1a;主從復制 中曾提到&#xff0c;Redis主從復制的作用有數據熱備、負載均衡、故障恢復等&#xff1b;但主從復制存在的一個問題是故障恢復無法自動化。本文將要介紹的哨兵&#xff0c;它基于Redis主從復制&#xff0c;…

1805. 字符串中不同整數的數目

1805. 字符串中不同整數的數目 給你一個字符串 word &#xff0c;該字符串由數字和小寫英文字母組成。 請你用空格替換每個不是數字的字符。例如&#xff0c;“a123bc34d8ef34” 將會變成 " 123 34 8 34" 。注意&#xff0c;剩下的這些整數為&#xff08;相鄰彼此至…

經天測繪測量工具包_公共土地測量系統

經天測繪測量工具包部分-鄉鎮第一師 (Sections — First Divisions of Townships) The PLSS Townships are typically divided into 36 Sections (nominally one mile on a side), but in the national standard this feature is called the first division because Townships …

洛谷 P4012 深海機器人問題【費用流】

題目鏈接&#xff1a;https://www.luogu.org/problemnew/show/P4012 洛谷 P4012 深海機器人問題 輸入輸出樣例 輸入樣例#1&#xff1a; 1 1 2 2 1 2 3 4 5 6 7 2 8 10 9 3 2 0 0 2 2 2 輸出樣例#1&#xff1a; 42 說明 題解&#xff1a;建圖方法如下&#xff1a; 對于矩陣中的每…

day5 模擬用戶登錄

_user "yangtuo" _passwd "123456"# passd_authentication False #flag 標志位for i in range(3): #for 語句后面可以跟else&#xff0c;但是不能跟elifusername input("Username:")password input("Password:")if username _use…

opencv實現對象跟蹤_如何使用opencv跟蹤對象的距離和角度

opencv實現對象跟蹤介紹 (Introduction) Tracking the distance and angle of an object has many practical uses, especially in robotics. This tutorial explains how to get an accurate distance and angle measurement, even when the target is at a strong angle from…

spring cloud 入門系列七:基于Git存儲的分布式配置中心--Spring Cloud Config

我們前面接觸到的spring cloud組件都是基于Netflix的組件進行實現的&#xff0c;這次我們來看下spring cloud 團隊自己創建的一個全新項目&#xff1a;Spring Cloud Config.它用來為分布式系統中的基礎設施和微服務提供集中化的外部配置支持&#xff0c;分為服務端和客戶端兩個…

458. 可憐的小豬

458. 可憐的小豬 有 buckets 桶液體&#xff0c;其中 正好 有一桶含有毒藥&#xff0c;其余裝的都是水。它們從外觀看起來都一樣。為了弄清楚哪只水桶含有毒藥&#xff0c;你可以喂一些豬喝&#xff0c;通過觀察豬是否會死進行判斷。不幸的是&#xff0c;你只有 minutesToTest…

熊貓數據集_大熊貓數據框的5個基本操作

熊貓數據集Tips and Tricks for Data Science數據科學技巧與竅門 Pandas is a powerful and easy-to-use software library written in the Python programming language, and is used for data manipulation and analysis.Pandas是使用Python編程語言編寫的功能強大且易于使用…

圖嵌入綜述 (arxiv 1709.07604) 譯文五、六、七

應用 圖嵌入有益于各種圖分析應用&#xff0c;因為向量表示可以在時間和空間上高效處理。 在本節中&#xff0c;我們將圖嵌入的應用分類為節點相關&#xff0c;邊相關和圖相關。 節點相關應用 節點分類 節點分類是基于從標記節點習得的規則&#xff0c;為圖中的每個節點分配類標…

聊聊自動化測試框架

無論是在自動化測試實踐&#xff0c;還是日常交流中&#xff0c;經常聽到一個詞&#xff1a;框架。之前學習自動化測試的過程中&#xff0c;一直對“框架”這個詞知其然不知其所以然。 最近看了很多自動化相關的資料&#xff0c;加上自己的一些實踐&#xff0c;算是對“框架”有…

1971. Find if Path Exists in Graph

1971. Find if Path Exists in Graph 有一個具有 n個頂點的 雙向 圖&#xff0c;其中每個頂點標記從 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。圖中的邊用一個二維整數數組 edges 表示&#xff0c;其中 edges[i] [ui, vi] 表示頂點 ui 和頂點 vi 之間的雙向邊。 …

移動磁盤文件或目錄損壞且無法讀取資料如何找回

文件或目錄損壞且無法讀取說明這個盤的文件系統結構損壞了。在平時如果數據不重要&#xff0c;那么可以直接格式化就能用了。但是有的時候里面的數據很重要&#xff0c;那么就必須先恢復出數據再格式化。具體恢復方法可以看正文了解&#xff08;不格式化的恢復方法&#xff09;…

python 平滑時間序列_時間序列平滑以實現更好的聚類

python 平滑時間序列In time series analysis, the presence of dirty and messy data can alter our reasonings and conclusions. This is true, especially in this domain, because the temporal dependency plays a crucial role when dealing with temporal sequences.在…

基于SmartQQ協議的QQ自動回復機器人-1

0. 本項目的原始代碼及我二次開發后的代碼 1. 軟件安裝:【myeclipse6.0 maven2】 0. https://blog.csdn.net/zgmzyr/article/details/6886440 1. https://blog.csdn.net/shuzhe66/article/details/45009175 2. https://www.cnblogs.com/whgk/p/7112560.html<mirror><…

1725. 可以形成最大正方形的矩形數目

1725. 可以形成最大正方形的矩形數目 給你一個數組 rectangles &#xff0c;其中 rectangles[i] [li, wi] 表示第 i 個矩形的長度為 li 、寬度為 wi 。 如果存在 k 同時滿足 k < li 和 k < wi &#xff0c;就可以將第 i 個矩形切成邊長為 k 的正方形。例如&#xff0c…

幫助學生改善學習方法_學生應該如何花費時間改善自己的幸福

幫助學生改善學習方法There have been numerous studies looking into the relationship between sleep, exercise, leisure, studying and happiness. The results were often quite like how we expected, though there have been debates about the relationship between sl…