一.ReentrantLock特點:
1.1 是獨佔鎖並且是可重入的:
獨佔互斥的,需要手動釋放鎖;
可重入的,但是要釋放相同次數的鎖;
1.2 默認是非公平鎖,也可以實現公平鎖:
非公平鎖:當前線程直接嘗試獲取鎖,不管自己是不是身處隊尾;
公平鎖:按照隊列順序來,前面還有就等待;
創建的時候,加上參數true是公平鎖,公平鎖能夠避免線程飢餓
1.3 可以獲取鎖時限等待:(用來輔助解決死鎖):
在規定時間內獲取不到鎖,tryLock(),返回true false;
1.4 可以中斷線程lockInterruptibly():
1.5 condition:
condition 的使用代替了 wait 和 notify, 並且是在lock()方法調用之後使用,cndition signal()喚醒其他線程;
實現隊列 :
private Lock lock = new ReentrantLock();
private Condition incon = lock.newCondition(); //進隊列
private Condition outcon = lock.newCondition(); //出隊列
private LinkedList<Integer> list = new LinkedList<Integer>();
private int size=100;
public void inLock(int i){
try {
lock.lock();
while (list.size() == size)
incon.await(); //等待
list.add(i);
System.out.println("入隊 "+i);
outcon.signal(); //喚醒
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void outLock(){
try {
lock.lock();
while (list.size() == 0)
outcon.await(); //等待
int e= list.removeFirst();
System.out.println("出隊列 "+e);
outcon.signal(); //喚醒
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
1.6 公平鎖 非公平鎖的介紹:
流程 是嘗試獲取鎖,獲取失敗則封裝成節點放入對象中;
而獲取鎖的具體過程是
公平鎖;
先看下state =0,如果=,則說明是當前鎖沒被持有,並且判斷是否有其他線程比當前線程在同步隊列中等待的時間更長,後通過CAS操作獲取同步狀態,就獲取鎖;
否則檢測下當前線程是否是等於鎖線程,因爲是可重入鎖,所以會state++;等釋放的時候在減去,一直減到0;
否則會添加到隊列中;(在添加到雙向隊列中)
非公平鎖:
先看下state =0,如果=,當前線程會直接先嚐試修改state的狀態,如果修改成功直接獲得鎖,如果修改不成功就還是按照公平鎖來;
compareAndSetState 方法搶佔式加鎖,加鎖成功則將自己設爲持鎖線程,並返回;
若加鎖失敗,則調用 acquire 方法,將線程置於同步隊列尾部進行等待;
線程在同步隊列中成功獲取鎖,則將自己設爲持鎖線程後返回;
若同步狀態不爲0,且當前線程爲持鎖線程,則執行重入邏輯;
1.7 AQS:reentrantLock基於aQs原理
AQS是基於鏈表的雙向隊列,頭結點是當前獲取鎖的線程,對於所有沒有獲取鎖的都排在隊列中標,當頭結點運行完成後,會從隊列中刪除;
2.Synchronize
synchronize 底層 任何一個對象都有一個monitor相關聯,當montior被線程持有之後,對象就處於鎖定狀態,(存儲在java對象頭裏的是);
原理: jvm 通過進入和退出對象監視器實現(montior)來實現方法 、同步塊的同步。
具體是在調用同步塊之前插入一個montiorenter指令,退出的時候插入一個montiorexit指令。保證同一時刻只能有一個線程獲取,其他線程被阻塞等待。
同步代碼塊:
當線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor所有權,即嘗試獲取對象的鎖;monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置。
同步方法:
當執行方法時,會檢測Acc_SYNCHORNIZE 是否被設置過,如果設置了,就先進行獲取montior,執行完之後釋放montior,在執行期間其他線程不能夠在獲取。
對象在內存中的佈局分爲三塊區域:對象頭、對象實際數據和填充數據;
對象頭:
對象頭裏面放的是: 類型指針(用來確定實例是屬於哪個類) 和 標記字段;
標記字段放的是該實例運行時的一些信息,如哈希碼,gc年齡代, monitor引用指針等;
monitor:是線程私有的數據結構,線程有一個monitor record表,每一個被鎖住的對象都與一個montior相關聯,
同時,montior中有一個ower字段,存放的是與持有鎖的線程的唯一標誌。
鎖優化:
偏向鎖:
在大多數的情況,往往是不存在競爭的,並且很可能是同一線程連續多次獲取這個鎖,爲了降低獲得鎖的代價,出現了偏向鎖。
當該鎖第一次被線程訪問的時候, 線程訪問該鎖並獲取鎖,在對象頭和棧幀中的鎖記錄裏存儲該線程的id。當該現場再次訪問的時候,只需要簡單的測試西夏,看對象頭markword中存在的是否是該線程id,如果是則說明該線程已經獲取到了該鎖,不必在去通過cas比較來獲得鎖。 如果測試失敗,則先去對象頭中查看是否存在偏向鎖的標誌符=1,如果存在說明是偏向鎖,則進行cas競爭嘗試讓markword中指向當前線程的id,如果不是則進行cas競爭獲取鎖。偏向鎖轉向輕量級鎖。
核心原理: 如果一個線程獲得了鎖,那麼鎖就進入偏向模式,此時Mark Word 的結構也變爲偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供程序的性能。
適用於: 沒有鎖競爭的場合,偏向鎖有很好的優化效果,畢竟極有可能連續多次是同一個線程申請相同的鎖。但是對於鎖競爭比較激烈的場合,偏向鎖就失效了。
輕量級鎖:
使用的是: 對絕大部分的鎖,在整個同步週期內都不存在競爭”有訪問鎖,適用於不存在競爭的情況
當關閉偏向鎖 或者 當多個競爭存在,導致偏向鎖升級爲輕量級鎖。
具體
1.獲取鎖 。
第一步:判斷是否處於無鎖狀態。如果是,則在當前線程的棧幀中創建一個lock record鎖記錄,將對象頭中的markword存儲到線程棧幀的鎖記錄中。 如果不是轉向第三步。
第二步: 通過cas嘗試將對象頭中的markword指向線程棧幀中的鎖記錄表,如果成功表示當前線程獲取鎖,將鎖標誌位變成00。如果失敗,嘗試自旋鎖獲取,失敗,轉向第三步。
第三步:判斷當前rmarkword指向的是否是當前線程的鎖記錄表,如果是,則表示當前線程已經獲取鎖。如果不是,存在競爭則轉向重量級鎖。
2. 釋放鎖
取出在lock record中的保存的markword數據,嘗試cas替換當前對象的markword,成功表示釋放鎖了。失敗,則表示存在鎖競爭,膨脹成重量級鎖。
重量級鎖:
理念: 對絕大部分的鎖,在整個同步週期內都不存在競爭”
適用場景: 線程交替執行同步塊的場合,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹爲重量級鎖。
鎖粗化:
將多次連續的加鎖/解鎖操作,換成一個更大範圍的鎖操作。
鎖消除:
對於些不必要的鎖操作,撤銷,節省無意義的鎖操作消耗時間。
自旋鎖:
當獲取鎖失敗的時候,不會立刻掛起,而是持續一段時間進行嘗試獲取。看當前鎖是不是可以在短時間內就釋放了。
自適應自旋鎖:
是爲了解決自旋鎖中自旋的次數問題,讓jvm更加的智能。自旋的次數不固定,線程自旋成功了,下次這個鎖自旋的次數增多;自旋失敗了,成功的次數很少,那麼這個鎖對應的下次自旋的次數減少。
樂觀鎖悲觀鎖:
樂觀鎖: 樂觀的認爲在拿數據的時候,當前的數據沒有其他線程進行修改,不加鎖,當前線程在更新數據的時候,需要判斷下當前數據是否被修改。
方式:
通過版本控制號 或者cas;
版本控制號:給數據設置個version版本控制號, 初始變量=0,線程在準備修改數據的時候,同時也會讀取這個數字, 提交更新時, 會對比下現在數據的version和開始時讀取的version, 二者一致纔會進行提交version+1,否則重試,知道更新成功。
CAS:
在準備修改數據的時候,會通過cas進行比較,cas有三個值,一個是預期的現存進行比較的值A,一個是將要寫入的值B,一個是需要讀寫的內存上的值V,如果內存地址上的值與預期的值相等,則修改值。
使用場景 :讀操作比較多,而很少發生寫操作競爭的時候。
缺點: cas 的ABA問題,如果其他線程佔用時間過長,當前新線程自旋一直等待,佔用cpu,循環時間長開銷大。只能保證一個變量的原子性操作;a=a+b;無法保證
優點:使用與衝突比較少的情況,多讀操作,這樣的話可以減少鎖的開銷,提高吞吐量。
悲觀鎖: 悲觀的認爲每次取數據的時候,會有其他線程修改數據,所以先加上鎖,保證自己在修改讀取數據的時候,其他線程無法進行操作。同一時刻只能有一個線程進行操作。
向retrantLock synchroncized 都是悲觀鎖的實現。