java高并发(十四)ReetrantLock 与锁
java主要分兩類鎖,一種是synchronized關鍵字修飾的鎖,另一種是J.U.C.提供的鎖。J.U.C里核心鎖就是ReentrantLock
ReentrantLock(可重入鎖)和synchronized區別
- 可重入性
- 鎖的實現,synchronized關鍵字是依賴于JVM實現的,而ReentrantLock是JDK實現的,這兩個有什么區別?說白了就類似于操作系統來控制實現和用戶自己敲代碼實現的區別。synchronized實現依賴于JVM,很難查到源碼,而ReentrantLock可以通過閱讀源碼來實現。
- 性能區別,自從synchronized引入偏向鎖、輕量級鎖(自旋鎖)之后性能差不多,官方建議使用synchronized。
- 功能區別,synchronized使用更方便,由編譯器保證加鎖與釋放,而ReentrantLock需要手動聲明加鎖與釋放鎖。
ReentrantLock獨有的功能:
- 可指定公平鎖還是非公平鎖,而synchronized只能是非公平鎖。公平鎖:先等待的線程先獲得鎖。
- 提供了一個Condition類,可以分組喚醒需要喚醒的線程。而不是像synchronized,隨機
- 提供了一種能夠中斷等待鎖的線程的機制,lock.lockInterruptibly()。ReentrantLock是一種自旋鎖,通過循環調用CAS操作來實現加鎖,性能比較好也是因為避免了使線程進入內核態的阻塞狀態,想盡辦法避免線程進入內核的阻塞狀態,是我們分析和理解鎖設計的關鍵鑰匙
使用場景
如果需要ReentrantLock獨有的功能時可以使用ReentrantLock。其他情況下可以根據性能來選擇使用ReentrantLock還是synchronized。
synchronized能做的事情ReentrantLock都能做,而ReentrantLock能做的synchronize卻不一定能做,性能方面ReentrantLock也不必synchronize差。那么我們要不要拋棄synchronize?當然是否定的,java.util.current.lock下的類一般用于高級用戶和高級情況的工具,一般來說除非對lock的某個高級特性有明確的需要或者有明確的證據標明在特定的情況下,同步已經成為可伸縮性的瓶頸的時候,否則建議繼續使用synchronized。
即使對于這些高級的鎖定類來說,synchronized仍然有一些優勢,比如在使用synchronized時候不可能忘記釋放鎖,在退出synchronize塊時,jvm會為你做這些事情,否則一旦忘記釋放鎖而產生死鎖很難查出原因,因此不建議初級開發人員使用lock。
另外當jvm用synchronized來管理鎖定請求和釋放時,jvm在生成線程轉儲時,能夠包括鎖定信息,這些對調試非常有價值,因為他們能標識死鎖或者其他異常行為的來源。而lock類只是一個普通的類,jvm不知道具體哪個線程擁有lock對象,而且幾乎每個開發人員都熟悉synchronized,可以在jvm的所有版本中工作,在大部分使用場景下都可以使用synchronized來實現。?
@Slf4j public class LockExample2 {// 請求總數public static int clientTotal = 5000;// 同時并發執行的線程數public static int threadTotal = 200;public static int count = 0;private final static Lock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {//線程池ExecutorService executorService = Executors.newCachedThreadPool();//定義信號量final Semaphore semaphore = new Semaphore(threadTotal);//定義計數器final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for(int i = 0; i < clientTotal; i++) {executorService.execute(() ->{try {semaphore.acquire();add();semaphore.release();} catch (InterruptedException e) {log.error("exception", e);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();log.info("count:{}", count);}public static void add() {lock.lock();try{count++;}finally {lock.unlock();}}}ReentrantReadWriteLock
@Slf4j public class LockExample3 {private final Map<String, Data> map = new TreeMap<>();private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private final Lock readLock = lock.readLock();private final Lock writeLock = lock.writeLock();public Data get (String key){readLock.lock();try{return map.get(key);} finally {readLock.unlock();}}public Set<String> getAllKeys() {readLock.lock();try{return map.keySet();}finally {readLock.unlock();}}public Data put(String key, Data value) {writeLock.lock();try {return map.put(key, value);}finally {writeLock.unlock();}}class Data {}}write.lock()保證在沒有讀鎖的時候才進行寫入操作,對數據同步做的更多一些,使用悲觀讀取,也就是說如果有寫入鎖時,堅決不允許有讀鎖還保持著,保證了在寫的時候其他事情已經做完了。這樣就會有一個問題,在讀取情況較多而寫入很少的時候,調用上面例子中的put方法就會遭遇饑餓(寫鎖一直想執行,但是讀鎖一直在,導致寫鎖永遠無法執行,一直在等待)
StampedLock
stampedLock控制鎖有三種模式,分別是寫、讀、樂觀讀。StampedLock由版本和模式兩個模塊組成,返回一個數字(票據stamp),用相應的鎖狀態來表示并控制相應的訪問,數字0表示沒有寫鎖被訪問。讀鎖分為樂觀鎖和悲觀鎖,樂觀鎖是當讀的情況很多,寫的情況很少,可以認為讀寫同時發生的幾率很小,因此不悲觀的使用完全悲觀的鎖定,程序可以讀取數據之后是否得到寫入執行的變更,再采取后續的措施,這一個小小的改進,可以大幅度提高程序的吞吐量。
@Slf4j public class LockExample4 {// 請求總數public static int clientTotal = 5000;// 同時并發執行的線程數public static int threadTotal = 200;public static int count = 0;private final static StampedLock lock = new StampedLock();public static void main(String[] args) throws InterruptedException {//線程池ExecutorService executorService = Executors.newCachedThreadPool();//定義信號量final Semaphore semaphore = new Semaphore(threadTotal);//定義計數器final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for(int i = 0; i < clientTotal; i++) {executorService.execute(() ->{try {semaphore.acquire();add();semaphore.release();} catch (InterruptedException e) {log.error("exception", e);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();log.info("count:{}", count);}public static void add() {long stamp = lock.writeLock();try{count++;}finally {lock.unlock(stamp);}}}總結
synchronized:是在jvm層面實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在代碼執行時出現異常JVM也可以釋放鎖定,jvm會自動加鎖與解鎖。
ReentrantLock、ReentrantReadWriteLock、StampedLock:都是對象層面的鎖定,要保證鎖一定會被釋放,一定要把unlock操作放在finally中才安全一些。StampedLock對吞度量有巨大的改進,特別是在讀線程很多的場景下。
那么我們該如何選擇使用哪個鎖?
我們在用鎖時不是看哪個鎖高級用哪個。適合自己使用場景的才是最關鍵的。?
需要注意的是synchronized不會引發死鎖,jvm會自動解鎖。而其他的Lock一旦使用不當會造成死鎖,有可能會在一些情況下未執行unlock操作。
總結
以上是生活随笔為你收集整理的java高并发(十四)ReetrantLock 与锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java高并发(十三)并发容器J.U.C
- 下一篇: java高并发(十五)J.U.C之Fut