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