JAVA线程基础二——CAS 实现(Unsafe类)

Unsafe 类中的重要方法

        JDK的rt.jar 包中的 Unsafe 类提供了硬件级别的原子性操作,Unsafe 类中的方法都是native 方法,它们使用JNI的方式访问本地C++实现库。下面我们来了解一下 Unsafe 提供的几个主要的方法以及编程时如何使用 Unsafe 类做一些事情。

  • long objectFieldOfset(Field feld)方法:返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该Unsafe函数中访问指定字段时使用。如下代码使用Unsafe 类获取变量 value 在 AtomicLong 对象中的内存偏移。
  • int arrayBaseOffset(Class arrayClass)方法:获取数组中第一个元素的地址。
  • int arrayIndexScale(Class arrayClass)方法:获取数组中一个元素占用的字节。
  • boolean compareAndSwapLong(Object obj, long offset, long expect, long update)方法:比较对象 obj 中偏移量为 offset的变量的值是否与 expect 相等,相等则使用 update值更新,然后返回 true,否则返回 false。
  • public native long getLongvolatile(Object obj, long offset)方法:获取对象 obj中偏移量为 offset 的变量对应 volatile 语义的值。
  • void putLongvolatile(Object obj, long offset, long value)方法:设置 obj对象中 ofset偏移的类型为 long 的 feld 的值为 value,支持 volatile 语义。
  • void putOrderedLong(Object obj, long offset, long value)万法:设置 obj 对象中 offset偏移地址对应的long型 feld 的值为 value。这是一个有延迟的 putLongvolatile 方法,并且不保证值修改对其他线程立刻可见。只有在变量使用volatile修饰并且预计会被意外修改时才使用该方法。
  • void park(boolean isAbsolute,long time)方法:阻塞当前线程,其中参数isAbsolute等于 false 且 time 等于0表示一直阻塞。time 大于0表示等待指定的 time 后阻塞线程会被唤醒,这个time是个相对值,是个增量值,也就是相对当前时间累加time后当前线程就会被唤醒。如果isAbsolute等于true,并且time大于0,则表示阻塞的线程到指定的时间点后会被唤醒,这里time是个绝对时间,是将某个时间点换算为ms后的值。另外,当其他线程调用了当前阻塞线程的interrupt方法而中断了当前线程时,当前线程也会返回,而当其他线程调用了unPark方法并且把当前线程作为参数时当前线程也会返回。
  • void unpark(Object thread)方法:唤醒调用 park 后阻塞的线程

下面是JDK8新增的函数,这里只列出 Long 类型操作

  • long getAndSetLong(Object obj,long ofset, long update)方法:获取对象 obj 中偏移量为 offset 的变量 volatile 语义的当前值,并设置变量 volatile 语义的值为 update。
  • long getAndAddLong(Object obj,long ofset, long addValue)方法:获取对象 obj 中偏移量为 ofset 的变量 volatile 语义的当前值,并设置变量值为原始值 +addValue。
  • 类似 getAndSetLong的实现,只是这里进行CAS操作时使用了原始值+传递的增量参数 addValue 的值。

如何使用 Unsafe 类

public class UnsafeTest {
    //获取 Unsafe的实例
//    static final Unsafe unsafe = Unsafe.getUnsafe();
    static final Unsafe unsafe;

    //记录变量state在类TestUnsafe中的偏移值
    static final long stateOffset;

    //变量
    private volatile long state = 1;

    static {
        try {
            //使用反射获取Unsafe的成员变量theUnsafe
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            //设置为可存取
            field.setAccessible(true);
            //获取该变量的值
            unsafe = (Unsafe) field.get(null);
            //获取state变量在类TestUnsafe中的偏移值
            stateOffset = unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("state"));

        } catch (Exception ex) {
            System.out.println(ex.getLocalizedMessage());
            throw new Error(ex);
        }
    }

    public static void main(String[] args) {
        UnsafeTest test = new UnsafeTest();

        boolean sucess = unsafe.compareAndSwapInt(test, stateOffset, 1, 2);
        boolean sucess2 = unsafe.compareAndSwapInt(test, stateOffset, 2, 3);
        System.out.println(sucess);
        System.out.println(sucess2);
        System.out.println(test.state);
    }

}

这里通过反射,是由于看 getUnsafe方法做了特殊处理

   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对象,这里是 UnSafeTest.class 

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

思考一下,这里为何要有这个判断?我们知道Unsafe 类是rtjar 包提供的,rt.jar 包里面的类是使用 Bootstrap类加载器加载的,而我们的启动main函数所在的类是使用AppClassLoader 加载的,所以在 main 函数里面加载 Unsafe 类时,根据委托机制,会委托给 Bootstrap 去加载 Unsafe 类。

如果没有代码(2.2.8)的限制,那么我们的应用程序就可以随意使用 Unsafe 做事情了,而 Unsafe 类可以直接操作内存,这是不安全的,所以JDK开发组特意做了这个限制,不让开发人员在正规渠道使用Unsafe 类,而是在rt.jar 包里面的核心类中使用 Unsafe 功能。

如果开发人员想要使用Unsafe,只能使用万能的反射来实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值