tcp out of order解决_Java解决CAS机制中ABA问题的方案
通過對atomic包的分析我們知道了CAS機(jī)制,我們在看一下CAS的公式。
CAS(V,A,B)1:V表示內(nèi)存中的地址2:A表示預(yù)期值3:B表示要修改的新值CAS的原理就是預(yù)期值A(chǔ)與內(nèi)存中的值相比較,如果相同則將內(nèi)存中的值改變成新值B。這樣比較有兩類:
第一類:如果操作的是基本變量,則比較的是值是否相等。
第二類:如果操作的是對象的引用,則比較的是對象在內(nèi)存的地址是否相等。
總結(jié)一句話就是:比較并交換。
其實CAS是Java樂觀鎖的一種實現(xiàn)機(jī)制,在Java并發(fā)包中,大部分類就是通過CAS機(jī)制實現(xiàn)的線程安全,它不會阻塞線程,如果更改失敗則可以自旋重試,但是它也存在很多問題:
1:ABA問題,也就是說從A變成B,然后就變成A,但是并不能說明其他線程并沒改變過它,利用CAS就發(fā)現(xiàn)不了這種改變。2:由于CAS失敗后會繼續(xù)重試,導(dǎo)致一致占用著CPU。用一個圖來說明ABA的問題。
線程1準(zhǔn)備利用CAS修改變量值A(chǔ),但是在修改之前,其他線程已經(jīng)將A變成了B,然后又變成A,即A->B->A,線程1執(zhí)行CAS的時候發(fā)現(xiàn)仍然為A,所以CAS會操作成功,但是其實目前這個A已經(jīng)是其他線程修改的了,但是線程1并不知道,最終內(nèi)存值變成了B,這就導(dǎo)致了ABA問題。
接下來我們看一個關(guān)于ABA的例子:
public class AtomicMarkableReferenceTest { private final static String A = "A"; private final static String B = "B"; private final static AtomicReference ar = new AtomicReference<>(A); public static void main(String[] args) { new Thread(() -> { try { Thread.sleep(Math.abs((int) (Math.random() * 100))); } catch (InterruptedException e) { e.printStackTrace(); } if (ar.compareAndSet(A, B)) { System.out.println("我是線程1,我成功將A改成了B"); } }).start(); new Thread(() -> { if (ar.compareAndSet(A, B)) { System.out.println("我是線程2,我成功將A改成了B"); } }).start(); new Thread(() -> { if (ar.compareAndSet(B,A)) { System.out.println("我是線程3,我成功將B改成了A"); } }).start(); }}上面例子運行結(jié)果如下,線程1并不知道線程2和線程3已經(jīng)改過了值,線程1發(fā)現(xiàn)此時還是A則會更改成功,這就是ABA:
所以每種技術(shù)都有它的兩面性,在解決了一些問題的同時也出現(xiàn)了一些新的問題,在JDK中也為我們提供了兩種解決ABA問題的方案,接下來我們就看一下是怎樣解決的。
本篇文章的主要內(nèi)容:
1:AtomicMarkableReference實例和源碼解析2:AtomicStampedReference實例和源碼解析一、AtomicMarkableReference實例和源碼解析
上面的例子如果利用這個類去實現(xiàn),會怎樣呢?稍微改變上面的代碼如下:
public class AtomicMarkableReferenceTest { private final static String A = "A"; private final static String B = "B"; private final static AtomicMarkableReference ar = new AtomicMarkableReference<>(A, false); public static void main(String[] args) { new Thread(() -> { try { Thread.sleep(Math.abs((int) (Math.random() * 100))); } catch (InterruptedException e) { e.printStackTrace(); } if (ar.compareAndSet(A, B, false, true)) { System.out.println("我是線程1,我成功將A改成了B"); } }).start(); new Thread(() -> { if (ar.compareAndSet(A, B, false, true)) { System.out.println("我是線程2,我成功將A改成了B"); } }).start(); new Thread(() -> { if (ar.compareAndSet(B, A, ar.isMarked(), true)) { System.out.println("我是線程3,我成功將B改成了A"); } }).start(); }}運行結(jié)果如下:
是不是解決了這個ABA的問題,AtomicMarkableReference僅僅用一個boolean標(biāo)記解決了這個問題,那接下來我們進(jìn)入源碼看看它是怎么一種機(jī)制。
1:成員變量
private volatile Pair pair;定義了一個被關(guān)鍵字volatile修飾的Pair,那Pair是什么對象呢?
private static class Pair {//封裝了我們傳遞的對象 final T reference;//這個就是boolean標(biāo)記 final boolean mark; private Pair(T reference, boolean mark) { this.reference = reference; this.mark = mark; } static Pair of(T reference, boolean mark) { return new Pair(reference, mark); }}2:構(gòu)造函數(shù)
public AtomicMarkableReference(V initialRef, boolean initialMark) { pair = Pair.of(initialRef, initialMark);}這個構(gòu)造函數(shù)就是調(diào)用Pair中的of()方法,把我們需要操作的對象和boolean標(biāo)記傳遞進(jìn)去。
那說明以后的操作都是基于Pair這個類進(jìn)行操作了。那接下來我們看一下它的CAS方法是怎樣定義的。
//expectedReference表示我們傳遞的預(yù)期值//newReference表示將要更改的新值//expectedMark表示傳遞的預(yù)期boolean類型標(biāo)記//newMark表示將要更改的boolean類型標(biāo)記的新值。public boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) { Pair current = pair; return expectedReference == current.reference && expectedMark == current.mark && ((newReference == current.reference && newMark == current.mark) || casPair(current, Pair.of(newReference, newMark)));}上面的return后的代碼分解后主要有三大邏輯:
第一個邏輯&&(第二個邏輯 || 第三個邏輯)
第一個邏輯:預(yù)期對象和預(yù)期的boolean類型標(biāo)記必須和內(nèi)部的Pair中相等
expectedReference == current.reference && expectedMark == current.mark如果第一個邏輯是true,才能繼續(xù)往下判斷,否則直接返回false。
第二個邏輯:如果這個邏輯為true,就不在執(zhí)行第三個邏輯了
newReference == current.reference && newMark == current.mark如果新的將要更改的對象和新的將要更改的boolean類型的標(biāo)記和內(nèi)部Pair的相等,則就不在執(zhí)行第三個邏輯了。如果為false,則繼續(xù)往下執(zhí)行第三個邏輯
第三個邏輯:CAS邏輯
casPair(current, Pair.of(newReference, newMark))如果預(yù)期的對象和預(yù)期的boolean標(biāo)記和Pair都相等,但是新的對象和新的boolean標(biāo)記和Pair不相等,此時需要進(jìn)行CAS更新了
從上面的講解大家能不能總結(jié)出來它是怎樣解決ABA的問題的,現(xiàn)在我們總結(jié)以下:
它是通過把操作的對象和一個boolean類型的標(biāo)記封裝成Pair,而Pair有被volatile修飾,說明只要更改其他線程立刻可見,而只有Pair中的兩個成員變量都相等。來解決CAS中ABA的問題的。一個偽流程圖如下:
二、AtomicStampedReference實例和源碼解析
上面我們知道了AtomicMarkableReference是通過添加一個boolean類型標(biāo)記和操作的對象封裝成Pair來解決ABA問題的,但是如果想知道被操作對象更改了幾次,這個類就無法處理了,因為它僅僅用一個boolean去標(biāo)記,所以AtomicStampedReference就是解決這個問題的,它通過一個int類型標(biāo)記來代替boolean類型的標(biāo)記。
上面的例子更改如下:
public class AtomicMarkableReferenceTest { private final static String A = "A"; private final static String B = "B"; private static AtomicInteger ai = new AtomicInteger(1); private final static AtomicStampedReference ar = new AtomicStampedReference<>(A, 1); public static void main(String[] args) { new Thread(() -> { try { Thread.sleep(Math.abs((int) (Math.random() * 100))); } catch (InterruptedException e) { e.printStackTrace(); } if (ar.compareAndSet(A, B, 1,2)) { System.out.println("我是線程1,我成功將A改成了B"); } }).start(); new Thread(() -> { if (ar.compareAndSet(A, B, ai.get(),ai.incrementAndGet())) { System.out.println("我是線程2,我成功將A改成了B"); } }).start(); new Thread(() -> { if (ar.compareAndSet(B, A, ai.get(),ai.incrementAndGet())) { System.out.println("我是線程3,我成功將B改成了A"); } }).start(); }}運行結(jié)果:
1:成員變量
private volatile Pair pair;private static class Pair { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static Pair of(T reference, int stamp) { return new Pair(reference, stamp); }}這種結(jié)果和AtomicMarkableReference中的Pair結(jié)構(gòu)類似,只不過是把boolean類型標(biāo)記改成了int類型標(biāo)記。
2:構(gòu)造函數(shù)
public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp);}3:CAS方法
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp)));}上面分析了JDK中解決CAS中ABA問題的兩種解決方案,他們的原理是相同的,就是添加一個標(biāo)記來記錄更改,兩者的區(qū)別如下:
1:AtomicMarkableReference利用一個boolean類型的標(biāo)記來記錄,只能記錄它改變過,不能記錄改變的次數(shù)2:AtomicStampedReference利用一個int類型的標(biāo)記來記錄,它能夠記錄改變的次數(shù)。atomic包中的類已經(jīng)介紹結(jié)束,接下來一篇文章我將對atomic做一個總結(jié),然后就開始Java并發(fā)包中l(wèi)ock包進(jìn)行全面解析。
總結(jié)
以上是生活随笔為你收集整理的tcp out of order解决_Java解决CAS机制中ABA问题的方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android日志[进阶篇]二-分析堆栈
- 下一篇: java美元兑换,(Java实现) 美元