在分佈式開發中,鎖是線程控制的重要途徑。Java爲此也提供了2種鎖機制,synchronized和lock。作爲Java愛好者,天然少不了對比一下這2種機制,也能從中學到些分佈式開發須要注意的地方。
咱們先從最簡單的入手,逐步分析這2種的區別。
1、synchronized和lock的用法區別
synchronized:在須要同步的對象中加入此控制,synchronized能夠加在方法上,也能夠加在特定代碼塊中,括號中表示須要鎖的對象。
lock:須要顯示指定起始位置和終止位置。通常使用ReentrantLock類作爲鎖,多個線程中必需要使用一個ReentrantLock類作爲對象才能保證鎖的生效。且在加鎖和解鎖處須要經過lock()和unlock()顯示指出。因此通常會在finally塊中寫unlock()以防死鎖。
用法區別比較簡單,這裏不贅述了,若是不懂的能夠看看Java基本語法。
2、synchronized和lock性能區別
synchronized是託管給JVM執行的,而lock是java寫的控制鎖的代碼。在Java1.5中,synchronize是性能低效的。由於這是一個重量級操做,須要調用操做接口,致使有可能加鎖消耗的系統時間比加鎖之外的操做還多。相比之下使用Java提供的Lock對象,性能更高一些。可是到了Java1.6,發生了變化。synchronize在語義上很清晰,能夠進行不少優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。致使在Java1.6上synchronize的性能並不比Lock差。官方也表示,他們也更支持synchronize,在將來的版本中還有優化餘地。
說到這裏,仍是想提一下這2中機制的具體區別。據我所知,synchronized原始採用的是CPU悲觀鎖機制,即線程得到的是獨佔鎖。獨佔鎖意味着其餘線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引發線程上下文切換,當有不少線程競爭鎖的時候,會引發CPU頻繁的上下文切換致使效率很低。
而Lock用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。樂觀鎖實現的機制就是CAS操做(Compare and Swap)。咱們能夠進一步研究ReentrantLock的源代碼,會發現其中比較重要的得到鎖的一個方法是compareAndSetState。這裏其實就是調用的CPU提供的特殊指令。
現代的CPU提供了指令,能夠自動更新共享數據,並且可以檢測到其餘線程的干擾,而 compareAndSet() 就用這些代替了鎖定。這個算法稱做非阻塞算法,意思是一個線程的失敗或者掛起不該該影響其餘線程的失敗或掛起的算法。
我也只是瞭解到這一步,具體到CPU的算法若是感興趣的讀者還能夠在查閱下,若是有更好的解釋也能夠給我留言,我也學習下。
3、synchronized和lock用途區別
synchronized原語和ReentrantLock在通常狀況下沒有什麼區別,可是在很是複雜的同步應用中,請考慮使用ReentrantLock,特別是遇到下面2種需求的時候。
1.某個線程在等待一個鎖的控制權的這段時間須要中斷
2.須要分開處理一些wait-notify,ReentrantLock裏面的Condition應用,可以控制notify哪一個線程
3.具備公平鎖功能,每一個到來的線程都將排隊等候
下面細細道來……
先說第一種狀況,ReentrantLock的lock機制有2種,忽略中斷鎖和響應中斷鎖,這給咱們帶來了很大的靈活性。好比:若是A、B2個線程去競爭鎖,A線程獲得了鎖,B線程等待,可是A線程這個時候實在有太多事情要處理,就是一直不返回,B線程可能就會等不及了,想中斷本身,再也不等待這個鎖了,轉而處理其餘事情。這個時候ReentrantLock就提供了2種機制,第一,B線程中斷本身(或者別的線程中斷它),可是ReentrantLock不去響應,繼續讓B線程等待,你再怎麼中斷,我全當耳邊風(synchronized原語就是如此);第二,B線程中斷本身(或者別的線程中斷它),ReentrantLock處理了這個中斷,而且再也不等待這個鎖的到來,徹底放棄。(若是你沒有了解java的中斷機制,請參考下相關資料,再回頭看這篇文章,80%的人根本沒有真正理解什麼是java的中斷,呵呵)
這裏來作個試驗,首先搞一個Buffer類,它有讀操做和寫操做,爲了避免讀到髒數據,寫和讀都須要加鎖,咱們先用synchronized原語來加鎖,以下: java
11 |
long startTime = System.currentTimeMillis(); |
12 |
System.out.println("開始往這個buff寫入數據…"); |
15 |
if (System.currentTimeMillis() |
16 |
- startTime > Integer.MAX_VALUE) |
19 |
System.out.println("終於寫完了"); |
25 |
System.out.println("從這個buff讀數據"); |