先定一個全文的基調(diào),本文只是自己對鎖相關(guān)東西的一點(diǎn)想法,寫下來整理下,前文寫了點(diǎn)對鎖的理解,后文貼了點(diǎn)自己嘗試的無鎖數(shù)據(jù)結(jié)構(gòu),不是特別嚴(yán)謹(jǐn),只是我的一點(diǎn)嘗試和探索。
just my humble opinion, for reference only
在并發(fā)程序中,保證訪問的有序性是非常重要的,內(nèi)存模型也規(guī)范了代碼來訪問共享變量的順序,本文不太涉及虛擬機(jī)的一些實(shí)現(xiàn),只是在應(yīng)用層面分析下各自的有點(diǎn)與取舍。
在Java中,最簡單的保證訪問順序的就是用鎖了
Synchronized關(guān)鍵字
Synchronized這個關(guān)鍵字使用是最簡單的,可以加在普通方法名上相當(dāng)于用this來加鎖,用在靜態(tài)方法名上相當(dāng)于this.class來加鎖,也可以單獨(dú)加在代碼塊上自己制定某個對象來加鎖,這樣的例子網(wǎng)上數(shù)不勝數(shù),我也不就在這多展開了,底層實(shí)現(xiàn)是虛擬機(jī)在字節(jié)碼上價了個montior,拋開底層,從應(yīng)用層講的話,就是對對象的對象頭通過CAS重寫,簡單說下這個過程,就是每個對象都有一部分叫做“Mark Word”的空間,一般是32位,上面有著對象的類信息指針、年齡(用于GC)、hash值、以及相關(guān)鎖的信息,synchronized關(guān)鍵字其實(shí)就是每個線程走到這后去改對象頭里面鎖的信息,改成自己的了就是獲取到鎖了,改幾次都改失敗了,那么就得等著掛起了,這里我只是粗略的描述了下過程,大家可以網(wǎng)上搜搜synchronized底層實(shí)現(xiàn),包括JVM優(yōu)化的偏向鎖輕量級鎖等,都值得深究。
顯示鎖
Java中的顯示鎖最多的就是ReentrantLock了,在我的上一篇文章中分析AQS時花了很多筆墨分析其實(shí)現(xiàn)與特性,有問題的讀者可以點(diǎn)鏈接閱讀我前一篇博文。
Synchronized VS ReentrantLock
對于這兩者的比較,相同點(diǎn)很簡單,都是為了保證對于某些非線程安全的區(qū)域的順序訪問,不同點(diǎn)的話,我從功能和實(shí)現(xiàn)去比較下兩者
功能
synchronized這個關(guān)鍵字呢,這樣說,就是他一旦開始等這個鎖,他就會認(rèn)為自己有朝一日定能拿到執(zhí)行權(quán),無比自信,然而ReentrantLock就不這樣,他并不那么堅(jiān)定的認(rèn)為自己一定能拿到鎖,他會試探,就有了tryLock方法,他會給自己留退路,就有了lockInterruptly方法,他不像synchronizd那樣執(zhí)拗,所以tryLock方法還有帶超時機(jī)制的版本。除此之外,ReetrantLock還有綁定多個Condition功能,以及公平非公平模式,其實(shí)還有些api,可以看看源碼里面返回boolean值的方法,在這不多說啦,這就是他們在功能上的差異。實(shí)現(xiàn)
在文章開頭已經(jīng)說到了這兩者實(shí)現(xiàn)的差異,一個是由JVM去實(shí)現(xiàn),加個monitor保證訪問時的排他性,一個是基于AQS實(shí)現(xiàn)的排他類工具,也因?yàn)閮烧叱錾牟煌?,synchronized未來有更多的優(yōu)化余地,現(xiàn)在兩者性能上幾乎也沒差距,官方建議是除非你需要ReentrantLock的功能,不然就用synchronized關(guān)鍵字,最后選哪個還是看你自己咯。
Lock-Free 無鎖
說完鎖,說說無鎖把,無鎖的核心就是CompareAndSet(CAS)了,并發(fā)包里面以CAS為核心實(shí)現(xiàn)的工具類也特別多,可以說整個Atomic包都是把,但是CAS算法也不是萬能的,在極大的并發(fā)程度下性能是不如鎖的,這也很好理解,一群人占著僅有的資源重復(fù)做一些事,這些事又注定只有少部分人能成功,大部分的人效率自然低,Atomic包中的AtomicLong就是以CAS去實(shí)現(xiàn)加減操作,但是新版本的JDK中又多了LongAdder類,這是為什么?原因就是為了盡量避免前面提到的那種“大部分人都失敗少部分人成功”的現(xiàn)象,其思路就是分散熱點(diǎn)域,比如a要加1,AtomicLong就是先取出a的值,再用a+1的值去覆蓋,不斷嘗試直至成功,LongAdder的思路就是把a(bǔ) 分為多個數(shù)值,比如a-3, 2 ,1三個部分,然后選中一部分,做AtomicLong做的事,最后全部累加起來,但是因?yàn)閍被分為多個部分,多個線程執(zhí)行操作時可以很好的分散開來,不會集中在一個地方不斷嘗試,這樣做正確性也是毋庸置疑的,畢竟整體+1和部分+1肯定是相等的。
Atomic包有很多這樣的類,都可以研究研究,而對于無鎖數(shù)據(jù)結(jié)構(gòu)的編寫,那就是一些experts要研究的東西了,在這里我寫兩個玩具demo,讓大家體會下。
Lock Free Queue
無鎖的隊(duì)列自己寫起來也不是太難,因?yàn)槌鲫?duì)入隊(duì)只在頭尾節(jié)點(diǎn),所以只要處理下頭尾節(jié)點(diǎn)就可以,無鎖隊(duì)列的問題在于我在尾節(jié)點(diǎn)插入時,可能此時此刻有同樣的操作在另一個線程進(jìn)行,所以我拿到的尾節(jié)點(diǎn)可能是過期的,所以要通過CAS嘗試。
我們拿尾節(jié)點(diǎn)插入來展示,頭節(jié)點(diǎn)讀者可以對照實(shí)現(xiàn)
public class LockFreeQueue<V> {
private class Node<V> {//內(nèi)部節(jié)點(diǎn)
V value = null;
AtomicReference<Node<V>> next = null;
Node(V value, Node next) {
this.value = value;
this.next = new AtomicReference<>(next);
}
}
private AtomicReference<Node<V>> head = null;
private AtomicReference<Node<V>> tail = null;
public LockFreeQueue(){
Node<V> dummy = new Node<V>(null,null);
head = new AtomicReference<>(dummy);
tail = new AtomicReference<>(dummy);
}
public boolean enQueue(V value) {
Node<V> newNode = new Node<V>(value,null);
boolean success=false;
while(!success){//插入的時候可能有其他節(jié)點(diǎn)搶先入隊(duì),tail會失效
Node<V> oldTail = tail.get();
AtomicReference<Node<V>> nextNode = oldTail.next;
success=nextNode.compareAndSet(null,newNode);
if(success) return true;
}
return false;
}
}
Lock Free List
隊(duì)列還是挺好理解的,下面我貼下我寫了大半天的無鎖單鏈表,不保證可伸縮性,簡單測試了下,因?yàn)閱捂湵聿迦霑羞@樣的問題,比如A->B->C->D的鏈表,我想在BC間插一個X,當(dāng)我拿到B時,按理說我應(yīng)該連成B->X->C這樣的,但是有可能我拿到B的時候,C被人刪了,但是我不知道,我還是按B->X->C連的話,鏈表就被我弄斷了,同理,如果此時此刻B被人刪了,還這么連,鏈表一樣會斷,同理的同理,如果這時候有人在BC中已經(jīng)差了個Y,鏈表已經(jīng)是B->Y->C了,我還按B->X->C連,Y就丟了,所以要顧及的情況很多,remove方法也有如上所述的問題,網(wǎng)上對于無鎖鏈表的實(shí)現(xiàn)不多,我是參考這篇論文實(shí)現(xiàn)的,這篇論文將刪除分為兩步,即先標(biāo)記(邏輯刪除),再實(shí)際刪除,我找了下JDK的工具類,認(rèn)為AtomicMarkableReference這個類。這個類內(nèi)部有個Pair(和C++中的pair差不多)就是一個二元組,第一個是持有的對象,第二個boolean標(biāo)記值,符合要求,下面我貼一下代碼。
public class ConcurrentLinkedList<T> {
private class Node<T> {
public Node(T value, AtomicMarkableReference<Node<T>> next) {
this.value = value;
this.next = next;
}
T value;
AtomicMarkableReference<Node<T>> next;
}
AtomicMarkableReference<Node<T>> head
= new AtomicMarkableReference<>(new Node<>(null, null), false);
//其實(shí)這里還有邏輯分支要處理,就是尾端插入時node.next.isMarked()會
//有null異常,但因?yàn)橹R演示核心思路,所以就略去了(因?yàn)樗接邢蓿? //代碼會寫的很丑,有心的讀者可以自己實(shí)現(xiàn),讓我學(xué)習(xí)學(xué)習(xí))
public boolean insert(T after, T value) {
boolean success = false;
while (!success) {
for (Node<T> node = head.getReference(); node != null && !node.next.isMarked(); node = node.next.getReference()) {
if (node.value.equals(after) && !node.next.isMarked()) {
Node<T> nextNode = node.next.getReference();
Node<T> insertNode = new Node<>(value, new AtomicMarkableReference<>(nextNode, false));
success = node.next.compareAndSet(nextNode, insertNode, false, false);
if (success) return true;
} else if (node.next == null) {//如果已經(jīng)是最后一個結(jié)點(diǎn),還沒有匹配到就返回false
return false;
}
}
}
return false;
}
public boolean remove(T aim) {
boolean success = false;
while (!success) {
for (Node<T> predecessor = head.getReference(); predecessor != null && !predecessor.next.isMarked();
predecessor = predecessor.next.getReference()) {
AtomicMarkableReference<Node<T>> target = predecessor.next; //要刪除的目標(biāo)節(jié)點(diǎn)
AtomicMarkableReference<Node<T>> successor = target.getReference().next;//目標(biāo)節(jié)點(diǎn)的后繼結(jié)點(diǎn)
if (target.getReference().equals(aim) && !successor.isMarked()) {
while (!target.attemptMark(target.getReference(), true)) {}//標(biāo)記已經(jīng)刪除,即邏輯刪除
success = predecessor.next.compareAndSet(target.getReference(), successor.getReference(), true, false);//這里是物理刪除
if (success) return true;
} else if (successor == null)
return false;//如果目標(biāo)節(jié)點(diǎn)的后繼結(jié)點(diǎn)已經(jīng)是null了,說明已經(jīng)到鏈表結(jié)尾了,
}
}
return false;
}
}
我自己實(shí)現(xiàn)的代碼已經(jīng)貼出來了,就當(dāng)成偽代碼看吧!自己也花了很多時間,我手動構(gòu)造了點(diǎn)數(shù)據(jù)沒出現(xiàn)什么問題,但是我不知道更加復(fù)雜的場景會不會出現(xiàn)問題,這只是我給出的思路,not solution,just my opinion,網(wǎng)上我也沒找到很好的關(guān)于無鎖鏈表的實(shí)現(xiàn),只有一些論文,給出了思路,當(dāng)然思路和實(shí)現(xiàn)細(xì)節(jié)還是有差距,我貼出了我的看法,如果有讀者有更好的實(shí)現(xiàn)希望能放出來也讓我學(xué)習(xí)學(xué)習(xí)。
我寫這篇文章不是希望用這么點(diǎn)篇幅就能把很多鎖和無鎖的細(xì)節(jié)都描述的淋漓盡致甚至庖丁解牛,我更多的目的只是希望能作為一篇“雜談”談?wù)勎覍o鎖和鎖的看法,給讀者提供一些我的理解,幫助讀者在自己的“深究之路上”提供更多的墊腳石,可能很多細(xì)節(jié)還是比較粗糙,歡迎吐槽。
最后再聲明下Just my humble opinion,I'm open to discussion
參考論文:(那個插圖我很早保存的,實(shí)在記不起哪里來的了,如有侵權(quán),望通知,必刪)