Java8就像一個寶藏,一個小的API改進,也足與寫一篇文章,好比同步,一直是多線程併發編程的一個老話題,相信沒有人喜歡同步的代碼,這會下降應用的吞吐量等性能指標,最壞的時候會掛起死機,可是即便這樣你也沒得選擇,由於要保證信息的正確性。因此本文決定將從synchronized、Lock到Java8新增的StampedLock進行對比分析,相信StampedLock不會讓你們失望。java
在java5以前,實現同步主要是使用synchronized。它是Java語言的關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,可以保證在同一時刻最多隻有一個線程執行該段代碼。編程
有四種不一樣的同步塊:多線程
實例方法併發
靜態方法工具
實例方法中的同步塊性能
靜態方法中的同步塊優化
你們對此應該不陌生,因此很少講了,如下是代碼示例
this
synchronized(this) // do operation }
小結:在多線程併發編程中Synchronized一直是元老級角色,不少人都會稱呼它爲重量級鎖,可是隨着Java SE1.6對Synchronized進行了各類優化以後,性能上也有所提高。spa
它是Java 5在java.util.concurrent.locks新增的一個API。線程
Lock是一個接口,核心方法是lock(),unlock(),tryLock(),實現類有ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock;
ReentrantReadWriteLock, ReentrantLock 和synchronized鎖都有相同的內存語義。
與synchronized不一樣的是,Lock徹底用Java寫成,在java這個層面是無關JVM實現的。Lock提供更靈活的鎖機制,不少synchronized 沒有提供的許多特性,好比鎖投票,定時鎖等候和中斷鎖等候,但由於lock是經過代碼實現的,要保證鎖定必定會被釋放,就必須將unLock()放到finally{}中
下面是Lock的一個代碼示例
rwlock.writeLock().lock(); try { // do operation } finally { rwlock.writeLock().unlock(); }
小結:比synchronized更靈活、更具可伸縮性的鎖定機制,但無論怎麼說仍是synchronized代碼要更容易書寫些
它是java8在java.util.concurrent.locks新增的一個API。
ReentrantReadWriteLock 在沒有任何讀寫鎖時,才能夠取得寫入鎖,這可用於實現了悲觀讀取(Pessimistic Reading),即若是執行中進行讀取時,常常可能有另外一執行要寫入的需求,爲了保持同步,ReentrantReadWriteLock 的讀取鎖定就可派上用場。
然而,若是讀取執行狀況不少,寫入不多的狀況下,使用 ReentrantReadWriteLock 可能會使寫入線程遭遇飢餓(Starvation)問題,也就是寫入線程吃吃沒法競爭到鎖定而一直處於等待狀態。
StampedLock控制鎖有三種模式(寫,讀,樂觀讀),一個StampedLock狀態是由版本和模式兩個部分組成,鎖獲取方法返回一個數字做爲票據stamp,它用相應的鎖狀態表示並控制訪問,數字0表示沒有寫鎖被受權訪問。在讀鎖上分爲悲觀鎖和樂觀鎖。
所謂的樂觀讀模式,也就是若讀的操做不少,寫的操做不多的狀況下,你能夠樂觀地認爲,寫入與讀取同時發生概率不多,所以不悲觀地使用徹底的讀取鎖定,程序能夠查看讀取資料以後,是否遭到寫入執行的變動,再採起後續的措施(從新讀取變動信息,或者拋出異常) ,這一個小小改進,可大幅度提升程序的吞吐量!!
下面是java doc提供的StampedLock一個例子
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); } } //下面看看樂觀讀鎖案例 double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); //得到一個樂觀讀鎖 double currentX = x, currentY = y; //將兩個字段讀入本地局部變量 if (!sl.validate(stamp)) { //檢查發出樂觀讀鎖後同時是否有其餘寫鎖發生? stamp = sl.readLock(); //若是沒有,咱們再次得到一個讀悲觀鎖 try { currentX = x; // 將兩個字段讀入本地局部變量 currentY = y; // 將兩個字段讀入本地局部變量 } 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) { //循環,檢查當前狀態是否符合 long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉爲寫鎖 if (ws != 0L) { //這是確認轉爲寫鎖是否成功 stamp = ws; //若是成功 替換票據 x = newX; //進行狀態改變 y = newY; //進行狀態改變 break; } else { //若是不能成功轉換爲寫鎖 sl.unlockRead(stamp); //咱們顯式釋放讀鎖 stamp = sl.writeLock(); //顯式直接進行寫鎖 而後再經過循環再試 } } } finally { sl.unlock(stamp); //釋放讀鎖或寫鎖 } } }
小結:
StampedLock要比ReentrantReadWriteLock更加廉價,也就是消耗比較小。
下圖是和ReadWritLock相比,在一個線程狀況下,是讀速度其4倍左右,寫是1倍。
下圖是六個線程狀況下,讀性能是其幾十倍,寫性能也是近10倍左右:
下圖是吞吐量提升:
一、synchronized是在JVM層面上實現的,不但能夠經過一些監控工具監控synchronized的鎖定,並且在代碼執行時出現異常,JVM會自動釋放鎖定;
二、ReentrantLock、ReentrantReadWriteLock,、StampedLock都是對象層面的鎖定,要保證鎖定必定會被釋放,就必須將unLock()放到finally{}中;
三、StampedLock 對吞吐量有巨大的改進,特別是在讀線程愈來愈多的場景下;
四、StampedLock有一個複雜的API,對於加鎖操做,很容易誤用其餘方法;
五、當只有少許競爭者的時候,synchronized是一個很好的通用的鎖實現;
六、當線程增加可以預估,ReentrantLock是一個很好的通用的鎖實現;
StampedLock 能夠說是Lock的一個很好的補充,吞吐量以及性能上的提高足以打動不少人了,但並非說要替代以前Lock的東西,畢竟他仍是有些應用場景的,起碼API比StampedLock容易入手,下篇博文爭取更新快一點,可能會是Nashorn的內容,這裏容許我先賣個關子。。。