zl程序教程

您现在的位置是:首页 >  Java

当前栏目

Java Review - 并发编程_Unsafe

2023-03-20 14:53:08 时间

文章目录

Unsafe

Unsafe 提供的几个主要的方法

long objectFieldOffset(Field field)

int arrayBaseOffset(Class arrayClass)

int arrayIndexScale(Class arrayClass)

boolean compareAndSwapLong(Object obj, long offset, long expect, long update)

public native long getLongvolatile(Object obj, long offset)

void putLongvolatile(Object obj, long offset, long value)

void putOrderedLong(Object obj, long offset, long value)

void park(boolean isAbsolute, long time)

void unpark(Object thread)

long getAndSetLong(Object obj, long offset, long update)

long getAndAddLong(Object obj, long offset, long addValue)

使用Unsafe类

版权声明:本文为CSDN博主「小小工匠」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/yangshangwei/article/details/121578156

Unsafe

JDK 的 rt.jar 包中的 Unsafe 类提供了硬件级别的原子性操作,Unsafe 类中的方法都是native 方法,它们使用 JNI 的方式访问本地 C++ 实现库。

Unsafe 提供的几个主要的方法

下面我们来了解一下 Unsafe 提供的几个主要的方法以及编程时如何使用 Unsafe 类做一些事情。

long objectFieldOffset(Field field)

返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该 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对象中offset偏移的类型为long的field的值为value,支持volatile语义

void putOrderedLong(Object obj, long offset, long value)

设置obj对象中offset偏移地址对应的long型field的值为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 offset, long update)

获取对象obj中偏移量为offset的变量volatile语义的当前值,并设置变量volatile语义的值为update

由以上代码可知,首先(1)处的getLongvolatile获取当前变量的值,然后使用CAS原子操作设置新值。这里使用while循环是考虑到,在多个线程同时调用的情况下CAS失败时需要重试。

long getAndAddLong(Object obj, long offset, long addValue)

获取对象obj中偏移量为offset的变量volatile语义的当前值,并设置变量值为原始值+addValue

类似getAndSetLong的实现,只是这里进行CAS操作时使用了原始值+传递的增量参数addValue的值。

使用Unsafe类


import sun.misc.Unsafe;

/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2021/11/28 20:05
 * @mark: show me the code , change the world
 */
public class TestUnSafe {

    // 1 获取 Unsafe的实例
    static final Unsafe unsafe = Unsafe.getUnsafe();

    // 2 记录变量 state在类 Testunsafe中的偏移值
    static final long stateoffset;

    // 3 变量
    private volatile long state = 0;

    static {
        try {
            // 4 获取state变量在类TestUnsafe中的偏移量
            stateoffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
        } catch (Exception e) {
            System.out.println(e.getLocalizedMessage());
            throw new Error(e);
        }
    }


    public static void main(String[] args) {
        // 5. 创建实例
        TestUnSafe testUnSafe = new TestUnSafe();

        // 6 设置state值为1 通过cas算法
        boolean b = unsafe.compareAndSwapLong(testUnSafe, stateoffset, 0, 1);
        System.out.println(b);

    }

}
  • 代码1 获取了Unsafe的一个实例
  • 代码3创建了一个变量state并初始化为0。
  • 代码4使用unsafe.objectFieldOffset获取TestUnSafe类里面的state变量,在TestUnSafe对象里面的内存偏移量地址并将其保存到stateOffset变量中。
  • 代码6 调用创建的unsafe实例的compareAndSwapInt方法,设置test对象的state变量的值。具体意思是,如果test对象中内存偏移量为stateOffset的state变量的值为0,则更新该值为1。

运行上面的代码,我们期望输出true,然而执行后会输出如下结果

看看getUnsafe的代码吧

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

继续看下

//  9 判断是不是BootStrap类加载器加载的 
publicstaticbooleanisSystemDomainLoader(ClassLoader var0){
return var0 ==null;
}
  • 代码7获取调用getUnsafe这个方法的对象的Class对象,这里是TestUnSafe.class。
  • 代码8判断是不是Bootstrap类加载器加载的localClass,在这里是看是不是Bootstrap加载器加载了TestUnSafe.class。很明显由于TestUnSafe.class是使用AppClassLoader加载的,所以这里直接抛出了异常。

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

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

如果开发人员真的想要实例化Unsafe类,那该如何做?

方法有多种,既然从正规渠道访问不了,那么就玩点黑科技,使用万能的反射来获取Unsafe实例方法。


import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2021/11/28 20:05
 * @mark: show me the code , change the world
 */
public class TestUnSafe {

    // 1 获取 Unsafe的实例
    static final Unsafe unsafe  ;

    // 2 记录变量 state在类 Testunsafe中的偏移值
    static final long stateoffset;


    // 3 变量
    private volatile long state = 0;

    public long getState() {
        return state;
    }


    static {
        try {
           // 使用反射获取Unsafe成员变量theUnsafe
            Field field =  Unsafe.class.getDeclaredField("theUnsafe");

            // 设置为可存取
            field.setAccessible(true);

            // 获取该变量的值
            unsafe  = (Unsafe)field.get(null);

            // 获取state在TestUnSfate中的偏移量
            stateoffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));

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


    public static void main(String[] args) {
        // 5. 创建实例
        TestUnSafe testUnSafe = new TestUnSafe();
        System.out.println("修改前:" + testUnSafe.getState());

        // 6 设置state值为1 通过cas算法
        boolean b = unsafe.compareAndSwapLong(testUnSafe, stateoffset, 0, 1);
        System.out.println("修改  " + b);

        System.out.println("修改前:" + testUnSafe.getState());

    }

}

在如上代码中,通过反射获取unsafe的实例, 运行后输出结果如下。