Java中的ReentrantLock和synchronized兩種鎖定機制的對比

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

多線程和并發性并不是什么新內容,但是 Java 語言設計中的創新之一就是,它是第一個直接把跨平臺線程模型和正規的內存模型集成到語言中的主流語言。核心類庫包含一個?Thread?類,可以用它來構建、啟動和操縱線程,Java 語言包括了跨線程傳達并發性約束的構造 ——?synchronized?和?volatile?。在簡化與平臺無關的并發類的開發的同時,它決沒有使并發類的編寫工作變得更繁瑣,只是使它變得更容易了。

synchronized 快速回顧

把代碼塊聲明為 synchronized,有兩個重要后果,通常是指該代碼具有?原子性(atomicity)和?可見性(visibility)。原子性意味著一個線程一次只能執行由一個指定監控對象(lock)保護的代碼,從而防止多個線程在更新共享狀態時相互沖突。可見性則更為微妙;它要對付內存緩存和編譯器優化的各種反常行為。一般來說,線程以某種不必讓其他線程立即可以看到的方式(不管這些線程在寄存器中、在處理器特定的緩存中,還是通過指令重排或者其他編譯器優化),不受緩存變量值的約束,但是如果開發人員使用了同步,如下面的代碼所示,那么運行庫將確保某一線程對變量所做的更新先于對現有?synchronized?塊所進行的更新,當進入由同一監控器(lock)保護的另一個?synchronized?塊時,將立刻可以看到這些對變量所做的更新。類似的規則也存在于?volatile?變量上。

synchronized (lockObject) { // update object state
}

所以,實現同步操作需要考慮安全更新多個共享變量所需的一切,不能有爭用條件,不能破壞數據(假設同步的邊界位置正確),而且要保證正確同步的其他線程可以看到這些變量的最新值。通過定義一個清晰的、跨平臺的內存模型(該模型在 JDK 5.0 中做了修改,改正了原來定義中的某些錯誤),通過遵守下面這個簡單規則,構建“一次編寫,隨處運行”的并發類是有可能的:

不論什么時候,只要您將編寫的變量接下來可能被另一個線程讀取,或者您將讀取的變量最后是被另一個線程寫入的,那么您必須進行同步。

不過現在好了一點,在最近的 JVM 中,沒有爭用的同步(一個線程擁有鎖的時候,沒有其他線程企圖獲得鎖)的性能成本還是很低的。(也不總是這樣;早期 JVM 中的同步還沒有優化,所以讓很多人都這樣認為,但是現在這變成了一種誤解,人們認為不管是不是爭用,同步都有很高的性能成本。)


對 synchronized 的改進

如此看來同步相當好了,是么?那么為什么 JSR 166 小組花了這么多時間來開發?java.util.concurrent.lock?框架呢?答案很簡單-同步是不錯,但它并不完美。它有一些功能性的限制 —— 它無法中斷一個正在等候獲得鎖的線程,也無法通過投票得到鎖,如果不想等下去,也就沒法得到鎖。同步還要求鎖的釋放只能在與獲得鎖所在的堆棧幀相同的堆棧幀中進行,多數情況下,這沒問題(而且與異常處理交互得很好),但是,確實存在一些非塊結構的鎖定更合適的情況。

ReentrantLock 類

java.util.concurrent.lock?中的?Lock?框架是鎖定的一個抽象,它允許把鎖定的實現作為 Java 類,而不是作為語言的特性來實現。這就為Lock?的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。?ReentrantLock?類實現了?Lock?,它擁有與?synchronized?相同的并發性和內存語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上。)

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

在查看清單 1 中的代碼示例時,可以看到?Lock?和 synchronized 有一點明顯的區別 —— lock 必須在 finally 塊中釋放。否則,如果受保護的代碼將拋出異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什么,但是實際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會在程序中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放。


清單 1. 用 ReentrantLock 保護代碼塊。

Lock lock = new ReentrantLock();
lock.lock();
try {?// update object state
}
finally {lock.unlock();?
}


除此之外,與目前的 synchronized 實現相比,爭用下的?ReentrantLock?實現更具可伸縮性。(在未來的 JVM 版本中,synchronized 的爭用性能很有可能會獲得提高。)這意味著當許多線程都在爭用同一個鎖時,使用?ReentrantLock?的總體開支通常要比?synchronized少得多。


比較 ReentrantLock 和 synchronized 的可伸縮性

Tim Peierls 用一個簡單的線性全等偽隨機數生成器(PRNG)構建了一個簡單的評測,用它來測量?synchronized?和?Lock?之間相對的可伸縮性。這個示例很好,因為每次調用?nextRandom()?時,PRNG 都確實在做一些工作,所以這個基準程序實際上是在測量一個合理的、真實的?synchronized?和?Lock?應用程序,而不是測試純粹紙上談兵或者什么也不做的代碼(就像許多所謂的基準程序一樣。)

在這個基準程序中,有一個?PseudoRandom?的接口,它只有一個方法?nextRandom(int bound)?。該接口與?java.util.Random?類的功能非常類似。因為在生成下一個隨機數時,PRNG 用最新生成的數字作為輸入,而且把最后生成的數字作為一個實例變量來維護,其重點在于讓更新這個狀態的代碼段不被其他線程搶占,所以我要用某種形式的鎖定來確保這一點。(?java.util.Random?類也可以做到這點。)我們為?PseudoRandom?構建了兩個實現;一個使用 syncronized,另一個使用?java.util.concurrent.ReentrantLock?。驅動程序生成了大量線程,每個線程都瘋狂地爭奪時間片,然后計算不同版本每秒能執行多少輪。圖 1 和 圖 2 總結了不同線程數量的結果。這個評測并不完美,而且只在兩個系統上運行了(一個是雙 Xeon 運行超線程 Linux,另一個是單處理器 Windows 系統),但是,應當足以表現?synchronized?與?ReentrantLock?相比所具有的伸縮性優勢了。

圖 1 和圖 2 中的圖表以每秒調用數為單位顯示了吞吐率,把不同的實現調整到 1 線程?synchronized?的情況。每個實現都相對迅速地集中在某個穩定狀態的吞吐率上,該狀態通常要求處理器得到充分利用,把大多數的處理器時間都花在處理實際工作(計算機隨機數)上,只有小部分時間花在了線程調度開支上。您會注意到,synchronized 版本在處理任何類型的爭用時,表現都相當差,而?Lock?版本在調度的開支上花的時間相當少,從而為更高的吞吐率留下空間,實現了更有效的 CPU 利用。


條件變量

根類?Object?包含某些特殊的方法,用來在線程的?wait()?、?notify()?和?notifyAll()?之間進行通信。這些是高級的并發性特性,許多開發人員從來沒有用過它們 —— 這可能是件好事,因為它們相當微妙,很容易使用不當。幸運的是,隨著 JDK 5.0 中引入java.util.concurrent?,開發人員幾乎更加沒有什么地方需要使用這些方法了。

通知與鎖定之間有一個交互 —— 為了在對象上?wait?或?notify?,您必須持有該對象的鎖。就像?Lock?是同步的概括一樣,?Lock?框架包含了對?wait?和?notify?的概括,這個概括叫作?條件(Condition)?。?Lock?對象則充當綁定到這個鎖的條件變量的工廠對象,與標準的?wait?和?notify?方法不同,對于指定的?Lock?,可以有不止一個條件變量與它關聯。這樣就簡化了許多并發算法的開發。例如,?條件(Condition)?的 Javadoc 顯示了一個有界緩沖區實現的示例,該示例使用了兩個條件變量,“not full”和“not empty”,它比每個 lock 只用一個 wait 設置的實現方式可讀性要好一些(而且更有效)。?Condition?的方法與?wait?、?notify?和?notifyAll?方法類似,分別命名為?await?、?signal?和?signalAll?,因為它們不能覆蓋?Object?上的對應方法。


這不公平

如果查看 Javadoc,您會看到,?ReentrantLock?構造器的一個參數是 boolean 值,它允許您選擇想要一個?公平(fair)鎖,還是一個?不公平(unfair)鎖。公平鎖使線程按照請求鎖的順序依次獲得鎖;而不公平鎖則允許討價還價,在這種情況下,線程有時可以比先請求鎖的其他線程先得到鎖。

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

那么同步又如何呢?內置的監控器鎖是公平的嗎?答案令許多人感到大吃一驚,它們是不公平的,而且永遠都是不公平的。但是沒有人抱怨過線程饑渴,因為 JVM 保證了所有線程最終都會得到它們所等候的鎖。確保統計上的公平性,對多數情況來說,這就已經足夠了,而這花費的成本則要比絕對的公平保證的低得多。所以,默認情況下?ReentrantLock?是“不公平”的,這一事實只是把同步中一直是事件的東西表面化而已。如果您在同步的時候并不介意這一點,那么在?ReentrantLock?時也不必為它擔心。

圖 3 和圖 4 包含與 圖 1和 圖 2 相同的數據,只是添加了一個數據集,用來進行隨機數基準檢測,這次檢測使用了公平鎖,而不是默認的協商鎖。正如您能看到的,公平是有代價的。如果您需要公平,就必須付出代價,但是請不要把它作為您的默認選擇。


處處都好?

看起來?ReentrantLock?無論在哪方面都比?synchronized?好 —— 所有?synchronized?能做的,它都能做,它擁有與?synchronized?相同的內存和并發性語義,還擁有?synchronized?所沒有的特性,在負荷下還擁有更好的性能。那么,我們是不是應當忘記?synchronized?,不再把它當作已經已經得到優化的好主意呢?或者甚至用?ReentrantLock?重寫我們現有的?synchronized?代碼?實際上,幾本 Java 編程方面介紹性的書籍在它們多線程的章節中就采用了這種方法,完全用?Lock?來做示例,只把 synchronized 當作歷史。但我覺得這是把好事做得太過了。

還不要拋棄 synchronized

雖然?ReentrantLock?是個非常動人的實現,相對 synchronized 來說,它有一些重要的優勢,但是我認為急于把 synchronized 視若敝屣,絕對是個嚴重的錯誤。?java.util.concurrent.lock?中的鎖定類是用于高級用戶和高級情況的工具?。一般來說,除非您對?Lock?的某個高級特性有明確的需要,或者有明確的證據(而不是僅僅是懷疑)表明在特定情況下,同步已經成為可伸縮性的瓶頸,否則還是應當繼續使用 synchronized。

為什么我在一個顯然“更好的”實現的使用上主張保守呢?因為對于?java.util.concurrent.lock?中的鎖定類來說,synchronized 仍然有一些優勢。比如,在使用 synchronized 的時候,不能忘記釋放鎖;在退出?synchronized?塊時,JVM 會為您做這件事。您很容易忘記用?finally?塊釋放鎖,這對程序非常有害。您的程序能夠通過測試,但會在實際工作中出現死鎖,那時會很難指出原因(這也是為什么根本不讓初級開發人員使用?Lock?的一個好理由。)

另一個原因是因為,當 JVM 用 synchronized 管理鎖定請求和釋放時,JVM 在生成線程轉儲時能夠包括鎖定信息。這些對調試非常有價值,因為它們能標識死鎖或者其他異常行為的來源。?Lock?類只是普通的類,JVM 不知道具體哪個線程擁有?Lock?對象。而且,幾乎每個開發人員都熟悉 synchronized,它可以在 JVM 的所有版本中工作。在 JDK 5.0 成為標準(從現在開始可能需要兩年)之前,使用?Lock類將意味著要利用的特性不是每個 JVM 都有的,而且不是每個開發人員都熟悉的。

什么時候選擇用 ReentrantLock 代替 synchronized

既然如此,我們什么時候才應該使用?ReentrantLock?呢?答案非常簡單 —— 在確實需要一些 synchronized 所沒有的特性的時候,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票。?ReentrantLock?還具有可伸縮性的好處,應當在高度爭用的情況下使用它,但是請記住,大多數 synchronized 塊幾乎從來沒有出現過爭用,所以可以把高度爭用放在一邊。我建議用 synchronized 開發,直到確實證明 synchronized 不合適,而不要僅僅是假設如果使用?ReentrantLock?“性能會更好”。請記住,這些是供高級用戶使用的高級工具。(而且,真正的高級用戶喜歡選擇能夠找到的最簡單工具,直到他們認為簡單的工具不適用為止。)。一如既往,首先要把事情做好,然后再考慮是不是有必要做得更快

轉載于:https://my.oschina.net/u/4008390/blog/3014029

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

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

相關文章

10.15 lzxkj

幾天前寫的,忘了放了,在此填坑 10月16的題我出的不寫題解了 lzxkj 題目背景 眾所不周知的是, 酒店之王 xkj 一個經常迷失自我的人 有一天, 當起床鈴再一次打響的時候, TA 用 O(1)的時間在 TA 那早就已經生銹的大腦中自…

大數定理 中心極限定理_中心極限定理:直觀的遍歷

大數定理 中心極限定理One of the most beautiful concepts in statistics and probability is Central Limit Theorem,people often face difficulties in getting a clear understanding of this and the related concepts, I myself struggled understanding this during my…

萬惡之源 - Python數據類型二

列表 列表的介紹 列表是python的基礎數據類型之一 ,其他編程語言也有類似的數據類型. 比如JS中的數 組, java中的數組等等. 它是以[ ]括起來, 每個元素用 , 隔開而且可以存放各種數據類型: lst [1,a,True,[2,3,4]]列表相比于字符串,不僅可以存放不同的數據類型.而且可…

230. Kth Smallest Element in a BST

Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.Note: You may assume k is always valid, 1 ≤ k ≤ BSTs total elements.Example 1: Input: root [3,1,4,null,2], k 13/ \1 4\2 Output: 1 Example 2: Input: root …

探索性數據分析(EDA)-不要問如何,不要問什么

數據科學 , 機器學習 (Data Science, Machine Learning) This is part 1 in a series of articles guiding the reader through an entire data science project.這是一系列文章的第1部分 ,指導讀者完成整個數據科學項目。 I am a new writer on Medium…

unity3d 攝像機跟隨鼠標和鍵盤的控制

鼠標控制: using UnityEngine; using System.Collections; public class shubiao : MonoBehaviour { //public Transform firepos; public int Ball30; public int CurBall1; public Rigidbody projectile; public Vector3 point; public float time100f; public…

《必然》九、享受重混盛宴,是每個人的機會

今天說的是《必然》的第七個關鍵詞,過濾Filtering。1我們需要過濾如今有一個問題,彌漫在我們的生活當中,困擾著所有人。那就是“今天我要吃什么呢?”同樣的,書店里這么多的書,我要看哪一本呢?網…

IDEA 插件開發入門教程

2019獨角獸企業重金招聘Python工程師標準>>> IntelliJ IDEA 是目前最好用的 JAVA 開發 IDE,它本身的功能已經非常強大了,但是每個人的需求不一樣,有些需求 IDEA 本身無法滿足,于是我們就需要自己開發插件來解決。工欲善…

安卓代碼還是xml繪制頁面_我們應該繪制實際還是預測,預測還是實際還是無關緊要?

安卓代碼還是xml繪制頁面Plotting the actual and predicted data is frequently used for visualizing and analyzing how the actual data correlate with those predicted by the model. Ideally, this should correspond to a slope of 1 and an intercept of 0. However, …

Mecanim動畫系統

本期教程和大家分享Mecanim動畫系統的重定向特性,Mecanim動畫系統是Unity3D推出的全新的動畫系統,具有重定向、可融合等諸多新特性,通過和美工人員的緊密合作,可以幫助程序設計人員快速地設計出角色動畫。一起跟著人氣博主秦元培學…

【嵌入式硬件Esp32】Ubuntu 1804下ESP32交叉編譯環境搭建

一、ESP32概述EPS32是樂鑫最新推出的集成2.4GWi-Fi和藍牙雙模的單芯片方案,采用臺積電(TSMC)超低功耗的40nm工藝,擁有最佳的功耗性能、射頻性能、穩定性、通用性和可靠性,適用于多種應用和不同的功耗要求。 ESP32搭載低功耗的Xtensa LX6 32bi…

你認為已經過時的C語言,是如何影響500萬程序員的?...

看招聘職位要c語言的占比真不多了,是否c語言真得落伍了? 看一下許多招聘平臺有關于找純粹的c語言開發的占比確實沒有很多,都被Java,php,python等等語言刷屏。這對于入門正在學習c語言的小白真他媽就是驚天霹靂&#xf…

換熱站起停條件

循環泵 自動條件: 一、循環泵啟動條件 兩臺泵/三臺泵: 1)本循環泵在遠程狀態 2)本循環泵自動狀態 3)本循環泵沒有故障 4)二次網的回水壓力(測量值)>設定值 5)…

云尚制片管理系統_電影制片廠的未來

云尚制片管理系統Data visualization is a key step of any data science project. During the process of exploratory data analysis, visualizing data allows us to locate outliers and identify distribution, helping us to control for possible biases in our data ea…

JAVA單向鏈表實現

JAVA單向鏈表實現 單向鏈表 鏈表和數組一樣是一種最常用的線性數據結構,兩者各有優缺點。數組我們知道是在內存上的一塊連續的空間構成,所以其元素訪問可以通過下標進行,隨機訪問速度很快,但數組也有其缺點,由于數組的…

軟件公司管理基本原則

商業人格:獨立履行責任 獨立堅持原則兩大要素:1)靠原則做事,原則高于一切。2)靠結果做交換,我要什么我清楚兩個標準: 1)我不是孩子,我不需要照顧2)承認邏輯,我履行我的責任社會人心態: 1)用社會…

201771010102 常惠琢《面向對象程序設計(java)》第八周學習總結

1、實驗目的與要求 (1) 掌握接口定義方法; (2) 掌握實現接口類的定義要求; (3) 掌握實現了接口類的使用要求; (4) 掌握程序回調設計模式; (5) 掌握Comparator接口用法; (6) 掌握對象淺層拷貝與深層拷貝方法&#xff1b…

新版 Android 已支持 FIDO2 標準,免密登錄應用或網站

谷歌剛剛宣布了與 FIDO 聯盟達成的最新合作,為 Android 用戶帶來了無需密碼、即可登錄網站或應用的便捷選項。 這項服務基于 FIDO2 標準實現,任何運行 Android 7.0 及后續版本的設備,都可以在升級最新版 Google Play 服務后,通過指…

t-sne原理解釋_T-SNE解釋-數學與直覺

t-sne原理解釋The method of t-distributed Stochastic Neighbor Embedding (t-SNE) is a method for dimensionality reduction, used mainly for visualization of data in 2D and 3D maps. This method can find non-linear connections in the data and therefore it is hi…

oracle操作

imp kfqrlcs/kfqrlcshx fileC:\kfqrlcs.dmp fully //創建臨時表空間 create temporary tablespace kfqrlcs_temp tempfile C:\oracledata\kfqrlcs_temp.dbf size 32m autoextend on next 32m maxsize 8048m extent management local; //tempfile參數必須有 //創建數據表…