淺談Java中ABA問題及避免
本文主要研究的是關(guān)于Java中ABA問題及避免的相關(guān)內(nèi)容,具體如下。
在《Java并發(fā)實(shí)戰(zhàn)》一書的第15章中有一個(gè)用原子變量實(shí)現(xiàn)的并發(fā)棧,代碼如下:
public class Node { public final String item; public Node next; public Node(String item){ this.item = item; } }
public class ConcurrentStack { AtomicReference<Node> top = new AtomicReference<Node>(); public void push(String item){ Node newTop = new Node(item); Node oldTop; do{ oldTop = top.get(); newTop.next = oldTop; } while(!top.compareAndSet(oldTop, newTop)); } public String pop(){ Node newTop; Node oldTop; do{ oldTop = top.get(); if(oldTop == null){ return null; } newTop = oldTop.next; } while(!top.compareAndSet(oldTop, newTop)); return oldTop.item; } }
這個(gè)例子并不會(huì)引發(fā)ABA問題,至于為什么不會(huì),后面再講解,下面先講一下ABA問題
什么是ABA?
引用原書的話:如果在算法中的節(jié)點(diǎn)可以被循環(huán)使用,那么在使用“比較并交換”指令就可能出現(xiàn)這種問題,在CAS操作中將判斷“V的值是否仍然為A?”,并且如果是的話就繼續(xù)執(zhí)行更新操作,在某些算法中,如果V的值首先由A變?yōu)锽,再由B變?yōu)锳,那么CAS將會(huì)操作成功
ABA的例子
有時(shí)候,ABA造成的后果很嚴(yán)重,下面將并發(fā)棧的例子修改一下,看看ABA會(huì)造成什么問題:
public class Node { public final String item; public Node next; public Node(String item){ this.item = item; } }
public class ConcurrentStack { AtomicReference<Node> top = new AtomicReference<Node>(); public void push(Node node){ Node oldTop; do{ oldTop = top.get(); node.next = oldTop; } while(!top.compareAndSet(oldTop, node)); } public Node pop(int time){ Node newTop; Node oldTop; do{ oldTop = top.get(); if(oldTop == null){ return null; } newTop = oldTop.next; TimeUnit.SECONDS.sleep(time); } while(!top.compareAndSet(oldTop, newTop)); return oldTop; } }
注意這里的變化,Node基本沒有變化
重點(diǎn)關(guān)注ConcurrentStack的變化
1、push方法:原來是使用內(nèi)容構(gòu)造Node,現(xiàn)在直接傳入Node,這樣就符合了“在算法中的節(jié)點(diǎn)可以被循環(huán)使用”這個(gè)要求
2、pop方法的sleep,這是模擬線程的執(zhí)行情況,以便觀察結(jié)果
我們先往stack中壓入兩個(gè)Node:
ConcurrentStack stack = new ConcurrentStack(); stack.push(new Node("A")); stack.push(new Node("B"));
然后創(chuàng)建兩個(gè)線程來執(zhí)行出入棧的操作
線程A先執(zhí)行出棧:讓NodeA出棧
stack.pop(3);
因?yàn)槟承┰?,線程A執(zhí)行出棧比較久,用了3s
線程B執(zhí)行出棧之后再入棧:先然NodeA和NodeB出棧,然后讓NodeD,NodeC,NodeA入棧(NodeA在棧頂)
Node A = stack.pop(0); stack.pop(0); stack.push(new Node("D")); stack.push(new Node("C")); stack.push(A);
注意:線程B實(shí)現(xiàn)了節(jié)點(diǎn)的循環(huán)利用,它先將棧里面的內(nèi)容全部出棧,然后入棧,最后棧頂?shù)膬?nèi)容是之前出棧的Node
線程B執(zhí)行完這些動(dòng)作之后,線程A才執(zhí)行CAS,此時(shí)CAS是可以執(zhí)行成功的
按照原來的想法,線程A和B執(zhí)行之后,stack的內(nèi)容應(yīng)該是:C和D,C在棧頂,但這里的執(zhí)行結(jié)果卻是Stack中什么都沒有,這就是ABA問題
如何避免ABA問題
Java中提供了AtomicStampedReference和AtomicMarkableReference來解決ABA問題
AtomicStampedReference可以原子更新兩個(gè)值:引用和版本號(hào),通過版本號(hào)來區(qū)別節(jié)點(diǎn)的循環(huán)使用,下面看AtomicStampedReference的例子:
public class ConcurrentStack { AtomicStampedReference<Node> top = new AtomicStampedReference<Node>(null,0); public void push(Node node){ Node oldTop; int v; do{ v=top.getStamp(); oldTop = top.getReference(); node.next = oldTop; } while(!top.compareAndSet(oldTop, node,v,v+1)); // }while(!top.compareAndSet(oldTop, node,top.getStamp(),top.getStamp()+1)); } public Node pop(int time){ Node newTop; Node oldTop; int v; do{ v=top.getStamp(); oldTop = top.getReference(); if(oldTop == null){ return null; } newTop = oldTop.next; try { TimeUnit.SECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } while(!top.compareAndSet(oldTop, newTop,v,v+1)); // }while(!top.compareAndSet(oldTop, newTop,top.getStamp(),top.getStamp())); return oldTop; } public void get(){ Node node = top.getReference(); while(node!=null){ System.out.println(node.getItem()); node = node.getNode(); } } }
注意:不能使用注釋中的方式,否則就和單純使用原子變量沒有區(qū)別了
AtomicMarkableReference可以原子更新一個(gè)布爾類型的標(biāo)記位和引用類型,看下面的例子:
AtomicMarkableReference<Node> top = new AtomicMarkableReference<Node>(null,true); public void push(Node node){ Node oldTop; Boolean v; do{ v=top.isMarked(); oldTop = top.getReference(); node.next = oldTop; } while(!top.compareAndSet(oldTop, node,v,!v)); }
總結(jié)
以上就是本文關(guān)于淺談Java中ABA問題及避免的全部內(nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
Java中如何利用Set判斷List集合中是否有重復(fù)元素
在開發(fā)工作中,我們有時(shí)需要去判斷List集合中是否含有重復(fù)的元素,這時(shí)候我們不需要找出重復(fù)的元素,我們只需要返回一個(gè)?Boolean?類型就可以了,下面通過本文給大家介紹Java中利用Set判斷List集合中是否有重復(fù)元素,需要的朋友可以參考下2023-05-05深度解析Java中的國際化底層類ResourceBundle
做項(xiàng)目應(yīng)該都會(huì)實(shí)現(xiàn)國際化,那么大家知道Java底層是如何實(shí)現(xiàn)國際化的嗎?這篇文章就來和大家深度解析一下Java中的國際化底層類ResourceBundle,希望對(duì)大家有所幫助2023-03-03詳談spring中bean注入無效和new創(chuàng)建對(duì)象的區(qū)別
這篇文章主要介紹了spring中bean注入無效和new創(chuàng)建對(duì)象的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02比較Java數(shù)組和各種List的性能小結(jié)
這篇文章主要是分別對(duì)Java數(shù)組、ArrayList、LinkedList和Vector進(jìn)行隨機(jī)訪問和迭代等操作,并比較這種集合的性能。有需要的可以參考借鑒。2016-08-08Maven將Jar包打入本地倉庫的實(shí)現(xiàn)
項(xiàng)目需要用到一個(gè)Jar包,不能從遠(yuǎn)程倉庫拉取,只有一個(gè)Jar包,所以需要將Jar包打入到本地倉庫才能引入項(xiàng)目,本文主要介紹了Maven將Jar包打入本地倉庫的實(shí)現(xiàn),感興趣的可以了解一下2023-12-12詳解如何用Java實(shí)現(xiàn)對(duì)m3u8直播流抽幀
抽幀(frame extraction)是指從視頻流中提取一些特定的幀,通常是關(guān)鍵幀或者隨機(jī)幀,以供后續(xù)處理。這篇文章主要為大家介紹了如何用Java實(shí)現(xiàn)對(duì)m3u8直播流抽幀,需要的可以參考一下2023-03-03Java常問面試內(nèi)容--數(shù)組、聲明、初始化、冒泡、多維數(shù)組、稀疏數(shù)組
這篇文章主要介紹了Java多線程面試題(面試官常問),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07Java面向?qū)ο蟪绦蛟O(shè)計(jì):類的定義,靜態(tài)變量,成員變量,構(gòu)造函數(shù),封裝與私有,this概念與用法詳解
這篇文章主要介紹了Java面向?qū)ο箢惖亩x,靜態(tài)變量,成員變量,構(gòu)造函數(shù),封裝與私有,this概念與用法,較為詳細(xì)的分析了Java類的定義,靜態(tài)變量,成員變量,構(gòu)造函數(shù),封裝,私有等相關(guān)原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04