tcp out of order解决_Java解决CAS机制中ABA问题的方案
通過對atomic包的分析我們知道了CAS機制,我們在看一下CAS的公式。
CAS(V,A,B)1:V表示內存中的地址2:A表示預期值3:B表示要修改的新值CAS的原理就是預期值A與內存中的值相比較,如果相同則將內存中的值改變成新值B。這樣比較有兩類:
第一類:如果操作的是基本變量,則比較的是值是否相等。
第二類:如果操作的是對象的引用,則比較的是對象在內存的地址是否相等。
總結一句話就是:比較并交換。
其實CAS是Java樂觀鎖的一種實現機制,在Java并發包中,大部分類就是通過CAS機制實現的線程安全,它不會阻塞線程,如果更改失敗則可以自旋重試,但是它也存在很多問題:
1:ABA問題,也就是說從A變成B,然后就變成A,但是并不能說明其他線程并沒改變過它,利用CAS就發現不了這種改變。2:由于CAS失敗后會繼續重試,導致一致占用著CPU。用一個圖來說明ABA的問題。
線程1準備利用CAS修改變量值A,但是在修改之前,其他線程已經將A變成了B,然后又變成A,即A->B->A,線程1執行CAS的時候發現仍然為A,所以CAS會操作成功,但是其實目前這個A已經是其他線程修改的了,但是線程1并不知道,最終內存值變成了B,這就導致了ABA問題。
接下來我們看一個關于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(); }}上面例子運行結果如下,線程1并不知道線程2和線程3已經改過了值,線程1發現此時還是A則會更改成功,這就是ABA:
所以每種技術都有它的兩面性,在解決了一些問題的同時也出現了一些新的問題,在JDK中也為我們提供了兩種解決ABA問題的方案,接下來我們就看一下是怎樣解決的。
本篇文章的主要內容:
1:AtomicMarkableReference實例和源碼解析2:AtomicStampedReference實例和源碼解析一、AtomicMarkableReference實例和源碼解析
上面的例子如果利用這個類去實現,會怎樣呢?稍微改變上面的代碼如下:
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(); }}運行結果如下:
是不是解決了這個ABA的問題,AtomicMarkableReference僅僅用一個boolean標記解決了這個問題,那接下來我們進入源碼看看它是怎么一種機制。
1:成員變量
private volatile Pair pair;定義了一個被關鍵字volatile修飾的Pair,那Pair是什么對象呢?
private static class Pair {//封裝了我們傳遞的對象 final T reference;//這個就是boolean標記 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:構造函數
public AtomicMarkableReference(V initialRef, boolean initialMark) { pair = Pair.of(initialRef, initialMark);}這個構造函數就是調用Pair中的of()方法,把我們需要操作的對象和boolean標記傳遞進去。
那說明以后的操作都是基于Pair這個類進行操作了。那接下來我們看一下它的CAS方法是怎樣定義的。
//expectedReference表示我們傳遞的預期值//newReference表示將要更改的新值//expectedMark表示傳遞的預期boolean類型標記//newMark表示將要更改的boolean類型標記的新值。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后的代碼分解后主要有三大邏輯:
第一個邏輯&&(第二個邏輯 || 第三個邏輯)
第一個邏輯:預期對象和預期的boolean類型標記必須和內部的Pair中相等
expectedReference == current.reference && expectedMark == current.mark如果第一個邏輯是true,才能繼續往下判斷,否則直接返回false。
第二個邏輯:如果這個邏輯為true,就不在執行第三個邏輯了
newReference == current.reference && newMark == current.mark如果新的將要更改的對象和新的將要更改的boolean類型的標記和內部Pair的相等,則就不在執行第三個邏輯了。如果為false,則繼續往下執行第三個邏輯
第三個邏輯:CAS邏輯
casPair(current, Pair.of(newReference, newMark))如果預期的對象和預期的boolean標記和Pair都相等,但是新的對象和新的boolean標記和Pair不相等,此時需要進行CAS更新了
從上面的講解大家能不能總結出來它是怎樣解決ABA的問題的,現在我們總結以下:
它是通過把操作的對象和一個boolean類型的標記封裝成Pair,而Pair有被volatile修飾,說明只要更改其他線程立刻可見,而只有Pair中的兩個成員變量都相等。來解決CAS中ABA的問題的。一個偽流程圖如下:
二、AtomicStampedReference實例和源碼解析
上面我們知道了AtomicMarkableReference是通過添加一個boolean類型標記和操作的對象封裝成Pair來解決ABA問題的,但是如果想知道被操作對象更改了幾次,這個類就無法處理了,因為它僅僅用一個boolean去標記,所以AtomicStampedReference就是解決這個問題的,它通過一個int類型標記來代替boolean類型的標記。
上面的例子更改如下:
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(); }}運行結果:
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); }}這種結果和AtomicMarkableReference中的Pair結構類似,只不過是把boolean類型標記改成了int類型標記。
2:構造函數
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問題的兩種解決方案,他們的原理是相同的,就是添加一個標記來記錄更改,兩者的區別如下:
1:AtomicMarkableReference利用一個boolean類型的標記來記錄,只能記錄它改變過,不能記錄改變的次數2:AtomicStampedReference利用一個int類型的標記來記錄,它能夠記錄改變的次數。atomic包中的類已經介紹結束,接下來一篇文章我將對atomic做一個總結,然后就開始Java并發包中lock包進行全面解析。
總結
以上是生活随笔為你收集整理的tcp out of order解决_Java解决CAS机制中ABA问题的方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android日志[进阶篇]二-分析堆栈
- 下一篇: java quartz spring_J