CAS与sun.misc.Unsafe

什么是Compare And Swap(CAS)?

        顾名思义,简单说就是比较并交换。CAS操作一般涉及三个操作数:内存值,预期原值,新值。如果内存值与预期原值相同,则将会用新值替换内存值,返回更新成功,否则,什么也不处理,返回更新失败。java.util.concurrent包的底层即是依靠CAS操作来实现,CAS在java中的具体实现是sun.misc.Unsafe类,作为java.util.concurrent的实现基石,学习sun.misc.Unsafe类的方法特性就会显得十分重要。

sun.misc.Unsafe

Unsafe实例

        在使用Unsafe之前,我们需要创建Unsafe实例,然而该类是单例模式,且虽然该类提供了静态方法Unsafe.getUnsafe()来获取实例,但该方法只对系统加载器信任,如果程序发现加载器为应用级加载器,则抛出安全异常,也就意味着我们无法直接通过Unsafe.getUnsafe()来获取实例。

public static Unsafe getUnsafe() {
       Class cc = sun.reflect.Reflection.getCallerClass(2);
       if (cc.getClassLoader() != null)
           throw new SecurityException("Unsafe");
           return theUnsafe;
}

        我们可以令我们的代码“受信任”。运行程序时,使用bootclasspath 选项,指定系统类路径加上你使用的一个Unsafe路径。

java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. 包名+类名

        但这太难了,我们可以通过反射来获取:

java.lang.reflect.Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
sun.misc.Unsafe  UNSAFE = (sun.misc.Unsafe) theUnsafe.get(null);

Unsafe API:

        sun.misc.Unsafe中有众多方法,但对实体的操作有几组重要方法如下,标红的方法需要重点掌握,在concurrent源码中使用频率较高:

Info.仅返回一些低级的内存信息

        addressSize
        pageSize

Objects.提供用于操作对象及其字段的方法

        allocateInstance

        public native long objectFieldOffset(Field f);//返回对象变量f在所在类中的偏移量

        public native Object getObject(Object o, long offset);//返回对象o中偏移量为offset的对象变量(八大基本类型都类似,仅名称参数不同)

        public native void putObject(Object o, long offset, Object x);//将对象x的引用赋给对象o中偏移offset的对象变量(八大基本类型都类似,仅名称参数不同)

        public native Object getObjectVolatile(Object o, long offset);//返回对象o中偏移量为offset的对象变量,支持volatile load语义(八大基本类型都类似,仅名称参数不同)

        public native void putObjectVolatile(Object o, long offset, Object x);//将对象x的引用赋给对象o中偏移offset的对象变量,支持volatile store语义(八大基本类型都类似,仅名称参数不同)

        public native void park(boolean isAbsolute, long time);//如果isAbsolute为true,则一直阻塞当前线程,如果为false,则在阻塞time后恢复当前线程。阻塞会持续到unpark出现、线程被中断或者timeout周期到,如果unpark在park之前出现,则这里只计数

        public native void unpark(Object thread);//将park中的thread唤醒

       park和unpark调用顺序没关系,但unpark只会生效一次,例如,先连续调用2次unpark,则调一次park后不会阻塞,但调第二次park时会阻塞,需要调用用一次unpark解除阻塞状态,详细参考:http://blog.csdn.net/hengyunabc/article/details/28126139

Classes.提供用于操作类及其静态字段的方法

        public native long staticFieldOffset(Field f);//返回一个静态变量在类中的偏移量

        defineClass

        defineAnonymousClass

        public native void ensureClassInitialized(Class c);//保证该类被加载

Arrays.操作数组

        public native int arrayBaseOffset(Class arrayClass);//返回arrayClass类型数组的第一个元素偏移位置

        public native int arrayIndexScale(Class arrayClass);//返回arrayClass类型数组每个元素占几位,一般类为4个字节(引用类型为32位)

Synchronization.低级的同步原语

        monitorEnter

        tryMonitorEnter

        monitorExit

        public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);//对象o偏移量offset处,如果expected引用和内存中引用相同,则用x替代原引用,返回true,如果不同返回false(int long 类似,仅名称参数不同)

        public native void putOrderedObject(Object o, long offset, Object x);//设置obj对象中offset偏移地址对应的整型field的值为指定值。这是一个有序或者有延迟的putIntVolatile方法,并且不保证值的改变被其他线程立即看到。只有在field被volatile修饰并且期望被意外修改的时候使用才有用。 (int long 类似,类型不同而已)

Memory.直接内存访问方法

        public native long allocateMemory(long bytes);//分配bytes大小内存,返回内存地址address

        public native long reallocateMemory(long address, long bytes);//将address内存重分配,大小为bytes

        public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset, long bytes);

        public native void setMemory(Object o, long offset, long bytes, byte value);

        public native void freeMemory(long address);

        public native long getAddress(long address);//从给定的内存地址获取指针

        public native void putAddress(long address, long x);//将地址x设置到给定内存地址

        public native byte getByte(long address);//从给定的内存读取byte值 (基本类型类似)

        public native void putByte(long address, byte x);//将x值设置到给定的内存(基本类型类似)

CAS缺点

        CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作

        1.ABA问题。 因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查 时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么 A-B-A 就会变成1A-2B-3A。

        从Java1.5 开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用 是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
        参考文档: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html

        2.循环时间长开销大。 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两 个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延 迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

        3.只能保证一个共享变量的原子操作。 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可 以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操 作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

JAVA CAS操作与volatile的内存模型关系

        由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

  1. A线程写volatile变量,随后B线程读这个volatile变量。
  2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
  3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
  4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

        Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改 -写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包 得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

        首先,声明共享变量为volatile;
        然后,使用CAS的原子条件更新来实现线程之间的同步;
        同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
        AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使 用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
图片来源于他人博客

sun.misc.Unsafe源码地址:http://www.docjar.com/docs/api/sun/misc/Unsafe.html

参考:http://ifeve.com/sun-misc-unsafe/
           http://blog.csdn.net/alex19881006/article/details/24646365
           http://www.infoq.com/cn/articles/java-memory-model-5

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值