常见锁的概念
常見的鎖的概念
修改 使用鎖 或者同步機制
僅僅給變量添加volatile 是不行的 還會出現多賣少買狀況
synchronized 簡介 :非常經典的處理手段,具體使用有多種形式,它的核心思想就是修飾一個方法或者一段代碼,這段代碼不能同時兩個以上的線程同時運行。
代碼塊 中的this 是調用該方法的對象 一般都是使用代碼塊
理解一下synchronized 直譯過來 是同步的意思 但是我們京城稱呼其為一種鎖,為什么叫鎖,原則上每個對象都可以持有一把鎖,當某一個線程操作這個對象時, 這個線程就獲得該對象的鎖,在此期間其他線程就無法操作該對象了
Lock
簡介:Lock是自JDK1.5 之后推出的一個接口,當熱也是一系列子實現類,接口非常重要,定義了Java所認定的鎖的概念,跟synchronized有顯著區別,
synchronized是一個關鍵字,使用它可以實現類似鎖的效果,但是嚴格的來說它并不是鎖,Lock是一個接口,詳細描述了鎖的概念,其中重點包含若干方法,這些方法就是核心內容了。
方法:
lock():這個方法用來加鎖,或者可以理解為獲取對象的鎖,如果獲取成功,就執行下面的代碼,若獲取失敗則一直等待,重復嘗試獲取
lockInterruptibly(); 也是嘗試獲取鎖,但是對應帶了Interrupter這個部分,所以對于獲取鎖的結果會進行進一步工作;
tryLock(); 嘗試獲取鎖,返回值boolean類型,會返回獲取鎖的結果,跟lock()不一樣,如果獲取失敗,返回false ,并且不會繼續嘗試獲取;
unlock():解鎖,運行之后即可釋放鎖,就能讓其他線程獲取了;
ReentrantLock:
是最常用的子實現類,加強對它的熟悉字面翻譯它叫重入鎖,確實它也是符合重入鎖的要求,但是它僅僅可以表示重入鎖。
可重入鎖:
是一個概念,可重用鎖在大部分時候并不直接表示RenntrantLock,可重用鎖強調的是一種類型,這個類型關鍵特點是可重入,什么是可重入?
就是一個已經獲得鎖的線程還能繼續調用加鎖的代碼。
這里我們強調可重入是一種典型的鎖的類型,我們自己可以編碼實現一些可重入鎖,在實際項目中它的運用可以體現靈活性等等諸多優勢點,就不一一展開。
ThreadDemo類
/*** @Author: Jiangjun* @Date: 2019/10/7 11:53* @Description: 描述購票的邏輯*/ public class ThreadDemo implements Runnable{Lock lock = new ReentrantLock();volatile int num = 20;@Overridepublic void run() {lock.lock();while (num>0){addlock();System.out.println(Thread.currentThread().getName() + "認為此時還有票可選");num = num - 1;System.out.println(Thread.currentThread().getName() + "買到了一張票,還剩" + num);}lock.unlock();}public void addlock(){lock.lock();}}主函數:
public class TestMain {public static void main(String[] args) {ThreadDemo td = new ThreadDemo();Thread t1 = new Thread(td,"掌上編程");Thread t2 = new Thread(td,"公眾號");t1.start();t2.start();} }公平鎖:
公平=先來后到,先來的獲取鎖,就來的后獲取鎖。
同樣公平鎖也是一種概念,也是一種鎖的類型,如果設計鎖的機制符合公平要求,那么這個鎖就是公平的鎖。
若適用購票場景,如果強調先來后到,(從性能角度考慮)就必須采取特備的方案來實現這種先后順序,這樣必然會帶來性能損失,所以公平鎖的性能會劣于非公平鎖。
如果我們使用非公平鎖也會帶帶來一定風險,容易造成真實意義上的不公平,就有一些線程老早就在等待了,但是運氣不好一直拿不到鎖,這個可以成為鎖饑餓現象。
因此我們強調根據情況來選擇鎖是否公平,一般如果要避免掉鎖饑餓現象,就要考慮使用公平鎖,否則可以使用非公平鎖。
Synchronized 只能是非公平鎖
ReentrantLock 是后來加入的類,所以在設計上更加完善,可以直接通過構造函數來制定鎖是否公平。
細節注意:
養成比較規范的編碼習慣,一旦使了Lock,要充分考慮到可能出現的報錯情況,所以一般編碼會這樣寫try…catch…fianlly
public class LockFairThread implements Runnable{//創建公平鎖private static ReentrantLock lock = new ReentrantLock(true);@Overridepublic void run() {lock.lock();try{System.out.println(Thread.currentThread().getName() + "獲得鎖");}catch (Exception e){System.out.println("異常相關提示");}finally {lock.unlock();}}}獨享鎖/共享鎖:
獨享鎖只能提供一個線程所享用,共享鎖表示鎖可以提供多個線程享用。
這里會覺得比較奇怪,感覺鎖好像就是提供一個線程使用,這個其實不絕對,比如一些操作是可以共享的,比如我們在修改數據時,必須要獨享,不允許多個地方同時對數據進行修改,容易導致一些錯誤,但是在讀取數據時,其實可以多線程一起讀。共享鎖主要在一些場合使用,提升一下靈活性。
互斥鎖/讀寫鎖
樂觀鎖/悲觀鎖:
樂觀鎖強調認為并發不一定會導致數據出現不一致等問題,所以原則來說就是允許并發的發生,比如允許多個線程同時讀寫同一個變量,但是單純的樂觀相信也是不行的,還要想辦法控制一下。
所以樂觀鎖強調一種思路和理念通過方式來允許并發出現又能夠規避掉出錯的情況,典型的方案就是使用版本號控制,下面就詳細描述一下:
場景是修改商品的庫存數據,比如原本庫存100個,現在多個線程在操作商品表,他們在操作的時候,需要先讀取數據,再減數據,比如先要讀一下商品庫存還有多少個,如果足夠再做減法,減去商品的庫存。這個商品中如果使用悲觀鎖,就是非常簡單的直接把從讀商品到減商品做成同步的,同時同一時間只允許一個線程來做先讀后減。這就是悲觀鎖的典型思路,就是覺得一旦有并發出現,多個線程同時讀寫數據,一旦導致數據出現問題所以不允許出現并發情況的。顯然悲觀鎖使用得非常少。使用樂觀鎖,就是允許多個線程同時去讀寫商品庫存數據,但是想要個法子讓他們同時讀寫之后還要不出問題。所以我們要加入一個檢查機制,如果發現出現問題的話,就取這次操作,重新來過.具體做法就是給商品表額外添加一個字段,叫做version,然后每次讀取商品之前先檢查詢version,再每一次修改商品之后讓version+1,d當然操作和處理商品之前要檢查一下version是否匹配。
下面我們通過一個現時場景進行描述:
- 線程A查看庫存,此時讀取的版本號,發現是0.
- 線程B查看庫存,此時讀取的版本號,發現是0.
- 線程A此時想要修改庫存,所以檢查下庫存了,這時可以把檢查版本號和扣減庫存一次性完成,比如執行如下sql:UPDATE 表明 SET 庫存=庫存-20,version=version+1 WHERE 商品id=…AND version=0
- 執行第三步之后,趕緊檢查一下自己修改是否成功,如果修改失敗,意味著什么?意味著此時在查詢數據時,根據版本號=0沒有查詢出來,表示在第一步和第三步之間,商品已經被修改了,所以就要撤銷重做。如果修改成功,表示第一步和第三步之間沒有出現問題,商品庫存沒有被其他線程修改過,就可以順利的成功修改。
- 讓B修改商品,此時就會失敗,因為線程A在過程中已經修改過商品庫存了,之前N讀取的庫存已經失效,它需要重新讀取。
面試10個問題 1-2多線程
-
多線程基礎(線程狀態 、run/start 、 Thread/Runnable)
-
常用方法和經典問題(wait notify join 生產消費者)
-
同步基礎 (volatile synchronized)
-
核心類庫(Callable 集合)
-
JUC原子類(LongAdder AtomicReference)
-
JUC鎖 (Lock AQS RenntrantLock ReadWriteLock)
-
鎖概念(重入鎖、公平鎖、讀寫鎖、樂觀鎖等等)
總結
- 上一篇: 【java--反射】注解(反射解析注解+
- 下一篇: JDK库rt包中常用包说明