Java锁 (概览)
?
一、常用鎖
1、? Synchronized
a)???????? synchronized鎖是什么?
Java關鍵字,能夠將方法或者代碼塊鎖起來
只要在方法或者代碼塊中加上關鍵字synchronized就能實現同步功能
?
1 package demo;2 3 public class SynchronizedDemo implements Runnable{4 String lock = "aa";5 public void run() { 6 methodA(); 7 methodC();8 methodB();9 } 10 11 public synchronized void methodA(){ 12 System.out.println(this); 13 try { 14 Thread.sleep(5000); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 20 public void methodB(){ 21 synchronized(this){ 22 System.out.println(this); 23 } 24 } 25 26 public void methodC(){ 27 synchronized(lock){ 28 System.out.println(lock); 29 } 30 } 31 32 public static void main(String[] args) { 33 SynchronizedDemo f1=new SynchronizedDemo(); 34 new Thread(f1).start(); 35 } 36 }?
兩種方法都能加鎖,效率上使用同步代碼塊的方式要比使用同步方法的效率高一些,因為同步代碼塊是在方法中進行同步。
b)???????? Synchronized 原理
?
?
上面標記的3和12分別是進入和退出指令,synchronized底層是是通過monitor對象,對象有自己的對象頭,存儲了很多信息,其中一個信息標示是被哪個線程持有。
?
2、? StampedLock
StampedLock是java8在java.util.concurrent.locks新增的一個API。
?
ReentrantReadWriteLock 在沒有任何讀寫鎖時,才可以取得寫入鎖,這可用于實現了悲觀讀取(Pessimistic Reading),即如果執行中進行讀取時,經常可能有另一執行要寫入的需求,為了保持同步,ReentrantReadWriteLock 的讀取鎖定就可派上用場。
?
如果讀取執行情況很多,寫入很少的情況下,使用 ReentrantReadWriteLock 可能會使寫入線程遭遇饑餓(Starvation)問題,也就是寫入線程遲遲無法競爭到鎖定而一直處于等待狀態。
?
StampedLock控制鎖有三種模式(寫,讀,樂觀讀),一個StampedLock狀態是由版本和模式兩個部分組成,鎖獲取方法返回一個數字作為票據stamp,它用相應的鎖狀態表示并控制訪問,數字0表示沒有寫鎖被授權訪問。
在讀鎖上分為悲觀鎖和樂觀鎖。
?
所謂的樂觀讀模式,也就是若讀的操作很多,寫的操作很少的情況下,你可以樂觀地認為,寫入與讀取同時發生幾率很少,因此不悲觀地使用完全的讀取鎖定,程序可以查看讀取資料之后,是否遭到寫入執行的變更,再采取后續的措施(重新讀取變更信息,或者拋出異常) ,這一個小小改進,可大幅度提高程序的吞吐量!!
?
3、? ReentrantLock
?
1 package demo;2 3 import java.util.concurrent.locks.ReentrantLock;4 5 public class ReentrantLockDemo extends Thread{6 public static ReentrantLock lock = new ReentrantLock();7 public static int i = 0;8 9 public ReentrantLockDemo(String name) { 10 super.setName(name); 11 } 12 13 @Override 14 public void run() { 15 for (int j = 0; j < 100000; j++) { 16 lock.lock(); 17 try { 18 System.out.println(this.getName() + " " + i); 19 i++; 20 } finally { 21 lock.unlock(); 22 } 23 } 24 } 25 26 public static void main(String[] args) throws InterruptedException { 27 ReentrantLockDemo test1 = new ReentrantLockDemo("thread1"); 28 ReentrantLockDemo test2 = new ReentrantLockDemo("thread2"); 29 30 test1.start(); 31 test2.start(); 32 test1.join(); 33 test2.join(); 34 System.out.println(i); 35 } 36 }?
保證線程安全,需要開啟和關閉鎖,如果忘記關閉鎖那么資源就不會釋放,下次在調用這個鎖的時候就會一直等待,也就是說會造成死鎖,程序崩潰
?
4、? ReentrantReadWriteLock
提供兩把鎖,一把用于讀操作和一把用于寫操作。同時可以有多個線程執行讀操作,但只有一個線程可以執行寫操作。當一個線程正在執行一個寫操作,不可能有任何線程執行讀操作。
?
5、? ConcurrentHashMap
使用分段鎖技術,允許多個修改操作并發進行。ConcurrentHashMap內部使用段來表示這些不同的部分,每個段其實就是一個小的Hashtable,它們有自己的鎖。只要多個修改操作發生在不同的段上,它們就可以并發進行。
HashTable是一個線程安全的類,它使用synchronized來鎖住整張Hash表來實現線程安全,即每次鎖住整張表讓線程獨占
有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖。這里“按順序”是很重要的,否則極有可能出現死鎖,在ConcurrentHashMap內部,段數組是final的,并且其成員變量實際上也是final的,但是,僅僅是將數組聲明為final的并不保證數組成員也是final的,這需要實現上的保證。這可以確保不會出現死鎖,因為獲得鎖的順序是固定的。
?
6、? 總結
synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在代碼執行時出現異常,JVM會自動釋放鎖定;
?
ReentrantLock、ReentrantReadWriteLock,、StampedLock都是對象層面的鎖定,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中;
?
StampedLock 對吞吐量有巨大的改進,特別是在讀線程越來越多的場景下;
?
StampedLock有一個復雜的API,對于加鎖操作,很容易誤用其他方法;
?
當只有少量競爭者的時候,synchronized是一個很好的通用的鎖實現;
?
當線程增長能夠預估,ReentrantLock是一個很好的通用的鎖實現;
二、鎖特性
?
1、? 升級鎖
讀取鎖是不能直接升級為寫入鎖的。因為獲取一個寫入鎖需要釋放所有讀取鎖,所以如果有兩個讀取鎖視圖獲取寫入鎖而都不釋放讀取鎖時就會發生死鎖。
?
2、? 重入鎖
ReentrantLock和Synchronized都具有
可以反復得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。
? ? 讀寫鎖允許讀線程和寫線程按照請求鎖的順序重新獲取讀取鎖或者寫入鎖。當然了只有寫線程釋放了鎖,讀線程才能獲取重入鎖。
寫線程獲取寫入鎖后可以再次獲取讀取鎖,但是讀線程獲取讀取鎖后卻不能獲取寫入鎖。
另外讀寫鎖最多支持65535個遞歸寫入鎖和65535個遞歸讀取鎖。
?
3、? 讀寫鎖
讀寫鎖維護了一對相關的鎖,一個用于只讀操作,一個用于寫入操作。只要沒有writer,讀取鎖可以由多個reader線程同時保持。寫入鎖是獨占的。
互斥鎖一次只允許一個線程訪問共享數據,哪怕進行的是只讀操作;讀寫鎖允許對共享數據進行更高級別的并發訪問:對于寫操作,一次只有一個線程(write線程)可以修改共享數據,對于讀操作,允許任意數量的線程同時進行讀取。
與互斥鎖相比,使用讀寫鎖能否提升性能則取決于讀寫操作期間讀取數據相對于修改數據的頻率,以及數據的爭用——即在同一時間試圖對該數據執行讀取或寫入操作的線程數。
讀寫鎖適用于讀多寫少的情況。
?
4、? 公平鎖和非公平鎖
公平鎖是根據等待順序前一把鎖釋放排在最前面的任務獲取鎖。
?
5、? 中斷鎖
讀取鎖和寫入鎖都支持獲取鎖期間被中斷。這個和獨占鎖一致。
?
6、? 悲觀鎖和樂觀鎖
悲觀鎖(Pessimistic Lock), 每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞(block)直到它拿到鎖。
傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
?
樂觀鎖(Optimistic Lock),每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。
樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似于write_condition機制的其實都是提供的樂觀鎖。
?
兩種鎖各有優缺點,不可認為一種好于另一種,像樂觀鎖適用于寫比較少的情況下,即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生沖突,上層應用會不斷的進行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。
?
7、? 自旋鎖
自旋鎖(Spinlock)是一種廣泛運用的底層同步機制。自旋鎖是一個互斥設備,它只有兩個值:“鎖定”和“解鎖”。它通常實現為某個整數值中的某個位。希望獲得某個特定鎖得代碼測試相關的位。
如果鎖可用,則“鎖定”被設置,而代碼繼續進入臨界區;相反,如果鎖被其他人獲得,則代碼進入忙循環(而不是休眠,這也是自旋鎖和一般鎖的區別)并重復檢查這個鎖,直到該鎖可用為止,這就是自旋的過程。“測試并設置位”的操作必須是原子的,這樣,即使多個線程在給定時間自旋,也只有一個線程可獲得該鎖。
?
8、? 互斥鎖
就是一次只能有一個線程持有鎖,也即所謂獨占鎖的概念
?
總結
以上是生活随笔為你收集整理的Java锁 (概览)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring事务管理amp;数据库隔离级
- 下一篇: 跨域三种方式