Java併發學習筆記10 CAS

bilibili-Java併發學習筆記10 CAS

基於 java 1.8.0

P39_CAS詳解及透過字節碼分析變量操作的原子性

  • 悲觀鎖
    • synchronized 關鍵字與 Lock 等鎖機制都是悲觀鎖:無論做任何操作,首先都需要先上鎖,接下來再去執行後續操作,從而確保了接下來的所有操作都是由當前這個線程來執行的。
  • 樂觀鎖
    • 線程在操作之前不會做任何預先的處理,而是直接去執行;當在最後執行變量更新的時候,當前線程需要有一種機制來確保當前被操作的變量是沒有被其他線程修改的;CAS樂觀鎖的一種非常重要的實現方式
    • CAS (Compare And Swap) 比較與交換 : 這是一個不斷循環的過程,一直到變量值被修改成功爲止。CAS 本身是由硬件指令來提供支持的,換句話說,硬件是通過一個原子指令來實現比較與交換的;因此,CAS 可以確保變量操作的原子性的。
      • hotspot 源碼實現中調用 cmpxchg 函數(hotspot/src/share/vm/prims/unsafe.cpp)
      • 其調用了彙編指令 cmpxchg (hotspot/src/share/vm/runtime/Atomic.cpp)
      • 在IA64,x86 指令集中有 cmpxchg 指令完成 CAS 功能,在 sparc-TSO 也有 casa 指令實現,而在 ARM 和 PowerPC 架構下,則需要使用一對 ldrex/strex 指令來完成 LL/SC 的功能。在精簡指令集的體系架構中,則通常是靠一對兒指令,如:load and reserve 和 store conditional 實現的,在大多數處理器上 CAS 都是個非常輕量級的操作。(https://juejin.im/post/5de5f8575188253232282e8e)
    • Atomic 原子類其底層基於 CAS 樂觀鎖來實現
    • LongAdder 其底層也是基於 CAS 機制實現的

package new_package.thread.p39;

public class CasTest1 {

    private int count;

    public int getCount() {
        return count;
    }

    public void increaseCount() {
        // java 代碼的一句
        ++this.count;
    }
}

javap -v xxx

// 字節碼四行指令
0: aload_0
1: dup
2: getfield      #2                  // Field count:I  獲取值
5: iconst_1                                            1
6: iadd                                                計算 +1
7: putfield      #2                  // Field count:I  賦新值
10: return

讀取 -> 修改 -> 寫入 : 不是原子操作


加鎖方式改進:

package new_package.thread.p39;

public class CasTest1 {


    private int count;

    public synchronized int getCount() {
        return count;
    }

    public synchronized void increaseCount() {
        ++this.count;
    }
}

錯誤改進:

package new_package.thread.p39;

public class CasTest1 {

    // volatile 不保證原子性
    // 只保證可見性和防止指令重排序
    private volatile int count;

    public int getCount() {
        return count;
    }

    public synchronized void increaseCount() {
        ++this.count;
    }
}

正確改進:

package new_package.thread.p39;

import java.util.concurrent.atomic.AtomicInteger;

public class CasTest2 {

    private AtomicInteger count = new AtomicInteger(0);

    public int getCount() {
        return count.get();
    }

    public void increaseCount() {
        count.incrementAndGet();
    }
}
package new_package.thread.p39;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.IntStream;

public class CasTest3 {

    private LongAdder count = new LongAdder();

    public int getCount() {
        return count.intValue();
    }

    public void increaseCount() {
        count.increment();
    }

    public static void main(String[] args) throws InterruptedException {
        CasTest3 casTest3 = new CasTest3();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        IntStream.range(0, 10).forEach(n -> new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                casTest3.increaseCount();
            }
            countDownLatch.countDown();
        }).start());
        countDownLatch.await();
        System.out.println(casTest3.getCount());
    }
}

P40_CAS底層實現與源碼剖析

對於 CAS 來說,其操作步驟主要涉及到三步:

    1. 需要被操作的內存值 V
    1. 需要進行比較的值 A
    1. 需要進行寫入的值 B

只有當 V==A 時,CAS 纔會通過原子操作的手段來將 V 的值更新爲 B ;


CAS 存在的問題:

    1. 循環開銷問題:併發量大的情況下會導致線程一直自旋;
    1. 只能保證一個變量的原子操作:可以通過 AtomicReference 來實現多個變量的原子操作;
    1. ABA 問題:加版本號;

package java.util.concurrent.atomic;
import java.util.function.IntUnaryOperator;
import java.util.function.IntBinaryOperator;
import sun.misc.Unsafe;

/**
 * @since 1.5
 * @author Doug Lea
*/
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    public AtomicInteger() {
    }

    public final int get() {
        return value;
    }

   public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    // ...
}
package sun.misc;
import java.security.*;
import java.lang.reflect.*;

import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;


/**
 * A collection of methods for performing low-level, unsafe operations.
 * Although the class and all methods are public, use of this class is
 * limited because only trusted code can obtain instances of it.
 *
 * @author John R. Rose
 * @see #getUnsafe
 */
public final class Unsafe {

    private static native void registerNatives();
    static {
        registerNatives();
        sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
    }

    private Unsafe() {}

    private static final Unsafe theUnsafe = new Unsafe();

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

    public final int getAndSetInt(Object o, long offset, int newValue) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, newValue));
        return v;
    }

    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

    // ...

}
// hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

// hotspot/src/share/vm/runtime/atomic.cpp
jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
  assert(sizeof(jbyte) == 1, "assumption.");
  uintptr_t dest_addr = (uintptr_t)dest;
  uintptr_t offset = dest_addr % sizeof(jint);
  volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
  jint cur = *dest_int;
  jbyte* cur_as_bytes = (jbyte*)(&cur);
  jint new_val = cur;
  jbyte* new_val_as_bytes = (jbyte*)(&new_val);
  new_val_as_bytes[offset] = exchange_value;
  while (cur_as_bytes[offset] == compare_value) {
    jint res = cmpxchg(new_val, dest_int, cur);
    if (res == cur) break;
    cur = res;
    new_val = cur;
    new_val_as_bytes[offset] = exchange_value;
  }
  return cur_as_bytes[offset];
}

package new_package.thread.p39;

import sun.misc.Unsafe;

import java.util.concurrent.atomic.AtomicInteger;

public class CasTest4 {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) {
            throw new Error(ex);
        }
    }

    private volatile int value;

    public static void main(String[] args) {
        System.out.println("hello");
    }
}
/**
 * Exception in thread "main" java.lang.ExceptionInInitializerError
 * Caused by: java.lang.SecurityException: Unsafe
 * at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
 * at new_package.thread.p39.CasTest4.<clinit>(CasTest4.java:13)
 */

可以通過 Java 反射來獲取 unsafe 實例;

package new_package.thread.p39;

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.atomic.AtomicInteger;

public class CasTest5 {
    private static final Unsafe unsafe;
    private volatile int value;
    private static final long valueOffset;

    static {
        try {
            final PrivilegedExceptionAction<Unsafe> action = new PrivilegedExceptionAction<Unsafe>() {
                @Override
                public Unsafe run() throws Exception {
                    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                    theUnsafe.setAccessible(true);
                    return (Unsafe) theUnsafe.get(null);
                }
            };
            unsafe = AccessController.doPrivileged(action);
        } catch (Exception e) {
            throw new RuntimeException("Unable to load unsafe", e);
        }

        try {
            valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) {
            throw new Error(ex);
        }
    }

    public static void main(String[] args) {
        System.out.println("hello");
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章