11-Java中CAS操作

2021/10/6 20:12:34

本文主要是介绍11-Java中CAS操作,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1. Java中CAS操作

  • 在Java中使用锁不好的地方就是当一个线程没有获取到锁时会被阻塞挂起,这会导致线程上下文重新调度与开销。Java提供了非阻塞的volatile关键字来解决共享变量的可见性问题。但是volatile只能保证共享变量的可见性,不能解决读-改-写的原子性问题。CAS即为Compare and Swap,是JDK提供的非阻塞的原子性操作。它通过硬件方式保证了比较-更新操作的原子性。

    • 经典ABA问题:假如线程1使用CAS修改初始值为A的变量X(X=A),那么线程1首先会获取当前变量X的值(A),然后使用CAS操作尝试修改X的值为B,如果使用CAS修改成功了,那么程序运行一定是正常的吗?其实未必,这是因为有可能在线程1获取到变量X的值A后,在执行CAS之前,线程2使用了CAS修改了变量X值为B,然后又使用了CAS操作使得变量X值为A,虽然线程A执行了CAS操作时X=A,但是这个A已经不是线程1获取到的A了。这就是ABA问题。ABA问题的产生是因为变量的状态值产生了环形转换,就是变量值可以从A到B,也可以B到A,如果变量的值只能朝着一个方向转换,例如A到B,B到C,不构成环路,就不会存在这个问题。JDK中的AtomicStampedReference类给每个变量的状态值都配备了一个时间戳,从而避免了ABA问题。

2. Unsafe类

2.1 Unsafe类中重要的方法

JDK的rt.jar包中的Unsafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法,他们使用JIN的方法访问本地C++实现库,下面我们介绍一下Unsafe类能做的事情:

  • long objectFieldOffset(Field field)方法:返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该Unsafe函数中访问指定字段时候使用。如下代码就是使用Unsafe类来获取变量value在AtmoicLong对象中的内存偏移。

    • static{
          try{
       valueOffset=unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
          } catch(Exception e){
              e.printStack();
          }
      }
      
  • int arrayBaseOffset(Class arrayClass)方法:获取数组中第一个元素的地址。

  • int arrayIndexScale(Class arrayClass)方法:获取数组中第一个元素占用的字节数。

  • boolean comapreAndSwapLong(Object obj,long offset,long expect,long update)方法:比较对象obj的偏移量为offset的变量值是否为expect,如果不是,则修改为update,然后返回true,否则返回false。

  • native long getLongvolatile(Object obj,long offset)方法:获取对象obj中偏移量为offset的变量对应的volatile语义的值。

  • void park()方法:阻塞当前线程。

  • void unpark()方法:唤醒调用park后阻塞的线程。

  • 。。。。。。

2.2使用Unsafe类

public class TestUnsafe {
    //获取Unsafe实例
    static final Unsafe unsafe = Unsafe.getUnsafe();
    //记录变量state在类TestUnsafe中的偏移值
    static long stateOffset = -1L;
    //变量state
    private volatile long state = 0;

    static {
        try {
            //获取state变量在TestUnsafe中的偏移值
            stateOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("state"));
        } catch (NoSuchFieldException e) {
            System.out.println(e.getLocalizedMessage());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //创建实例,并设置state为1
        TestUnsafe test = new TestUnsafe();
        Boolean success = unsafe.compareAndSwapInt(test, stateOffset, 0, 1);
        System.out.println(success);
    }
}
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.SecurityException: Unsafe
	at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
	at com.heiye.temp.TestUnsafe.<clinit>(TestUnsafe.java:7)

为了找出原因,我们需要查看getUnsafe代码

 @CallerSensitive
    public static Unsafe getUnsafe() {
        //2.2.7
        Class var0 = Reflection.getCallerClass();
        //2.2.8
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

	//2.2.9
   public static boolean isSystemDomainLoader(ClassLoader var0) {
        return var0 == null;
    }

代码(2.2.7)获取调用getUnsafe方法的Class对象,这里这个对象是TestUnsafe.class。

代码(2.2.8)判断是不是Bootsrap类加载器加载的localClass,在这里看是不是Bootsrap加载器加载了TestUnsafe.class,很明显查看是由AppClassLoader加载的,所以这里就直接抛出了异常。

为什么要进行判断呢?我们知道Unsafe类是rt.jat提供的,rt.jat包里面的类是Bootsrap类加载器加载的,而我们启动main方法的时候是AppClassLoader类加载器加载的,所以main方法在启动的时候,根据委托机制,会委托给Bootsrap加载器加载Unsafe类。如果没有代码(2.2.8)的限制,我们会可以随意的使用Unsafe类做事情了。而Unsafe类是可以直接操作内存的,是不安全的。所以JDK开发组特意做了这个限制,不让开发人员正规渠道使用Unsafe类,而是在rt.jar包的核心类中使用Unsafe类。

如果我们想用,我们可以通过反射的角度来获取Unsafe实例。

public class TestUnsafe1 {
    static Unsafe unsafe;
    static Long stateOffset;

    private volatile long state = 0;

    static {
        try {
            //使用反射来获取Unsafe成员变量theUnsafe
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            //设置为可存取
            field.setAccessible(true);
            //获取该变量的值
            unsafe = (Unsafe) field.get(null);
            //获取state在TestUnsafe1中的偏移量
            stateOffset = unsafe.objectFieldOffset(TestUnsafe1.class.getDeclaredField("state"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            System.out.println(e.getLocalizedMessage());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        TestUnsafe1 testUnsafe1 = new TestUnsafe1();
        Boolean success = unsafe.compareAndSwapInt(testUnsafe1, stateOffset, 0, 1);
        System.out.println(success);
    }
}
true


这篇关于11-Java中CAS操作的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程