Java8新特性之StampedLock_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Java8就像一個(gè)寶藏,一個(gè)小的API改進(jìn),也足與寫一篇文章,比如同步,一直是多線程并發(fā)編程的一個(gè)老話題,相信沒(méi)有人喜歡同步的代碼,這會(huì)降低應(yīng)用的吞吐量等性能指標(biāo),最壞的時(shí)候會(huì)掛起死機(jī),但是即使這樣你也沒(méi)得選擇,因?yàn)橐WC信息的正確性。所以本文決定將從synchronized、Lock到Java8新增的StampedLock進(jìn)行對(duì)比分析,相信StampedLock不會(huì)讓大家失望。
synchronized
在java5之前,實(shí)現(xiàn)同步主要是使用synchronized。它是Java語(yǔ)言的關(guān)鍵字,當(dāng)它用來(lái)修飾一個(gè)方法或者一個(gè)代碼塊的時(shí)候,能夠保證在同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼。
有四種不同的同步塊:
1.實(shí)例方法
2.靜態(tài)方法
3.實(shí)例方法中的同步塊
4.靜態(tài)方法中的同步塊
大家對(duì)此應(yīng)該不陌生,所以不多講了,以下是代碼示例
synchronized(this) // do operation }
小結(jié):在多線程并發(fā)編程中Synchronized一直是元老級(jí)角色,很多人都會(huì)稱呼它為重量級(jí)鎖,但是隨著Java SE1.6對(duì)Synchronized進(jìn)行了各種優(yōu)化之后,性能上也有所提升。
Lock
它是Java 5在java.util.concurrent.locks新增的一個(gè)API。
Lock是一個(gè)接口,核心方法是lock(),unlock(),tryLock(),實(shí)現(xiàn)類有ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock;
ReentrantReadWriteLock, ReentrantLock 和synchronized鎖都有相同的內(nèi)存語(yǔ)義。
與synchronized不同的是,Lock完全用Java寫成,在java這個(gè)層面是無(wú)關(guān)JVM實(shí)現(xiàn)的。Lock提供更靈活的鎖機(jī)制,很多synchronized 沒(méi)有提供的許多特性,比如鎖投票,定時(shí)鎖等候和中斷鎖等候,但因?yàn)閘ock是通過(guò)代碼實(shí)現(xiàn)的,要保證鎖定一定會(huì)被釋放,就必須將unLock()放到finally{}中
下面是Lock的一個(gè)代碼示例
rwlock.writeLock().lock(); try { // do operation } finally { rwlock.writeLock().unlock(); }
小結(jié):比synchronized更靈活、更具可伸縮性的鎖定機(jī)制,但不管怎么說(shuō)還是synchronized代碼要更容易書寫些
StampedLock
它是java8在java.util.concurrent.locks新增的一個(gè)API。
ReentrantReadWriteLock 在沒(méi)有任何讀寫鎖時(shí),才可以取得寫入鎖,這可用于實(shí)現(xiàn)了悲觀讀取(Pessimistic Reading),即如果執(zhí)行中進(jìn)行讀取時(shí),經(jīng)??赡苡辛硪粓?zhí)行要寫入的需求,為了保持同步,ReentrantReadWriteLock 的讀取鎖定就可派上用場(chǎng)。
然而,如果讀取執(zhí)行情況很多,寫入很少的情況下,使用 ReentrantReadWriteLock 可能會(huì)使寫入線程遭遇饑餓(Starvation)問(wèn)題,也就是寫入線程吃吃無(wú)法競(jìng)爭(zhēng)到鎖定而一直處于等待狀態(tài)。
StampedLock控制鎖有三種模式(寫,讀,樂(lè)觀讀),一個(gè)StampedLock狀態(tài)是由版本和模式兩個(gè)部分組成,鎖獲取方法返回一個(gè)數(shù)字作為票據(jù)stamp,它用相應(yīng)的鎖狀態(tài)表示并控制訪問(wèn),數(shù)字0表示沒(méi)有寫鎖被授權(quán)訪問(wèn)。在讀鎖上分為悲觀鎖和樂(lè)觀鎖。
所謂的樂(lè)觀讀模式,也就是若讀的操作很多,寫的操作很少的情況下,你可以樂(lè)觀地認(rèn)為,寫入與讀取同時(shí)發(fā)生幾率很少,因此不悲觀地使用完全的讀取鎖定,程序可以查看讀取資料之后,是否遭到寫入執(zhí)行的變更,再采取后續(xù)的措施(重新讀取變更信息,或者拋出異常) ,這一個(gè)小小改進(jìn),可大幅度提高程序的吞吐量??!
下面是java doc提供的StampedLock一個(gè)例子
class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } //下面看看樂(lè)觀讀鎖案例 double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); //獲得一個(gè)樂(lè)觀讀鎖 double currentX = x, currentY = y; //將兩個(gè)字段讀入本地局部變量 if (!sl.validate(stamp)) { //檢查發(fā)出樂(lè)觀讀鎖后同時(shí)是否有其他寫鎖發(fā)生? stamp = sl.readLock(); //如果沒(méi)有,我們?cè)俅潍@得一個(gè)讀悲觀鎖 try { currentX = x; // 將兩個(gè)字段讀入本地局部變量 currentY = y; // 將兩個(gè)字段讀入本地局部變量 } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } //下面是悲觀讀鎖案例 void moveIfAtOrigin(double newX, double newY) { // upgrade // Could instead start with optimistic, not read mode long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { //循環(huán),檢查當(dāng)前狀態(tài)是否符合 long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉(zhuǎn)為寫鎖 if (ws != 0L) { //這是確認(rèn)轉(zhuǎn)為寫鎖是否成功 stamp = ws; //如果成功 替換票據(jù) x = newX; //進(jìn)行狀態(tài)改變 y = newY; //進(jìn)行狀態(tài)改變 break; } else { //如果不能成功轉(zhuǎn)換為寫鎖 sl.unlockRead(stamp); //我們顯式釋放讀鎖 stamp = sl.writeLock(); //顯式直接進(jìn)行寫鎖 然后再通過(guò)循環(huán)再試 } } } finally { sl.unlock(stamp); //釋放讀鎖或?qū)戞i } } }
小結(jié):
StampedLock要比ReentrantReadWriteLock更加廉價(jià),也就是消耗比較小。
StampedLock與ReadWriteLock性能對(duì)比
下圖是和ReadWritLock相比,在一個(gè)線程情況下,是讀速度其4倍左右,寫是1倍。
下圖是六個(gè)線程情況下,讀性能是其幾十倍,寫性能也是近10倍左右:
下圖是吞吐量提高:
總結(jié)
1、synchronized是在JVM層面上實(shí)現(xiàn)的,不但可以通過(guò)一些監(jiān)控工具監(jiān)控synchronized的鎖定,而且在代碼執(zhí)行時(shí)出現(xiàn)異常,JVM會(huì)自動(dòng)釋放鎖定;
2、ReentrantLock、ReentrantReadWriteLock,、StampedLock都是對(duì)象層面的鎖定,要保證鎖定一定會(huì)被釋放,就必須將unLock()放到finally{}中;
3、StampedLock 對(duì)吞吐量有巨大的改進(jìn),特別是在讀線程越來(lái)越多的場(chǎng)景下;
4、StampedLock有一個(gè)復(fù)雜的API,對(duì)于加鎖操作,很容易誤用其他方法;
5、當(dāng)只有少量競(jìng)爭(zhēng)者的時(shí)候,synchronized是一個(gè)很好的通用的鎖實(shí)現(xiàn);
6、當(dāng)線程增長(zhǎng)能夠預(yù)估,ReentrantLock是一個(gè)很好的通用的鎖實(shí)現(xiàn);
相關(guān)文章
Java使用Sftp和Ftp實(shí)現(xiàn)對(duì)文件的上傳和下載
這篇文章主要介紹了Java使用Sftp和Ftp實(shí)現(xiàn)對(duì)文件的上傳和下載,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Spring AOP方法內(nèi)部調(diào)用不生效的解決方案
最近有個(gè)需求,統(tǒng)計(jì)某個(gè)方法的調(diào)用次數(shù),開(kāi)始使用 Spring AOP 實(shí)現(xiàn),后來(lái)發(fā)現(xiàn)當(dāng)方法被內(nèi)部調(diào)用時(shí),切面邏輯將不會(huì)生效,所以本文就給大家介紹了Spring AOP方法內(nèi)部調(diào)用不生效的解決方案,需要的朋友可以參考下2025-01-01MyBatis傳入?yún)?shù)的實(shí)例代碼
這篇文章主要介紹了MyBatis傳入?yún)?shù)的實(shí)例代碼的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06SpringBoot定時(shí)任務(wù)參數(shù)運(yùn)行代碼實(shí)例解析
這篇文章主要介紹了SpringBoot定時(shí)任務(wù)運(yùn)行代碼實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06關(guān)于Java的二叉樹(shù)、紅黑樹(shù)、B+樹(shù)詳解
這篇文章主要介紹了關(guān)于Java的二叉樹(shù)、紅黑樹(shù)、B+樹(shù)詳解,能同時(shí)具備數(shù)組查找快的優(yōu)點(diǎn)以及鏈表插入和刪除快的優(yōu)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)就是樹(shù),需要的朋友可以參考下2023-05-05Java項(xiàng)目開(kāi)啟遠(yuǎn)程調(diào)試的方法步驟(tomcat、springboot)
這篇文章主要介紹了Java項(xiàng)目開(kāi)啟遠(yuǎn)程調(diào)試的方法步驟(tomcat、springboot),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10詳解Java如何優(yōu)雅的實(shí)現(xiàn)異常捕獲
在一個(gè)優(yōu)秀的項(xiàng)目中一定少不了對(duì)程序流程良好的異常捕獲與日志打印,所以本文主要為大家介紹了如何優(yōu)雅的實(shí)現(xiàn)異常捕獲與日志打印輸出,有需要的可以參考下2023-09-09Java計(jì)算一個(gè)數(shù)加上100是完全平方數(shù),加上168還是完全平方數(shù)
這篇文章主要介紹了Java計(jì)算一個(gè)數(shù)加上100是完全平方數(shù),加上168還是完全平方數(shù),需要的朋友可以參考下2017-02-02