CAS
CAS(compare and swap)比较并替换。 CAS有三个参数:需要读写的内存位值(V)、进行比较的预期原值(A)和拟写入的新值(B)。当且仅当V的值等于A时,CAS才会通过原子方式用新值B来更新V的值,否则不会执行任何操作。简单来说,CAS的含义是:“我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”。CAS是一项乐观的技术,它希望能成功地执行更新操作,并且如果有另一个线程在最近一次检查后更新了该变量,那么CAS能检测到这个错误。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都将失败。但是,失败的线程并不会被挂起(这就是与获取锁的机制不同之处),而是被告知在这次竞争中失败,并可以多次尝试。这种灵活性就大大减少了与锁相关的活跃性风险。
在针对多处理器操作而设计的处理器中提供了一些特殊指令,用于管理共享数据的并发访问。现在,几乎所有的现代处理器中都包含了某种形式的原子读-改-写指令,例如比较并交换(Compare-and-Swap)或者关联加载/条件存储(Load-Linked/Store-Conditional)。操作系统和JVM可以使用这些指令来实现锁和并发的数据结构。
ABA问题
如果有两个线程x和y,如果x初次从内存中读取变量值为A;线程y对它进行了一些操作使其变成B,然后再改回A,那么线程x进行CAS的时候就会误认为这个值没有被修改过。尽管CAS操作会成功执行,但是不代表它是没有问题的,如果有一个单向链表A B组成的栈,栈顶为A,线程T1准备执行CAS操作head.compareAndSet(A,B)
,在执行之前线程T2介入,T2将A、B出栈,然后又把C、A放入栈,T2执行完毕;切回线程T1,T1发现栈顶元素依然为A,也会成功执行CAS将栈顶元素修改为B,但因为B.next为null,所以栈结构就会丢弃C元素。
针对这种情况,有一种简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个和版本号,即这个值由A变为B,然后又变成A,版本号也将是不同的。Java中提供了AtomicStampedReference
和AtomicMarkableReference
来解决ABA问题。他们支持在两个变量上执行原子的条件更新。AtomicStampedReference
将更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题。 类似地,AtomicMarkableReference
将更新一个“对象引用-布尔值”二元组,在某些算法中将通过这种二元组使节点保存在链表中同时又将其标记为“已删除的节点”。不过目前来说,这两个类比较鸡肋,大部分情况下的ABA问题不会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。
Unsafe
Unsafe是实现CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。Unsafe类提供了硬件级别的原子操作。
/*peek and poke 指令*/
/*获取对象o中给定偏移地址(offset)的值*/
public native int getInt(Object o, long offset);
public native Object getObject(Object o, long offset);
/*获取对象o的给定偏移地址的引用值(volatile方式)*/
public native Object getObjectVolatile(Object o, long offset);
/*在对象o的给定偏移地址存储数值x。以下set方法作用相同*/
public native void putInt(Object o, long offset, int x);
public native void putObject(Object o, long offset, Object x);
public native void putObjectVolatile(Object o, long offset, Object x);
/*从给定内存地址获取一个byte。*/
public native byte getByte(long address);
/*在给定内存地址放置一个x。*/
public native void putByte(long address, byte x);
/*获取给定内存地址的一个本地指针*/
public native long getAddress(long address);
/*在给定的内存地址处存放一个本地指针x*/
public native void putAddress(long address, long x);
/*内存操作*/
/*在本地内存分配一块指定大小的新内存,内存的内容未初始化;它们通常被当做垃圾回收。*/
public native long allocateMemory(long bytes);
/*重新分配给定内存地址的本地内存*/
public native long reallocateMemory(long address, long bytes);
/*将给定内存块中的所有字节设置为固定值(通常是0)*/
public native void setMemory(Object o, long offset, long bytes, byte value);
/*复制一块内存,double-register模型*/
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset, long bytes);
/*复制一块内存,single-register模型*/
public void copyMemory(long srcAddress, long destAddress, long bytes) {copyMemory(null, srcAddress, null, destAddress, bytes);}
/*释放给定地址的内存*/
public native void freeMemory(long address);
/*获取给定对象的偏移地址*/
public native long staticFieldOffset(Field f);
public native long objectFieldOffset(Field f);
/*数组操作*/
/*获取给定数组的第一个元素的偏移地址*/
public native int arrayBaseOffset(Class<?> arrayClass);
/*获取给定数组的元素增量地址,也就是说每个元素的占位数*/
public native int arrayIndexScale(Class<?> arrayClass);
/*告诉虚拟机去定义一个类。默认情况下,类加载器和保护域都来自这个方法*/
public native Class<?> defineClass(String name, byte[] b, int off, int len,ClassLoader loader, ProtectionDomain protectionDomain);
/*定义匿名内部类*/
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
/*定位一个实例,但不运行构造函数*/
public native Object allocateInstance(Class<?> cls) throws InstantiationException;
/*锁指令(synchronized)*/
/*对象加锁*/
public native void monitorEnter(Object o);
/*对象解锁*/
public native void monitorExit(Object o);
public native boolean tryMonitorEnter(Object o);
/*解除给定线程的阻塞*/
public native void unpark(Object thread);
/*阻塞当前线程*/
public native void park(boolean isAbsolute, long time);
/*CAS*/
public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);
/*用于lazySet,适用于低延迟代码。*/
public native void putOrderedObject(Object o, long offset, Object x);
/*获取并加上给定delta,返回加之前的值*/
public final int getAndAddInt(Object o, long offset, int delta);
/*为给定偏移地址设置一个新的值,返回设置之前的值*/
public final int getAndSetInt(Object o, long offset, int newValue);
/*1.8新增指令*/
/*loadFence() 表示该方法之前的所有load操作在内存屏障之前完成*/
public native void loadFence();
/*表示该方法之前的所有store操作在内存屏障之前完成*/
public native void storeFence();
/*表示该方法之前的所有load、store操作在内存屏障之前完成,这个相当于上面两个的合体功能*/
public native void fullFence();
putOrderedObject
:putOrderedObject
是 lazySet 的实现,适用于低延迟代码。它能够实现非堵塞写入,避免指令重排序,这样它使用快速的存储-存储(store-store) barrier,而不是较慢的存储-加载(store-load) barrier, 后者总是用在volatile
的写操作上。这种性能提升是有代价的,也就是写后结果并不会被其他线程看到,甚至是自己的线程,通常是几纳秒后被其他线程看到。类似的方法还有putOrderedInt
、putOrderedLong
。
网友评论