来说一说你对锁都怎么分类?
悲觀鎖和樂觀鎖
我覺得悲觀鎖和樂觀鎖更多的是指一種思想,實現鎖的不同方式。數據庫、git都有不同的對應悲觀鎖和樂觀鎖思想的應用.
-
悲觀鎖一般是互斥鎖,
但是1.阻塞和喚醒會帶來性能損耗,要去切換用戶態切換狀態,查看要喚醒的線程等等;2.而且可能導致永久阻塞,比如發生了無限循環,死鎖,那么阻塞等待獲取鎖的線程就可能永久等待了。3.還可能導致線程優先級混亂。適合并發多競爭很激烈的情況,代碼復雜 或者循環量大java中最常見的悲觀鎖就是synchronized和lock類,但是注意,synchronized引入了偏向鎖、輕量級鎖等優化措施,readWriteLock在讀的時候是共享鎖,寫的時候是獨占鎖??傮w上來說還是得先拿到鎖,才能執行
-
樂觀鎖是非互斥鎖,
認為自己在操作的時候不會有其他線程干擾,所以不會鎖住操作對象。更新的時候去再去比較對象數據是否被修改過,如果沒修改過最好,如果發生了修改,就要選擇放棄,拋棄,重試等策略。適合并發寫入少,讀取多的情況,提高讀取的性能樂觀鎖基本都是基于CAS算法實現的,注意ABA問題,可以加版本號解決。有原子類,并發容器
共享鎖和獨占鎖
獨占鎖 ,又稱排它鎖、獨享鎖
共享鎖,又稱讀鎖,獲取到鎖后可以查看但不能修改刪除。為什么要這樣設計呢 ?是因為很多線程讀并不會造成線程安全問題,所以如果允許多個線程來讀,就可以提高性能。
ReentrantReadWriteLock讀寫鎖,其中讀鎖是共享鎖,寫鎖時獨占鎖。
要么是多讀,要么是一寫。更具體點說就是,多個線程讀沒問題;以如果已經有線程在讀,那么其他線程申請寫鎖則必須要等待釋放讀鎖;如果一個線程已經在寫,那么其他線程申請讀或寫都必須要等待釋放寫鎖。
ReentrantReadWriteLock公平鎖:不允許任何插隊,不管寫鎖還是讀鎖,只要隊列里已經有線程了就應該阻塞等待
ReentrantReadWriteLock非公平:
- 寫鎖可以隨時插隊,即不需要阻塞
- 讀鎖僅在等待隊列中頭結點不是寫線程時可以插隊:
舉個例子:假設線程2和線程4正在同時讀,線程3想要寫入,所以進入了等待隊列且在頭結點。然后線程5過來想要讀,那么此時允不允許線程5去同時讀呢?
如果允許線程5插隊讀,則可能不停的有線程來插隊讀,寫的線程就可能饑餓,所以ReentrantReadWriteLock不允許插隊讀,如果有寫線程在排隊則必須進入隊列排隊
ReentrantReadWriteLock支持寫鎖降級為讀鎖,但不支持讀鎖升級為寫鎖(避免死鎖)。
為什么需要鎖降級?比如一個任務剛開始需要寫鎖拿到某個日志文件,但是后續都只需要讀就行,顯然如果還是一直持有寫鎖性能就會差很多,如果可以降級為讀鎖,就能允許其他線程一起讀,性能就會好很多。
為什么不支持鎖升級為寫鎖呢?可能造成死鎖,比如兩個線程同時準備升級為寫鎖
公平鎖和非公平鎖
公平鎖指的是完全按照線程請求的順序來分配鎖;非公平是不完全按照線程請求順序,注意不是完全隨機的,在一定情況下可以插隊。
為什么要設計非公平鎖呢?
非公平可以避免去喚醒線程時的空檔期,提高使用性能,提高吞吐量。但有可能造成線程饑餓,即某些線程一直都拿不到鎖
synchronize是非公平鎖,ReentrantLock默認是非公平鎖,但也可以構造公平鎖。其實對應源碼的實現很簡單,就是在獲取鎖時是否放入隊列
//非公平鎖 的嘗試獲取final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}//公平鎖的 嘗試獲取protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {//唯一的區別就在于這里,如果鎖沒被任何一個線程拿到,不像上面直接去cas爭搶,//而是會hasQueuedPredecessors去判斷是否有其他線程等待的時間更長if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}注意tryLock是個特例sync.nonfairTryAcquire(1);,即使設置的公平鎖,也會非公平的去爭搶鎖。
可重入鎖和不可重入鎖
什么是可重入鎖呢?簡單來說,就是一個線程可以多次拿到一個鎖,Reentrant和synchronize都是可重入鎖
有什么好處?
避免死鎖,如果不是可重入鎖,你拿到鎖了,然后你想進入鎖的另一個方法,你拿不到了!可能造成死鎖
避免了重復的加鎖和解鎖
自旋鎖和阻塞鎖
也就是準備獲取鎖的線程無法獲取到鎖時,就先自旋,不用阻塞,避免了線程切換帶來的開銷。但是可能帶來CPU的浪費,因此有自適應自旋鎖。
synchronize引入的輕量級鎖就是自旋鎖的最好應用。適合少量線程競爭,且每個線程持有鎖的時間不長的情況
可中斷鎖和不可中斷鎖
如果某個線程獲取到了鎖正在執行,線程B正在等待獲取鎖,可是由于等待時間過長,我們可以中斷線程B,這就是可中斷鎖。
synchronize就是不可中斷鎖,lock是可中斷鎖,try( time)和lockInterruptibly都能響應中斷。
鎖優化
- JVM提供了鎖粗化和鎖消除來優化鎖
- 我們在并發編程時要注意,盡量縮小同步代碼塊的范圍,盡量不要鎖住方法,減少加鎖的次數。鎖中不要再包含鎖,容易造成死鎖。選擇合適的鎖和工具類
總結
以上是生活随笔為你收集整理的来说一说你对锁都怎么分类?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mysql默认隔离级别为什么是可重复读?
- 下一篇: AQS简单学习