成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java 并發(fā)包中的讀寫(xiě)鎖及其實(shí)現(xiàn)分析

開(kāi)發(fā) 后端
在Java并發(fā)包中常用的鎖(如:ReentrantLock),基本上都是排他鎖,這些鎖在同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問(wèn),而讀寫(xiě)鎖在同一時(shí) 刻可以允許多個(gè)讀線程訪問(wèn),但是在寫(xiě)線程訪問(wèn)時(shí),所有的讀線程和其他寫(xiě)線程均被阻塞。

1. 前言

在Java并發(fā)包中常用的鎖(如:ReentrantLock),基本上都是排他鎖,這些鎖在同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問(wèn),而讀寫(xiě)鎖在同一時(shí) 刻可以允許多個(gè)讀線程訪問(wèn),但是在寫(xiě)線程訪問(wèn)時(shí),所有的讀線程和其他寫(xiě)線程均被阻塞。讀寫(xiě)鎖維護(hù)了一對(duì)鎖,一個(gè)讀鎖和一個(gè)寫(xiě)鎖,通過(guò)分離讀鎖和寫(xiě)鎖,使得 并發(fā)性相比一般的排他鎖有了很大提升。

除了保證寫(xiě)操作對(duì)讀操作的可見(jiàn)性以及并發(fā)性的提升之外,讀寫(xiě)鎖能夠簡(jiǎn)化讀寫(xiě)交互場(chǎng)景的編程方式。假設(shè)在程序中定義一個(gè)共享的數(shù)據(jù)結(jié)構(gòu)用作緩存,它大部分時(shí)間提供讀服務(wù)(例如:查詢(xún)和搜索),而寫(xiě)操作占有的時(shí)間很少,但是寫(xiě)操作完成之后的更新需要對(duì)后續(xù)的讀服務(wù)可見(jiàn)。

在沒(méi)有讀寫(xiě)鎖支持的(Java 5 之前)時(shí)候,如果需要完成上述工作就要使用Java的等待通知機(jī)制,就是當(dāng)寫(xiě)操作開(kāi)始時(shí),所有晚于寫(xiě)操作的讀操作均會(huì)進(jìn)入等待狀態(tài),只有寫(xiě)操作完成并進(jìn)行 通知之后,所有等待的讀操作才能繼續(xù)執(zhí)行(寫(xiě)操作之間依靠synchronized關(guān)鍵字進(jìn)行同步),這樣做的目的是使讀操作都能讀取到正確的數(shù)據(jù),而不 會(huì)出現(xiàn)臟讀。改用讀寫(xiě)鎖實(shí)現(xiàn)上述功能,只需要在讀操作時(shí)獲取讀鎖,而寫(xiě)操作時(shí)獲取寫(xiě)鎖即可,當(dāng)寫(xiě)鎖被獲取到時(shí),后續(xù)(非當(dāng)前寫(xiě)操作線程)的讀寫(xiě)操作都會(huì)被 阻塞,寫(xiě)鎖釋放之后,所有操作繼續(xù)執(zhí)行,編程方式相對(duì)于使用等待通知機(jī)制的實(shí)現(xiàn)方式而言,變得簡(jiǎn)單明了。

一般情況下,讀寫(xiě)鎖的性能都會(huì)比排它鎖要好,因?yàn)榇蠖鄶?shù)場(chǎng)景讀是多于寫(xiě)的。在讀多于寫(xiě)的情況下,讀寫(xiě)鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量。Java并發(fā)包提供讀寫(xiě)鎖的實(shí)現(xiàn)是ReentrantReadWriteLock,它提供的特性如表1所示。

表1. ReentrantReadWriteLock的特性

特性

說(shuō)明

公平性選擇

支持非公平(默認(rèn))和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平

重進(jìn)入

該鎖支持重進(jìn)入,以讀寫(xiě)線程為例:讀線程在獲取了讀鎖之后,能夠再次獲取讀鎖。而寫(xiě)線程在獲取了寫(xiě)鎖之后能夠再次獲取寫(xiě)鎖,同時(shí)也可以獲取讀鎖

鎖降級(jí)

遵循獲取寫(xiě)鎖、獲取讀鎖再釋放寫(xiě)鎖的次序,寫(xiě)鎖能夠降級(jí)成為讀鎖

2. 讀寫(xiě)鎖的接口與示例

ReadWriteLock僅定義了獲取讀鎖和寫(xiě)鎖的兩個(gè)方法,即readLock()和writeLock()方法,而其實(shí)現(xiàn)— ReentrantReadWriteLock,除了接口方法之外,還提供了一些便于外界監(jiān)控其內(nèi)部工作狀態(tài)的方法,這些方法以及描述如表2所示。

表2. ReentrantReadWriteLock展示內(nèi)部工作狀態(tài)的方法

方法名稱(chēng)

描述

int getReadLockCount()

返回當(dāng)前讀鎖被獲取的次數(shù)。該次數(shù)不等于獲取讀鎖的線程數(shù),比如:僅一個(gè)線程,它連續(xù)獲取(重進(jìn)入)了n次讀鎖,那么占據(jù)讀鎖的線程數(shù)是1,但該方法返回n

int getReadHoldCount()

返回當(dāng)前線程獲取讀鎖的次數(shù)。該方法在Java 6 中加入到ReentrantReadWriteLock中,使用ThreadLocal保存當(dāng)前線程獲取的次數(shù),這也使得Java 6 的實(shí)現(xiàn)變得更加復(fù)雜

boolean isWriteLocked()

判斷寫(xiě)鎖是否被獲取

int getWriteHoldCount()

返回當(dāng)前寫(xiě)鎖被獲取的次數(shù)

接下來(lái)通過(guò)一個(gè)緩存示例說(shuō)明讀寫(xiě)鎖的使用方式,示例代碼如代碼清單1所示。

代碼清單1. Cache.java

  1. public class Cache { 
  2.   static Map<String, Object> map = new HashMap<String, Object>(); 
  3.   static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
  4.   static Lock r = rwl.readLock(); 
  5.   static Lock w = rwl.writeLock(); 
  6.   // 獲取一個(gè)key對(duì)應(yīng)的value 
  7.   public static final Object get(String key) { 
  8.     r.lock(); 
  9.     try { 
  10.       return map.get(key); 
  11.     } finally { 
  12.       r.unlock(); 
  13.     } 
  14.   } 
  15.   // 設(shè)置key對(duì)應(yīng)的value,并返回舊有的value 
  16.   public static final Object put(String key, Object value) { 
  17.     w.lock(); 
  18.     try { 
  19.       return map.put(key, value); 
  20.     } finally { 
  21.       w.unlock(); 
  22.     } 
  23.   } 
  24.   // 清空所有的內(nèi)容 
  25.   public static final void clear() { 
  26.     w.lock(); 
  27.     try { 
  28.       map.clear(); 
  29.     } finally { 
  30.       w.unlock(); 
  31.     } 
  32.   } 

上述示例中,Cache組合了一個(gè)非線程安全的HashMap作為緩存的實(shí)現(xiàn),同時(shí)使用讀寫(xiě)鎖的讀鎖和寫(xiě)鎖來(lái)保證Cache是線程安全的。在讀操作 get(String key)方法中,需要獲取讀鎖,這使得并發(fā)訪問(wèn)該方法時(shí)不會(huì)被阻塞。寫(xiě)操作put(String key, Object value)和clear()方法,在更新HashMap時(shí)必須提前獲取寫(xiě)鎖,當(dāng)寫(xiě)鎖被獲取后,其他線程對(duì)于讀鎖和寫(xiě)鎖的獲取均被阻塞,而只有寫(xiě)鎖被釋放 之后,其他讀寫(xiě)操作才能繼續(xù)。Cache使用讀寫(xiě)鎖提升讀操作并發(fā)性,也保證每次寫(xiě)操作對(duì)所有的讀寫(xiě)操作的可見(jiàn)性,同時(shí)簡(jiǎn)化了編程方式。

3. 讀寫(xiě)鎖的實(shí)現(xiàn)分析

接下來(lái)將分析ReentrantReadWriteLock的實(shí)現(xiàn),主要包括:讀寫(xiě)狀態(tài)的設(shè)計(jì)、寫(xiě)鎖的獲取與釋放、讀鎖的獲取與釋放以及鎖降級(jí)(以下沒(méi)有特別說(shuō)明讀寫(xiě)鎖均可認(rèn)為是ReentrantReadWriteLock)。

3.1 讀寫(xiě)狀態(tài)的設(shè)計(jì)

讀寫(xiě)鎖同樣依賴(lài)自定義同步器來(lái)實(shí)現(xiàn)同步功能,而讀寫(xiě)狀態(tài)就是其同步器的同步狀態(tài)。回想ReentrantLock中自定義同步器的實(shí)現(xiàn),同步狀態(tài) 表示鎖被一個(gè)線程重復(fù)獲取的次數(shù),而讀寫(xiě)鎖的自定義同步器需要在同步狀態(tài)(一個(gè)整型變量)上維護(hù)多個(gè)讀線程和一個(gè)寫(xiě)線程的狀態(tài),使得該狀態(tài)的設(shè)計(jì)成為讀寫(xiě) 鎖實(shí)現(xiàn)的關(guān)鍵。

如果在一個(gè)整型變量上維護(hù)多種狀態(tài),就一定需要“按位切割使用”這個(gè)變量,讀寫(xiě)鎖是將變量切分成了兩個(gè)部分,高16位表示讀,低16位表示寫(xiě),劃分方式如圖1所示。

圖1. 讀寫(xiě)鎖狀態(tài)的劃分方式

Java并發(fā)包中的讀寫(xiě)鎖及其實(shí)現(xiàn)分析

如圖1所示,當(dāng)前同步狀態(tài)表示一個(gè)線程已經(jīng)獲取了寫(xiě)鎖,且重進(jìn)入了兩次,同時(shí)也連續(xù)獲取了兩次讀鎖。讀寫(xiě)鎖是如何迅速的確定讀和寫(xiě)各自的狀態(tài)呢? 答案是通過(guò)位運(yùn)算。假設(shè)當(dāng)前同步狀態(tài)值為S,寫(xiě)狀態(tài)等于 S & 0x0000FFFF(將高16位全部抹去),讀狀態(tài)等于 S >>> 16(無(wú)符號(hào)補(bǔ)0右移16位)。當(dāng)寫(xiě)狀態(tài)增加1時(shí),等于S + 1,當(dāng)讀狀態(tài)增加1時(shí),等于S + (1 << 16),也就是S + 0×00010000。

根據(jù)狀態(tài)的劃分能得出一個(gè)推論:S不等于0時(shí),當(dāng)寫(xiě)狀態(tài)(S & 0x0000FFFF)等于0時(shí),則讀狀態(tài)(S >>> 16)大于0,即讀鎖已被獲取。

3.2 寫(xiě)鎖的獲取與釋放

寫(xiě)鎖是一個(gè)支持重進(jìn)入的排它鎖。如果當(dāng)前線程已經(jīng)獲取了寫(xiě)鎖,則增加寫(xiě)狀態(tài)。如果當(dāng)前線程在獲取寫(xiě)鎖時(shí),讀鎖已經(jīng)被獲取(讀狀態(tài)不為0)或者該線程不是已經(jīng)獲取寫(xiě)鎖的線程,則當(dāng)前線程進(jìn)入等待狀態(tài),獲取寫(xiě)鎖的代碼如代碼清單2所示。

代碼清單2. ReentrantReadWriteLock的tryAcquire方法

  1. protected final boolean tryAcquire(int acquires) { 
  2.   Thread current = Thread.currentThread(); 
  3.   int c = getState(); 
  4.   int w = exclusiveCount(c); 
  5.   if (c != 0) { 
  6.     // 存在讀鎖或者當(dāng)前獲取線程不是已經(jīng)獲取寫(xiě)鎖的線程 
  7.     if (w == 0 || current != getExclusiveOwnerThread()) 
  8.       return false
  9.     if (w + exclusiveCount(acquires) > MAX_COUNT) 
  10.       throw new Error("Maximum lock count exceeded"); 
  11.     setState(c + acquires); 
  12.     return true
  13.   } 
  14.   if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) { 
  15.     return false
  16.   } 
  17.   setExclusiveOwnerThread(current); 
  18.   return true

該方法除了重入條件(當(dāng)前線程為獲取了寫(xiě)鎖的線程)之外,增加了一個(gè)讀鎖是否存在的判斷。如果存在讀鎖,則寫(xiě)鎖不能被獲取,原因在于:讀寫(xiě)鎖要確保 寫(xiě)鎖的操作對(duì)讀鎖可見(jiàn),如果允許讀鎖在已被獲取的情況下對(duì)寫(xiě)鎖的獲取,那么正在運(yùn)行的其他讀線程就無(wú)法感知到當(dāng)前寫(xiě)線程的操作。因此只有等待其他讀線程都 釋放了讀鎖,寫(xiě)鎖才能被當(dāng)前線程所獲取,而寫(xiě)鎖一旦被獲取,則其他讀寫(xiě)線程的后續(xù)訪問(wèn)均被阻塞。

寫(xiě)鎖的釋放與ReentrantLock的釋放過(guò)程基本類(lèi)似,每次釋放均減少寫(xiě)狀態(tài),當(dāng)寫(xiě)狀態(tài)為0時(shí)表示寫(xiě)鎖已被釋放,從而等待的讀寫(xiě)線程能夠繼續(xù)訪問(wèn)讀寫(xiě)鎖,同時(shí)前次寫(xiě)線程的修改對(duì)后續(xù)讀寫(xiě)線程可見(jiàn)。

3.3 讀鎖的獲取與釋放

讀鎖是一個(gè)支持重進(jìn)入的共享鎖,它能夠被多個(gè)線程同時(shí)獲取,在沒(méi)有其他寫(xiě)線程訪問(wèn)(或者寫(xiě)狀態(tài)為0)時(shí),讀鎖總會(huì)成功的被獲取,而所做的也只是 (線程安全的)增加讀狀態(tài)。如果當(dāng)前線程已經(jīng)獲取了讀鎖,則增加讀狀態(tài)。如果當(dāng)前線程在獲取讀鎖時(shí),寫(xiě)鎖已被其他線程獲取,則進(jìn)入等待狀態(tài)。獲取讀鎖的實(shí) 現(xiàn)從Java 5到Java 6變得復(fù)雜許多,主要原因是新增了一些功能,比如:getReadHoldCount()方法,返回當(dāng)前線程獲取讀鎖的次數(shù)。讀狀態(tài)是所有線程獲取讀鎖次 數(shù)的總和,而每個(gè)線程各自獲取讀鎖的次數(shù)只能選擇保存在ThreadLocal中,由線程自身維護(hù),這使獲取讀鎖的實(shí)現(xiàn)變得復(fù)雜。因此,這里將獲取讀鎖的 代碼做了刪減,保留必要的部分,代碼如代碼清單3所示。

代碼清單3. ReentrantReadWriteLock的tryAcquireShared方法

  1. protected final int tryAcquireShared(int unused) { 
  2.   for (;;) { 
  3.     int c = getState(); 
  4.     int nextc = c + (1 << 16); 
  5.     if (nextc < c) 
  6.       throw new Error("Maximum lock count exceeded"); 
  7.     if (exclusiveCount(c) != 0 && owner != Thread.currentThread()) 
  8.       return -1
  9.     if (compareAndSetState(c, nextc)) 
  10.       return 1
  11.   } 

在tryAcquireShared(int unused)方法中,如果其他線程已經(jīng)獲取了寫(xiě)鎖,則當(dāng)前線程獲取讀鎖失敗,進(jìn)入等待狀態(tài)。如果當(dāng)前線程獲取了寫(xiě)鎖或者寫(xiě)鎖未被獲取,則當(dāng)前線程(線程安全,依靠CAS保證)增加讀狀態(tài),成功獲取讀鎖。

讀鎖的每次釋放均(線程安全的,可能有多個(gè)讀線程同時(shí)釋放讀鎖)減少讀狀態(tài),減少的值是(1 << 16)。

3.4 鎖降級(jí)

鎖降級(jí)指的是寫(xiě)鎖降級(jí)成為讀鎖。如果當(dāng)前線程擁有寫(xiě)鎖,然后將其釋放,***再獲取讀鎖,這種分段完成的過(guò)程不能稱(chēng)之為鎖降級(jí)。鎖降級(jí)是指把持住(當(dāng)前擁有的)寫(xiě)鎖,再獲取到讀鎖,隨后釋放(先前擁有的)寫(xiě)鎖的過(guò)程。

接下來(lái)看一個(gè)鎖降級(jí)的示例:因?yàn)閿?shù)據(jù)不常變化,所以多個(gè)線程可以并發(fā)的進(jìn)行數(shù)據(jù)處理,當(dāng)數(shù)據(jù)變更后,當(dāng)前線程如果感知到數(shù)據(jù)變化,則進(jìn)行數(shù)據(jù)的準(zhǔn)備工作,同時(shí)其他處理線程被阻塞,直到當(dāng)前線程完成數(shù)據(jù)的準(zhǔn)備工作,示例代碼如代碼清單4所示。

代碼清單4. processData方法

  1. public void processData() { 
  2.   readLock.lock(); 
  3.   if (!update) { 
  4.     // 必須先釋放讀鎖 
  5.     readLock.unlock(); 
  6.     // 鎖降級(jí)從寫(xiě)鎖獲取到開(kāi)始 
  7.     writeLock.lock(); 
  8.     try { 
  9.       if (!update) { 
  10.         // 準(zhǔn)備數(shù)據(jù)的流程(略) 
  11.         update = true
  12.       } 
  13.       readLock.lock(); 
  14.     } finally { 
  15.       writeLock.unlock(); 
  16.     } 
  17.     // 鎖降級(jí)完成,寫(xiě)鎖降級(jí)為讀鎖 
  18.   } 
  19.   try { 
  20.     // 使用數(shù)據(jù)的流程(略) 
  21.   } finally { 
  22.     readLock.unlock(); 
  23.   } 

上述示例中,當(dāng)數(shù)據(jù)發(fā)生變更后,update變量(布爾類(lèi)型且Volatile修飾)被設(shè)置為false,此時(shí)所有訪問(wèn)processData() 方法的線程都能夠感知到變化,但只有一個(gè)線程能夠獲取到寫(xiě)鎖,而其他線程會(huì)被阻塞在讀鎖和寫(xiě)鎖的lock()方法上。當(dāng)前程獲取寫(xiě)鎖完成數(shù)據(jù)準(zhǔn)備之后,再 獲取讀鎖,隨后釋放寫(xiě)鎖,完成鎖降級(jí)。

鎖降級(jí)中讀鎖的獲取是否必要呢?答案是必要的。主要原因是保證數(shù)據(jù)的可見(jiàn)性,如果當(dāng)前線程不獲取讀鎖而是直接釋放寫(xiě)鎖,假設(shè)此刻另一個(gè)線程(記作 線程T)獲取了寫(xiě)鎖并修改了數(shù)據(jù),則當(dāng)前線程無(wú)法感知線程T的數(shù)據(jù)更新。如果當(dāng)前線程獲取讀鎖,即遵循鎖降級(jí)的步驟,則線程T將會(huì)被阻塞,直到當(dāng)前線程使 用數(shù)據(jù)并釋放讀鎖之后,線程T才能獲取寫(xiě)鎖進(jìn)行數(shù)據(jù)更新。

RentrantReadWriteLock不支持鎖升級(jí)(把持讀鎖、獲取寫(xiě)鎖,***釋放讀鎖的過(guò)程)。原因也是保證數(shù)據(jù)可見(jiàn)性,如果讀鎖已被多個(gè)線程獲取,其中任意線程成功獲取了寫(xiě)鎖并更新了數(shù)據(jù),則其更新對(duì)其他獲取到讀鎖的線程不可見(jiàn)。

責(zé)任編輯:王雪燕 來(lái)源: 魏鵬
相關(guān)推薦

2011-03-18 10:26:47

Java對(duì)象

2020-09-25 08:49:42

HashMap

2025-05-30 04:25:00

Java同步機(jī)制

2012-09-10 10:39:04

IBMdw

2024-08-12 17:36:54

2023-10-30 13:31:22

Springboot工具Java

2017-11-22 10:53:22

2012-03-07 10:34:44

Java

2024-01-29 01:08:01

悲觀鎖遞歸鎖讀寫(xiě)鎖

2023-01-04 13:43:24

讀寫(xiě)鎖AQS共享模式

2024-01-29 10:34:37

Java編程

2024-02-29 09:44:36

Java工具

2017-11-24 17:20:37

數(shù)據(jù)庫(kù)數(shù)據(jù)倉(cāng)庫(kù)讀寫(xiě)分離

2011-04-22 13:10:46

計(jì)算機(jī)邏輯門(mén)

2020-10-29 10:47:25

云計(jì)算容量管理

2022-12-31 18:13:10

2023-06-02 08:29:24

https://wwMutex

2019-08-14 15:08:51

緩存存儲(chǔ)數(shù)據(jù)

2019-11-11 15:33:34

高并發(fā)緩存數(shù)據(jù)

2020-08-16 11:37:27

Python開(kāi)發(fā)工具
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 中文字幕乱码一区二区三区 | 日韩精品一区二区三区在线 | 午夜视频在线 | 两性午夜视频 | 日本理论片好看理论片 | 97精品久久 | 午夜爽爽爽男女免费观看影院 | 色毛片 | 99在线免费视频 | 国产女人第一次做爰毛片 | 日韩高清中文字幕 | 久久99精品久久久 | 国产探花在线精品一区二区 | 久久国产精品久久久久久 | 国产在线中文字幕 | 久久久www成人免费无遮挡大片 | 国产精品91网站 | 91高清免费观看 | 亚洲高清视频一区二区 | 紧缚调教一区二区三区视频 | 国产精品国产三级国产aⅴ中文 | 一级a性色生活片久久毛片 一级特黄a大片 | 午夜成人免费视频 | 毛片在线视频 | 欧美一a一片一级一片 | 天天操欧美 | 成人在线观看网址 | 蜜桃特黄a∨片免费观看 | 国产高清视频 | 天天操天天射天天 | 免费福利视频一区二区三区 | 精品一区二区三区入口 | 日本高清不卡视频 | 成人片免费看 | 精品一区二区三区不卡 | 欧美视频区| 一区二区三区四区在线视频 | 国产精品国产精品 | 九九亚洲 | 国产三级精品三级在线观看四季网 | 一级全黄视频 |