自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

有了CopyOnWrite為何又要有ReadWriteLock?

開發(fā) 前端
ReentrantReadWriteLock的饑餓問(wèn)題如何解決?(ReentrantReadWriteLock實(shí)現(xiàn)了讀寫分離,想要獲取讀鎖就必須確保當(dāng)前沒有其他任何讀寫鎖了,但是一旦讀操作比較多的時(shí)候,想要獲取寫鎖就變得比較困難了,因?yàn)楫?dāng)前有可能會(huì)一直存在讀鎖。而無(wú)法獲得寫鎖。

[[396048]]

 引言

前文我們有介紹《看了CopyOnWriteArrayList后自己實(shí)現(xiàn)了一個(gè)CopyOnWriteHashMap》 關(guān)于CopyOnWrite容器的,但是它也有一些缺點(diǎn):

  • 內(nèi)存占用問(wèn)題:因?yàn)镃opyOnWrite的寫時(shí)復(fù)制機(jī)制每次進(jìn)行寫操作的時(shí)候都會(huì)有兩個(gè)數(shù)組對(duì)象的內(nèi)存,如果這個(gè)數(shù)組對(duì)象占用的內(nèi)存較大的話,如果頻繁的進(jìn)行寫入就會(huì)造成頻繁的Yong GC和Full GC
  • 數(shù)據(jù)一致性問(wèn)題:CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性,不能保證數(shù)據(jù)的實(shí)時(shí)一致性。讀操作的線程可能不會(huì)立即讀取到新修改的數(shù)據(jù),因?yàn)樾薷牟僮靼l(fā)生在副本上。但最終修改操作會(huì)完成并更新容器所以這是最終一致性。當(dāng)時(shí)有說(shuō)到解決這兩個(gè)缺點(diǎn)我們可以使用Collections.synchronizedList()來(lái)替代,找個(gè)無(wú)非就是對(duì)list的增刪改查方法都加了synchronized實(shí)現(xiàn)。我們知道synchronized其實(shí)是一個(gè)獨(dú)占鎖 (排他鎖),如果不知道什么是獨(dú)占鎖的可以看看這個(gè)文章《史上最全 Java 中各種鎖的介紹》 里面基本上把java里面的鎖都介紹完了。但是這樣的話就會(huì)存在一個(gè)性能問(wèn)題,如果對(duì)于讀多寫少的場(chǎng)景,每次讀也要去獲取鎖,讀完了之后再釋放鎖,這樣就造成了每個(gè)讀的請(qǐng)求都要進(jìn)行獲取鎖,但是讀的話并不會(huì)引起數(shù)據(jù)不安全,這樣就會(huì)造成一個(gè)性能瓶頸。為了解決這個(gè)問(wèn)題,就又出現(xiàn)了一種新的鎖,讀寫鎖(ReadWriteLock)。

什么是讀寫鎖

根據(jù)名字我們也可以猜個(gè)大概,就是有兩把鎖,分別是讀鎖和寫鎖。讀鎖在同一時(shí)刻可以允許多個(gè)讀線程獲取,但是在寫線程訪問(wèn)的時(shí)候,所有的讀線程和其他寫線程都會(huì)被阻塞。寫鎖同一時(shí)刻只能有一個(gè)寫線程獲取成功,其他都會(huì)被阻塞。讀寫鎖實(shí)際維護(hù)了兩把鎖,一個(gè)讀鎖和一個(gè)寫鎖,通過(guò)讀鎖和寫鎖進(jìn)行區(qū)分,在讀多寫少的情況下并發(fā)性比獨(dú)占鎖有了很大的提升。在java里面對(duì)讀寫鎖的實(shí)現(xiàn)就是ReentrantReadWriteLock,它有以下特性:

  • 公平性選擇:支持非公平性(默認(rèn))和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平;
  • 重入性:支持重入,讀鎖獲取后能再次獲取,寫鎖獲取之后能夠再次獲取寫鎖,同時(shí)也能夠獲取讀鎖;
  • 鎖降級(jí):遵循獲取寫鎖,獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級(jí)成為讀鎖

ReentrantReadWriteLock 的使用

我們先從官網(wǎng)來(lái)個(gè)事例https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html,看看它是如何使用的

  1. class RWDictionary { 
  2.   private final Map<String, Data> m = new TreeMap<String, Data>(); 
  3.   private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
  4.   private final Lock r = rwl.readLock(); 
  5.   private final Lock w = rwl.writeLock(); 
  6.  
  7.   public Data get(String key) { 
  8.     r.lock(); 
  9.     try { return m.get(key); } 
  10.     finally { r.unlock(); } 
  11.   } 
  12.   public String[] allKeys() { 
  13.     r.lock(); 
  14.     try { return m.keySet().toArray(); } 
  15.     finally { r.unlock(); } 
  16.   } 
  17.   public Data put(String key, Data value) { 
  18.     w.lock(); 
  19.     try { return m.put(key, value); } 
  20.     finally { w.unlock(); } 
  21.   } 
  22.   public void clear() { 
  23.     w.lock(); 
  24.     try { m.clear(); } 
  25.     finally { w.unlock(); } 
  26.   } 

這個(gè)使用起來(lái)還是非常簡(jiǎn)單明了的,跟ReentrantLock的用法基本一致,寫的時(shí)候獲取寫鎖,寫完了釋放寫鎖,讀的時(shí)候獲取讀鎖,讀完了就釋放讀寫。

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

我們知道ReentrantLock是通過(guò)state來(lái)控制鎖的狀態(tài),以及前面所介紹的《Java高并發(fā)編程基礎(chǔ)三大利器之Semaphore》《Java高并發(fā)編程基礎(chǔ)三大利器之CountDownLatch》《Java高并發(fā)編程基礎(chǔ)三大利器之CyclicBarrier》 都是通過(guò)state來(lái)進(jìn)行實(shí)現(xiàn)的那ReentrantReadWriteLock毋庸置疑肯定也是通過(guò)AQS的state來(lái)實(shí)現(xiàn)的,不過(guò)state是一個(gè)int值它是如何來(lái)讀鎖和寫鎖的。

讀寫鎖狀態(tài)的實(shí)現(xiàn)分析

如果我們有看過(guò)線程池的源碼,我們知道線程池的狀態(tài)和線程數(shù)是通過(guò)一個(gè)int類型原子變量(高3位保存運(yùn)行狀態(tài),低29位保存線程數(shù))來(lái)控制的。同樣的ReentrantReadWriteLock也是通過(guò)一個(gè)state的高16位和低16位來(lái)分別控制讀的狀態(tài)和寫狀態(tài)。

下面我們就來(lái)看看它是如何通過(guò)一個(gè)字段來(lái)實(shí)現(xiàn)讀寫分離的,

  1. static final int SHARED_SHIFT   = 16; 
  2.  static final int SHARED_UNIT    = (1 << SHARED_SHIFT); 
  3.  static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; 
  4.  static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; 
  5.  
  6.  /** Returns the number of shared holds represented in count  */ 
  7.  static int sharedCount(int c)    { return c >>> SHARED_SHIFT; } 
  8.  /** Returns the number of exclusive holds represented in count  */ 
  9.  static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } 
  • sharedCount : 讀鎖數(shù)量 是將同步狀態(tài)(int c)無(wú)符號(hào)右移16位,即取同步狀態(tài)的高16位。
  • exclusiveCount:寫鎖數(shù)量 我們要看下EXCLUSIVE_MASK 這個(gè)靜態(tài)變量:它是1進(jìn)行左移16位然后減1也就是0X0000FFFF即 (1 << SHARED_SHIFT) - 1= 0X0000FFFF 所以exclusiveCount 就是相當(dāng)于 c&0X0000FFFF 所以也就是低16位用來(lái)表示寫鎖的獲取次數(shù)。

源碼分析

基于jdk1.8 既然ReentrantReadWriteLock也是基于AQS來(lái)實(shí)現(xiàn)的,那么它肯定是重寫了AQS的獲取鎖的方法,那我們就直接去ReentrantReadWriteLock這個(gè)類里面看看lock的地方我們先看看獲取讀鎖的地方

  1. protected final boolean tryAcquire(int acquires) { 
  2.           /* 
  3.            * Walkthrough: 
  4.            * 1. If read count nonzero or write count nonzero 
  5.            *    and owner is a different thread, fail. 
  6.            * 2. If count would saturate, fail. (This can only 
  7.            *    happen if count is already nonzero.) 
  8.            * 3. Otherwise, this thread is eligible for lock if 
  9.            *    it is either a reentrant acquire or 
  10.            *    queue policy allows it. If so, update state 
  11.            *    and set owner. 
  12.            */ 
  13.           Thread current = Thread.currentThread(); 
  14.           // 獲取寫鎖當(dāng)前的同步狀態(tài) 
  15.           int c = getState(); 
  16.           // 寫鎖次數(shù) 
  17.           int w = exclusiveCount(c); 
  18.           if (c != 0) { 
  19.               // (Note: if c != 0 and w == 0 then shared count != 0) 
  20.               // 當(dāng)前狀態(tài)不為0,但是寫鎖為0 就說(shuō)明讀鎖不為0 
  21.              // 當(dāng)讀鎖已被讀線程獲取或者當(dāng)前線程不是已經(jīng)獲取寫鎖的線程的話獲取寫鎖失敗 
  22.               if (w == 0 || current != getExclusiveOwnerThread()) 
  23.                   return false
  24.               if (w + exclusiveCount(acquires) > MAX_COUNT) 
  25.                   throw new Error("Maximum lock count exceeded"); 
  26.               // Reentrant acquire 獲取到寫鎖 
  27.               setState(c + acquires); 
  28.               return true
  29.           } 
  30.              //writerShouldBlock 公平鎖和非公平鎖的判斷 
  31.           if (writerShouldBlock() || 
  32.               !compareAndSetState(c, c + acquires)) 
  33.               return false
  34.           setExclusiveOwnerThread(current); 
  35.           return true
  36.       } 

寫鎖完了,接下來(lái)肯定就是讀鎖了由于讀鎖是共享鎖,所以也應(yīng)該重寫了tryAcquireShared 這個(gè)就不貼代碼了,和讀鎖差不多這個(gè)就不做分析了。其實(shí)把AQS弄明白了再來(lái)看這些基于AQS來(lái)實(shí)現(xiàn)的玩意還是比較容易的。

讀寫鎖的升級(jí)與降級(jí)

前面我們有提到讀寫鎖是可以降級(jí)的,但是沒有說(shuō)是否可以升級(jí)。我們先看看什么是鎖降級(jí)和鎖升級(jí)

  • 鎖降級(jí):從寫鎖變成讀鎖;它的過(guò)程是先持有寫鎖,在獲取讀鎖,再釋放寫鎖。如果是持有寫鎖,釋放寫鎖,再獲取讀鎖這種情況不是鎖降級(jí)。
  • 為什么要鎖降級(jí)?

主要是為了保證數(shù)據(jù)的可見性,如果當(dāng)前線程不獲取讀鎖而是直接釋放寫鎖, 假設(shè)此刻另一個(gè)線程(記作線程T)獲取了寫鎖并修改了數(shù)據(jù),那么當(dāng)前線程無(wú)法感知線程T的數(shù)據(jù)更新。如果當(dāng)前線程獲取讀鎖,即遵循鎖降級(jí)的步驟,則線程T將會(huì)被阻塞,直到當(dāng)前線程使用數(shù)據(jù)并釋放讀鎖之后,線程T才能獲取寫鎖進(jìn)行數(shù)據(jù)更新。來(lái)源于《Java 并發(fā)編程的藝術(shù)》”

  • 鎖升級(jí):從讀鎖變成寫鎖。先持有讀鎖,再去獲取寫鎖(這是不會(huì)成功的)因?yàn)楂@取寫鎖是獨(dú)占鎖,如果有讀鎖被占用了,寫鎖就會(huì)放入隊(duì)列中等待,直至讀鎖全部被釋放之后才有可能獲取到寫鎖。

思考題

  • 本篇文章主要介紹了單機(jī)情況的讀寫鎖,如果要實(shí)現(xiàn)一個(gè)分布式的讀寫鎖該如何實(shí)現(xiàn)?
  • ReentrantReadWriteLock的饑餓問(wèn)題如何解決?(ReentrantReadWriteLock實(shí)現(xiàn)了讀寫分離,想要獲取讀鎖就必須確保當(dāng)前沒有其他任何讀寫鎖了,但是一旦讀操作比較多的時(shí)候,想要獲取寫鎖就變得比較困難了,因?yàn)楫?dāng)前有可能會(huì)一直存在讀鎖。而無(wú)法獲得寫鎖。)

結(jié)束

由于自己才疏學(xué)淺,難免會(huì)有紕漏,假如你發(fā)現(xiàn)了錯(cuò)誤的地方,還望留言給我指出來(lái),我會(huì)對(duì)其加以修正。

本文轉(zhuǎn)載自微信公眾號(hào)「java金融」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系java金融公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: java金融
相關(guān)推薦

2023-01-12 09:01:01

MongoDBMySQL

2024-09-04 08:00:00

安全黑客

2020-08-19 07:45:36

CopyOnwrite數(shù)據(jù)庫(kù)

2022-07-12 08:56:18

公平鎖非公平鎖Java

2024-04-15 08:32:11

線程讀寫鎖數(shù)據(jù)庫(kù)

2022-07-11 10:47:46

容器JAVA

2020-09-17 09:42:26

TikTok

2018-03-13 11:44:55

金融云銀行上云

2020-09-24 06:47:06

ServiceMesh模式

2011-12-31 09:11:08

OracleAMD

2020-10-18 17:17:54

深度學(xué)習(xí)優(yōu)化器人工智能

2014-04-09 11:04:31

OpenSSL安全漏洞OpenSSL漏洞

2016-10-10 08:38:40

Windows 10備份格式化

2015-10-26 10:34:20

IaaS持續(xù)交付

2013-07-17 10:16:57

Github項(xiàng)目許可證

2016-11-03 05:54:05

Iphone蘋果科技新聞早報(bào)

2024-12-09 10:17:17

2025-04-17 07:00:00

大數(shù)據(jù)數(shù)據(jù)治理數(shù)字化

2011-03-16 09:26:41

ReadWriteLoJava

2015-10-12 10:15:22

更新平壤時(shí)間Windows
點(diǎn)贊
收藏

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