Java Review - 并发编程_Unsafe
Posted 小小工匠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Review - 并发编程_Unsafe相关的知识,希望对你有一定的参考价值。
文章目录
- 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类
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类
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类加载器加载的
public static boolean isSystemDomainLoader(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的实例, 运行后输出结果如下。
以上是关于Java Review - 并发编程_Unsafe的主要内容,如果未能解决你的问题,请参考以下文章
Java Review - 并发编程_LockSupport