hello大家好,我是小卡,昨天給大家淺談了高併發下的CAS算法,以及更深層次的compareAndSwapObejct方法。在文章的最後提出了一個問題就是如何解決ABA的問題,今天花一點時間把這個問題給他家講一下。
首先我們再來回顧一波爲什麼會出現ABA?
在多cpu的服務器中可能會出現多線程操作這個容器,並同時執行CAS,因爲哥哥cpu之前的任務調度排序不同,執行的速度也可能會不同,就可能會出現A還在執行compare方法的時候,B線程已經執行完swap操作,同時將內存值修改成了A線程的預期值,這時候計算機以爲操作是對的。但是這確實有個錯誤的操作。
昨天我們也提到過使用AtomicStampReference來解決和這個問題,今天我們就來看看什麼是AtomicStampReference。
public class AtomicStampedReference<V> { // 內部類,atomicStampedReference的本質就只這個類 private static class Pair<T> { // 對象引用 final T reference; // 版本號 final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair;
我們再來看一看他的核心方法:
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; // 標藍方法就是解決ABA問題的關鍵算法 return // 原始值是否等於當前值 原始值 = 線程進入時的node值, 當前值 = 有可能被其他線程操作後的值 expectedReference == current.reference && // 原始版本是否和當前版本一致 expectedStamp == current.stamp && // 新值(替換值) = 原始值 && 新版本號 = 舊版本號 (想當於沒有任何變化) 直接返回true了 ((newReference == current.reference && newStamp == current.stamp) || // 或者 新值、新版本號成功替換 原始值、原始版本號 返回操作成功! casPair(current, Pair.of(newReference, newStamp))); }
他的最底層也是用我們Usafe包下的compareAndSwapObject()方方法。
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
上述代碼就是AtomicStampedReference的核心方法,接下來我們就要學會如何去用,怎麼樣用才能夠有效避免ABA。
package com.cdzg.shop.saleticket; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicStampedReference; /** * @author xiefei * @version 1.0 * @date 2020/5/14 14:40 */ public class Test { public static void main(String[] args) { /** * 這裏需要重點注意的是因爲我們解決ABA問題重點是關注 數據 * 所以我們關注的點是在數據,而不是ConcurrentHashMap這個容器 * 所以我們只需要在將關注的數據用AtomicStampedReference修飾就好了 * 我第一次使用AtomicStampedReference便出現過這個問題 * 希望大家引以爲戒!!!! */ ConcurrentHashMap<String,AtomicStampedReference<String>> concurrentHashMap = new ConcurrentHashMap<>(); AtomicStampedReference<String> stamp = new AtomicStampedReference<>("person",0); String mapKey = "original"; concurrentHashMap.put(mapKey,stamp); // 起線程模擬高併發操作同一個node Thread mainThread = new Thread(() ->{ System.out.println("----進入主線程----"); // 原始數據 AtomicStampedReference<String> reference = concurrentHashMap.get(mapKey); String oldStr = reference.getReference(); int oldValue = reference.getStamp(); // 模擬主線程網絡比較慢 try{ Thread.sleep(2000); }catch (InterruptedException e){ System.out.println(e); } AtomicStampedReference<String> concurrentRef = concurrentHashMap.get(mapKey); System.out.println("當前數據:" + concurrentRef.getReference() + "--- 當前版本: " + concurrentRef.getStamp() ); // 操作數據 boolean mainFlag = reference.compareAndSet(oldStr, "god", oldValue, oldValue + 10); if(mainFlag) concurrentHashMap.put(mapKey,reference); System.out.println("----主線程操作結果----:" + mainFlag); System.out.println("----主線程結束----"); },"主線程"); Thread disturbThread = new Thread(() ->{ System.out.println("----進入干擾線程----"); AtomicStampedReference<String> reference = concurrentHashMap.get(mapKey); String oldStr = reference.getReference(); int oldValue = reference.getStamp(); // 操作數據 boolean disturbFlag = reference.compareAndSet(oldStr, "devil", oldValue, oldValue + 10); concurrentHashMap.put(mapKey,reference); System.out.println("----干擾線程操作結果----:" + disturbFlag); System.out.println("----干擾線程結束----"); },"干擾線程"); mainThread.start(); disturbThread.start(); } }
提出操作結果,我們成功的利用AtomicStampedReference避免了ABA問題!
注:這裏有一點需要提醒大家,面試官可能會在面試的時候問到如何解決ABA的問題,我們在回答的時候要注意這個版本問題。首先這個版本使我們自定義的,其次這個版本的增幅也是我們自定義的 ,如上述代碼。因爲我看見有很博客寫的是內置版本,自動版本+1就很氣,那是錯的,不可取的!
希望大家一鍵三連!!!愛你們!!!
併發編程CAS鏈接:https://blog.csdn.net/qq_39941165/article/details/106092241