cas 通常認爲是compare and swap 也能夠認爲是compare and set java
(1) P 變量內存地址 (2)E 指望值 ,CPU作計算以前拿出來的舊值 (3) X 須要設置的新值
原子操做爲: 拿出內存地址當前的值A ,比較A == E ? 是 : 設置P內存的值爲X 否:結束。。失敗面試
(1) 第一篇 話說synchronized 畫過CAS的流程圖 我們再來一張?緩存
(2) CAS面試常常問的一個是ABA 問題 什麼是ABA ? 上圖
安全
(3) 有人說ABA 不影響啊 我反正指望的值是A 你最後是A就得了唄 微信
這個還要看具體的業務,拿生活中例子來講,銀行職員小孫,偷拿了銀行100萬,ide
而後去投資賺了20萬,最後把100萬還回去。 你細品。。 銀行能容許嗎oop
(4)ABA 的解決方案 版本 version 怎麼解決 ? this
假若有一個值 int count ,2個線程 每一個線程給count加5000次 1
按道理說 每一個人給你5000 你應該有1萬塊 atom
public class CasTest { public static int count = 0; public static void main(String[] args) throws InterruptedException { new Sub("第一個").start(); new Sub("第二個").start(); TimeUnit.SECONDS.sleep(5); System.out.println("count="+CasTest.count); } } class Sub extends Thread{ private String name; public Sub(String name) { this.name = name; } public void run() { System.out.println(name+"開始+"); for (int i = 0; i < 5000; i++) { try { CasTest.count = CasTest.count+1; } catch (Exception e) { e.printStackTrace(); } } System.out.println(name+"加完了.."); } } 執行結果: 第一個開始+ 第二個開始+ 第一個加完了.. 第二個加完了.. count=6811
解決方案1 加鎖synchronized 或者 lock 均可以 spa
public class CasTest02 { public static Integer count = 0; public static void main(String[] args) throws InterruptedException { new Sub02("第一個").start(); new Sub02("第二個").start(); TimeUnit.SECONDS.sleep(5); System.out.println("count="+CasTest02.count); } } class Sub02 extends Thread{ private String name; public Sub02(String name) { this.name = name; } public void run() { System.out.println(name+"開始+"); for (int i = 0; i < 500; i++) { try { // 加鎖 synchronized (CasTest02.class) { CasTest02.count = CasTest02.count+1; } } catch (Exception e) { e.printStackTrace(); } } System.out.println(name+"加完了.."); } }
解決方案2: CAS java自帶的原子類 AtomicInteger
讀者讀到這裏能夠了解一下LongAdder
public class CasTest03 { public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { new Sub03("第一個").start(); new Sub03("第二個").start(); TimeUnit.SECONDS.sleep(5); System.out.println("count="+CasTest03.count); } } class Sub03 extends Thread{ private String name; public Sub03(String name) { this.name = name; } public void run() { System.out.println(name+"開始+"); for (int i = 0; i < 5000; i++) { // 加鎖 CasTest03.count.incrementAndGet(); } System.out.println(name+"加完了.."); } } 執行結果: 第一個開始+ 第二個開始+ 第二個加完了.. 第一個加完了.. count=10000
這裏簡單復現一個ABA問題 可能不是很精確 讀者朋友體會意思便可
public class CasTest04 { public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { new Thread(()->{ count.compareAndSet(0,1); count.compareAndSet(1,0); System.out.println("線程1 把count從0 修改成1 再從1 修改成0 "); },"線程1").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(1); // 這裏是0 可是已經不是他所但願的那個0 了 count.compareAndSet(0,4); System.out.println("線程2 把count 從0 修改成4"); } catch (Exception e) { e.printStackTrace(); } },"線程2").start(); TimeUnit.SECONDS.sleep(5); System.out.println("count="+count); } } 執行結果: 線程1 把count從0 修改成1 再從1 修改成0 線程2 把count 從0 修改成4 count=4
ABA解決 加版本
public class CasTest05 { public static AtomicStampedReference<Integer> count = new AtomicStampedReference<>(0,0); public static void main(String[] args) throws InterruptedException { new Thread(()->{ try { // 等1秒 讓線程2拿到版本 TimeUnit.SECONDS.sleep(1); boolean res = count.compareAndSet( 0, 1, count.getStamp(), count.getStamp() + 1); boolean res2 = count.compareAndSet (1, 0, count.getStamp(), count.getStamp() + 1); System.out.println("線程1 把count從0 修改成1 再從1 修改成0 " + ( res2 ? "成功!":"失敗!")); }catch (Exception r){ r.printStackTrace(); } },"線程1").start(); new Thread(()->{ try { // 版本 int stamp = count.getStamp(); TimeUnit.SECONDS.sleep(2); // 這裏是0 可是已經不是他所但願的那個0 了 版本已經變了 boolean res = count.compareAndSet(0,4,stamp,stamp+1); System.out.println("線程2 把count 從0 修改成4" + ( res ? " 成功!":" 失敗!")); } catch (Exception e) { e.printStackTrace(); } },"線程2").start(); TimeUnit.SECONDS.sleep(5); System.out.println("count="+count.getReference()); } }
/** * @author 木子的晝夜 */ public class CasTest { public static void main(String[] args) { // JUC包裏的原子類 線程安全的 AtomicInteger ai = new AtomicInteger(); // 加1 並返回 Integer res = ai.incrementAndGet(); System.out.println(res); // 上邊這句話的意思至關於 int i = 0; i = i+1; int resi = i; System.out.println(resi); } } /** * Atomically increments by one the current value. * 當前值自動加1 * @return the updated value * 返回更新了的值 */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } // Unsafe.java#getAndAddInt public final int getAndAddInt(Object obj, long offset, int step) { int E; do { var5 = this.getIntVolatile(obj, offset); // 這裏不必定能一次就成功哦 會執行屢次 // 跟一個姑娘表白 一次不成 你就灰溜溜走了 ? 活該單身.. } while(!this.compareAndSwapInt(obj, offset, E, E + step)); return var5; } // Unsafe.java#getIntVolatile 這個方法是獲 對象obj 內存開始地址 相對偏移位置offset 對應的值 // 說白了 就是獲取對象對應字段的值 public native int getIntVolatile(Object obj, long offset); // public final native boolean compareAndSwapInt( Object obj, // 被修改屬性的對象 long offset, // 被修改字段相對於當前對象內存首地址偏移量 能夠經過他直接去內存拿數據 int E, // 指望值 int X);// 須要設置的新值
// obj設置屬性的對象 offset 字段相對於類內存起始位置偏移量 e指望值 x要設置的值 UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) { // 轉換對象格式 jobject- > oop oop p = JNIHandles::resolve(obj); if (p == NULL) { // 獲取filed對應的內存地址 對象起始地址+偏移量 volatile jint* addr = (volatile jint*)index_oop_from_field_offset_long(p, offset); // 把addr內存對應的值 設置爲x 前提是內存值要等於e return RawAccess<>::atomic_cmpxchg(addr, e, x) == e; } else { // assert_field_offset_sane(p, offset); return HeapAccess<>::atomic_cmpxchg_at(p, (ptrdiff_t)offset, e, x) == e; } } UNSAFE_END // 這裏就調用了 atomic_cmpxchg: 系統方法 : 原子比較並交換計數值。 atomic_cmpxchg(void* addr, T compare_value, T new_value) { if (is_hardwired_primitive<decorators>()) { const DecoratorSet expanded_decorators = decorators | AS_RAW; return PreRuntimeDispatch::atomic_cmpxchg<expanded_decorators> (addr, compare_value, new_value); } else { return RuntimeDispatch<decorators, T, BARRIER_ATOMIC_CMPXCHG>::atomic_cmpxchg (addr, compare_value, new_value); } }
再底層就是系統級別的實現了,CPU 實現Atomic ,我也是看文章看得,說是有2中方式
下邊鏈接是我準備寫文章的目錄,若是有讀者以爲須要添加,請微信公衆直接發消息給我。
https://www.processon.com/view/link/5f57817963768959e2dc7dca
注意: 以上文字 僅表明我的觀點,僅供參考,若有問題還請指出,當即立刻連滾帶爬的從被窩裏出來改正。