ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

11-Java中CAS操作

2021-10-06 18:03:13  阅读:152  来源: 互联网

标签:11 Java 变量 CAS Unsafe static TestUnsafe 加载


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,Unsafe,static,TestUnsafe,加载
来源: https://www.cnblogs.com/xiaomitu/p/15371828.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有