此文为读书笔记,欢迎评论,谈论问题,共同进步!
原子性操作,是指执行一系列操作时,这些操作要么全部执行,要么全部不执行,不存在只执行其中一部分的情况。在设计计数器时一般都先读取当前值,然后+1,再更新。这个过程是读—改一写的过程,如果不能保证这个过程是原子性的,那么就会出现线程安全问题。
Java 中的 CAS操作
CAS(Compare and Swap),其是 JDK提供的非阻塞原子性操作,它通过硬件保证了比较一更新操作的原子性。JDK里面的Unsafe类提供了一系列的 compareAndSwap*方法,下面以compareAndSwapLong方法为例进行简单介绍。
- boolean compareAndSwapLong(Object obj, long valueOffset, long expect, long update)方法∶
其中compareAndSwap的意思是比较并交换。
CAS有四个操作数,分别为∶对象内存位置、对象中的变量的偏移量、变量预期值和新的值。
其操作含义是,如果对象 obj 中内存偏移量为valueOffset 的变量值为expect,则使用新的值update替换旧的值 expect。这是处理器提供的一个原子性指令。
关于CAS 操作有个经典的ABA问题:
假如线程1使用 CAS 修改初始值为A的变量X,那么线程1会首先去获取当前变量X的值(为A),然后使用CAS操作尝试修改X的值为B,如果使用CAS操作成功了,那么程序运行一定是正确的吗?其实未必,这是因为有可能在线程1获取变量X的值A后,在执行CAS前,线程2使用CAS修改了变量X的值为B,然后又使用CAS修改了变量X的值为A。所以虽然线程1执行CAS时X的值是A,但是这个A已经不是线程1获取时的A了。这就是ABA问题。
ABA 问题的产生是因为变量的状态值产生了环形转换,就是变量的值可以从A到B,然后再从B到A。如果变量的值只能朝着一个方向转换,比如A到B,B到C,不构成环形,就不会存在问题。
JDK 中的AtomicStampedReference类给每个变量的状态值都配备了一个时间戳,从而避免了 ABA 问题的产生。
Unsafe 类
Unsafe 类中的重要方法
JDK 的rt.jar包中的 Unsafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是 native方法,它们使用JNI 的方式访问本地 C++ 实现库。
- long objectFieldOffset(Field field)方法∶
返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该 Unsafe 函数中访问指定字段时使用。
如下代码使用 Unsafe类获取变量 value 在 AtomicLong 对象中的内存偏移。
static {
try {
valueOffset = unsafe.objectFieldoffset(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
- int arrayBaseOffset(Class arrayClass)方法∶
获取数组中第一个元素的地址。 - int arrayIndexScale(Class arrayClass)方法∶
获取数组中一个元素占用的字节。 - boolean compareAndSwapLong(Object obj,long offset,long expect,long update)方法∶
比较对象 obj中偏移量为ofset的变量的值是否与expect相等,相等则使用update值更新,然后返回 true,否则返回 false。 - public native long getLongvolatile(Object obj,long offset)方法∶
获取对象 obj中偏移量为 offst 的变量对应 volatile 语义的值。 - void putLongvolatile(Object obj long ofset,long value)方法∶
设置obj对象中offet偏移的类型为 long 的 field 的值为value,支持 volatile 语义。 - void putOrderedLong(Object obj,long offst, 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。
public final long getAndsetLong(Object obj,long offset,long update) {
long l;
do {
l = getLongvolatile (obj, offset);// (1)
} while (!compareAndSwapLong(obj,offset,l,update));
return l;
}
上面代码: (1)处的 getLongvolatile 获取当前变量的值,然后使用 CAS 原子操作设置新值。这里使用 while循环是考虑到,在多个线程同时调用的情况下 CAS失败时需要重试。
- long getAndAddLong(Object obj,long offset, long addValue)方法∶获取对象 obj 中偏移量为 offset 的变量 volatile 语义的当前值,并设置变量值为原始值 +addValue。
public final long getAndAddLong(Object obj,long offset,long addValue) {
long l;
do {
l = getLongvolatile (obj,offset);
while(!compareAndSwapLong(obj,offset,l,l + addValue));
return l;
}
类似getAndSetLong 的实现,只是这里进行CAS操作时使用了原始值+传递的增量参数 addValue 的值。
如何使用 Unsafe 类
先简单测试下:
public class UnSafeTest {
/** unsafe 实例 */
static final Unsafe unsafe = Unsafe.getUnsafe();
/** 记录变量state 在类 UnSafeTest 中的偏移量 */
static long stateOffset;
/** 变量state */
private volatile long state = 0;
static {
try {
// 获取state变量 在类 UnSafeTest 中的偏移量
stateOffset = unsafe.objectFieldOffset(UnSafeTest.class.getDeclaredField("state"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 创建实例,设置state 为 1
UnSafeTest test = new UnSafeTest();
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 base.thread.book.UnSafeTest.<clinit>(UnSafeTest.java:12)
发现getUnsafe 实例报错了, 查看源码:
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
!VM.isSystemDomainLoader(var0.getClassLoader())
这个判断直接抛出的异常, 这是为什么?
因为Unsafe类可以直接操作内存, 是不安全的, 所以JDK开发人员特意做了这个限制, 不让技术人员在正规渠道中使用Unsafe类, 这个类是rt.jar中提供的, 这个jar包是使用Bootstrap 类加载器加载的, 而在启动main所在的类是使用AppClassLoader加载的, 所以会报错.
那怎么可以尝试用一下Unsafe类的 ?
可以使用反射来获取Unsafe实例:
public class UnSafeTest2 {
/** unsafe 实例 */
static Unsafe unsafe;
/** 记录变量state 在类 UnSafeTest 中的偏移量 */
static long stateOffset;
/** 变量state */
private volatile long state = 0;
static {
try {
// 使用反射获取Unsafe的成员变量theUnsafe
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置为可存取
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
// 获取state变量 在类 UnSafeTest 中的偏移量
stateOffset = unsafe.objectFieldOffset(UnSafeTest2.class.getDeclaredField("state"));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 创建实例,设置state 为 1
UnSafeTest2 test = new UnSafeTest2();
boolean success = unsafe.compareAndSwapInt(test, stateOffset, 0, 1);
System.out.println(success); // true
}
}