前言
Unsafe是位于sun.misc包下的一个类。Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。由于并发相关的源码很多用到了CAS,比如java.util.concurrent.atomic相关类、AQS、CurrentHashMap等相关类。所以本文主要讲Unsafe中CAS的实现。笔者源码环境为 OpenJDK8。
 CAS相关
主要相关源码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | 
 
 
 
 
 
 
 public final native boolean compareAndSwapObject(Object o, long offset,
 Object expected,
 Object x);
 
 public final native boolean compareAndSwapInt(Object o, long offset,
 int expected,
 int x);
 
 public final native boolean compareAndSwapLong(Object o, long offset,
 long expected,
 long x);
 
 | 
CAS是实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS是一条CPU的 原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
说明:对象的基地址baseAddress+valueOffset得到value的内存地址valueAddress
 Unsafe类获取
首先看下Unsafe的单例实现
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | private static final Unsafe theUnsafe = new Unsafe();
 @CallerSensitive
 public static Unsafe getUnsafe() {
 Class<?> caller = Reflection.getCallerClass();
 
 if (!VM.isSystemDomainLoader(caller.getClassLoader()))
 throw new SecurityException("Unsafe");
 return theUnsafe;
 }
 
 | 
那如若想使用这个类,该如何获取其实例?有如下两个可行方案。
其一,从 getUnsafe 方法的使用限制条件出发,通过Java命令行命令 -Xbootclasspath/a 把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过 Unsafe.getUnsafe 方法安全的获取Unsafe实例。
| 1
 | java -Xbootclasspath/a: ${path}   
 | 
其二,通过反射获取单例对象theUnsafe。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | @Slf4jpublic class UnsafeTest {
 
 private static Unsafe reflectGetUnsafe() {
 try {
 Field field = Unsafe.class.getDeclaredField("theUnsafe");
 field.setAccessible(true);
 return (Unsafe) field.get(null);
 } catch (Exception e) {
 log.error(e.getMessage(), e);
 return null;
 }
 }
 
 public static void main(String[] args) {
 Unsafe unsafe = UnsafeTest.reflectGetUnsafe();
 }
 }
 
 | 
 CAS演练
- 创建一个类
| 12
 3
 4
 5
 
 | @Getter@Setterpublic class User {
 private String name;
 private int age;
 }
 
 | 
- 反射获取Unsafe并测试CAS
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 
 | @Slf4jpublic class UnsafeTest {
 
 private static Unsafe reflectGetUnsafe() {
 try {
 Field field = Unsafe.class.getDeclaredField("theUnsafe");
 field.setAccessible(true);
 return (Unsafe) field.get(null);
 } catch (Exception e) {
 log.error(e.getMessage(), e);
 return null;
 }
 }
 
 public static void main(String[] args) throws Exception{
 Unsafe unsafe = UnsafeTest.reflectGetUnsafe();
 
 User user = (User)unsafe.allocateInstance(User.class);
 user.setName("admin");
 user.setAge(17);
 
 
 Field name = User.class.getDeclaredField("name");
 Field age = User.class.getDeclaredField("age");
 
 
 long nameOffset = unsafe.objectFieldOffset(name);
 long ageOffset = unsafe.objectFieldOffset(age);
 
 System.out.println("name内存偏移地址:" + nameOffset);
 System.out.println("age 内存偏移地址:" + ageOffset);
 
 System.out.println("---------------------");
 
 
 int currentValue = unsafe.getIntVolatile(user, ageOffset);
 System.out.println("age内存当前值:" + currentValue);
 boolean casAge = unsafe.compareAndSwapInt(user, ageOffset, 17, 18);
 System.out.println("age进行CAS更新成功:" + casAge);
 System.out.println("age更新后的值:" + user.getAge());
 
 System.out.println("---------------------");
 
 
 unsafe.putObjectVolatile(user, nameOffset, "test");
 System.out.println("name更新后的值:" + unsafe.getObjectVolatile(user, nameOffset));
 
 }
 }
 
 | 
结果输出
| 12
 3
 4
 5
 6
 7
 8
 
 | name内存偏移地址:16age 内存偏移地址:12
 ---------------------
 age内存当前值:17
 age进行CAS更新成功:true
 age更新后的值:18
 ---------------------
 name更新后的值:test
 
 | 
Unsafe中CAS操作是原子性的,所以在秒杀、库存扣减中也可以使用Unsafe来扣减库存。
 结语
本文对Java中的sun.misc.Unsafe的用法及应用场景进行了基本介绍,仅做后续源码阅读的铺垫。到此,本篇文章就写完了,感谢大家的阅读!如果您觉得对您有帮助,请关注公众号【当我遇上你】。