Unsafe 学习和源码阅读

在代码中获取 Unsafe 对象的方法:

// 在 AtomicInteger 里面是这么用的private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获取内存的偏移地址,有点奇怪啊,为啥是获取这个类的,而不是这个对象的呢?static {    try {        valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));    } catch (Exception ex) {         throw new Error(ex);     }}

1.8 之前 Unsafe 的包路径为:

package sun.misc;

而到了 Java 9,它的包路径改成了下面这个,说明这个类已经开放使用。

package jdk.internal.misc;

获取 Unsafe 对象,这个是 openjdk 里的方法,通过反射获得。

static {    Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");}

1)内存管理

可以分配内存了哦

public native long allocateMemory(long var1);

public native long reallocateMemory(long var1, long var3);

public native void setMemory(Object var1, long var2, long var4, byte var6);

2)CAS 乐观锁

如果变量没有被其他线程改变的话,会一直尝试去修改变量,直到变成修改值。

CAS,Compare and Swap 即比较并交换,设计并发算法时常用到的一种技术,java.util.concurrent 包全完建立在 CAS 之上,没有 CAS 也就没有此包,可见 CAS 的重要性。

当前的处理器基本都支持 CAS,只不过不同的厂家的实现不一样罢了。CAS 有三个操作数:内存值 V、旧的预期值 A、要修改的值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做并返回 false。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

这里的参数是,对象,偏移量,期望值,修改值。

每次都是先去获取当前的值,然后再进行修改,所以如果有机会的话是一定会成功的。

获取偏移量的方法:

valueOffset = unsafe.objectFieldOffset(AtomicBoolean.class.getDeclaredField("value"));

还有:

public native long staticFieldOffset(Field var1);

public native long objectFieldOffset(Field var1);

public native Object staticFieldBase(Field var1);

3)supportlock

public native void unpark(Object var1);

public native void park(boolean var1, long var2);

park类似于wait,unpark类似于notify。

4)cas 源码分析

具体见 unsafe.cpp (版本:openjdk 9 )

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {    oop p = JNIHandles::resolve(obj);    //获取对象的变量的地址    jint* addr = (jint *)index_oop_from_field_offset_long(p, offset);    //调用Atomic操作    return (jint)(Atomic::cmpxchg(x, addr, e)) == e;} UNSAFE_END

进入 atomic.hpp,大意就是先去获取一次结果,如果结果和现在不同,就直接返回,因为有其他人修改了;否则会一直尝试去修改。直到成功。

inline jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value, cmpxchg_memory_order order) {    STATIC_ASSERT(sizeof(jbyte) == 1);    volatile jint* dest_int =    static_cast<volatile jint*>(align_ptr_down(dest, sizeof(jint)));    size_t offset = pointer_delta(dest, dest_int, 1);    jint cur = *dest_int;    jbyte* cur_as_bytes = reinterpret_cast<jbyte*>(&cur);    // current value may not be what we are looking for, so force it    // to that value so the initial cmpxchg will fail if it is different    cur_as_bytes[offset] = compare_value;    // always execute a real cmpxchg so that we get the required memory    // barriers even on initial failure    do {        // value to swap in matches current value ...        jint new_value = cur;        // ... except for the one jbyte we want to update        reinterpret_cast<jbyte*>(&new_value)[offset] = exchange_value;        jint res = cmpxchg(new_value, dest_int, cur, order);        if (res == cur) break; // success        // at least one jbyte in the jint changed value, so update        // our view of the current jint        cur = res;        // if our jbyte is still as cur we loop and try again    } while (cur_as_bytes[offset] == compare_value);    return cur_as_bytes[offset];}

5)CAS的缺点

CAS 看起来很美,但这种操作显然无法涵盖并发下的所有场景,并且 CAS 从语义上来说也不是完美的,存在这样一个逻辑漏洞:如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,那我们就能说明它的值没有被其他线程修改过了吗?如果在这段期间它的值曾经被改成了 B,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过。这个漏洞称为 CAS 操作的 "ABA" 问题。java.util.concurrent 包为了解决这个问题,提供了一个带有标记的原子引用类 "AtomicStampedReference",它可以通过控制变量值的版本来保证 CAS 的正确性。不过目前来说这个类比较 "鸡肋",大部分情况下 ABA 问题并不会影响程序并发的正确性,如果需要解决 ABA 问题,使用传统的互斥同步可能回避原子类更加高效。

上一篇:首次公开!阿里云开源PolarDB总体架构和企业级特性


下一篇:select2插件用法