Unsafe -- Java的魔法类(二)
原创不易,转载请注明来源
文接上集,上集主要是对Unsafe类做了简单的介绍,从本问开始,主要是对Unsafe类的八大类的详细用法展开演述。
1、CAS
- java.util.concurrent.atomic相关类
- Java AQS
- ConcurrentHashMap
CAS 即compare and swap,中文名被翻译为比较并交换。在sun.misc.Unsafe类中,主要方法体现为一下三种:
1public final boolean compareAndSwapObject(Object o, long offset, Object expected, Object x) 2 3public final boolean compareAndSwapInt(Object o, long offset, int expected, int x) 4 5public final boolean compareAndSwapLong(Object o, long offset, long expected, long x)
- 第一个参数o:当前的对象。
- 第二个参数offset:内存地址。
- 第三个参数expected:被交换的值。
- 第四个参数x:交换的值。
解释:该方法的通过offset内存地址与被交换的值进行比较,如果他们相等,则将值替换为x,并返回true;
感觉听起来有点抽象的话,请看下面的案例:
案例: int a = 1; 先通过CAS的方式,将a的值变为2。
1public class CasDemo { 2 public static void main(String[] args) { 3 CasDemo casDemo = new CasDemo(); 4 boolean flag = casDemo.compareAndSwap(1, 2); 5 System.out.println(flag+", a="+casDemo.getA()); 6 } 7 8 public volatile int a = 1; 9 private long offset; 10 11 public int getA() { 12 return a; 13 } 14 15 /** 16 * 17 * @param curr 当前本应的值 18 * @param expect 期望变为的值 19 */ 20 @SneakyThrows 21 public boolean compareAndSwap(int curr, int expect){ 22 Unsafe unsafe = getUnsafe(); 23 offset = unsafe.objectFieldOffset(this.getClass().getField("a")); //获取变量a的内存地址 24 return unsafe.compareAndSwapInt(this,offset,curr,expect); 25 } 26 27 @SneakyThrows 28 Unsafe getUnsafe() { 29 Field declaredField = Unsafe.class.getDeclaredField("theUnsafe"); 30 declaredField.setAccessible(true); 31 return (Unsafe) declaredField.get(null); 32 } 33}
运行结果:

有人会想,不就是换个值嘛,有必要搞得这么花里胡哨吗?
细想来,如果是单线程少并发情况下,确实没必要,直接进行赋值a++就好了;但如果是高并发情况呢?有人说可以加锁啊!直接使用synchronized关键字进行加锁,不可否认确实可以解决问题,但是直接上来使用一个重量级锁,不感觉屈才了吗?也确实没必要,细想来也有一个线程安全的类AtomicInteger可以完成此任务,直接调用其方法 getAndIncrement() 默认向上加一。点开看其源码,如下:
1private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe(); 2 3public final int getAndIncrement() { 4 return U.getAndAddInt(this, VALUE, 1); 5} 6 7public final int getAndAddInt(Object o, long offset, int delta) { 8 int v; 9 do { 10 v = getIntVolatile(o, offset); 11 } while (!weakCompareAndSetInt(o, offset, v, v + delta)); 12 return v; 13} 14 15public final boolean weakCompareAndSetInt(Object o, long offset, int expected, int x) { 16 return compareAndSetInt(o, offset, expected, x); 17}
原来也还是通过调用Unsafe类的CAS方法进行操作,但是考虑到在多线程情况下,假设现在共享变量a=1,有两个线程t1和t2去获取修改共享变量a,线程t1欲使共享a的值加1,线程t2也欲使共享a的值加1。t1跑的比较快,先获取到a的值为1,随后t2也获取到a的值为1,紧接着t1拿自己副本o(值为1)与真实的o(即a的值,值为1)比较,相等,修改成功,t2也拿自己副本o(值为1)与真实的o(即a的值,值为2)比较,不相等,修改失败;
案例:main线程和线程t1修改共享变量a,main线程先拿到值1,但并未写回,线程t1获取到假设为主线本应写回的数据1,欲写回数据2。
1public class CasDemo03 { 2 @SneakyThrows 3 public static void main(String[] args) { 4 CasDemo03 casDemo = new CasDemo03(); 5 6 new Thread(() -> { 7 System.out.println("等待。。。"); 8 int andSwap = casDemo.compareAndSwap(1, 2); 9 System.out.println(andSwap); 10 }).start(); 11 12 Thread.sleep(3000); 13 casDemo.setA(1); 14 } 15 16 public volatile int a = 0; 17 private long offset; 18 private Unsafe unsafe = getUnsafe(); 19 20 public Integer getA() { 21 return a; 22 } 23 24 @SneakyThrows 25 public void setA(Integer a) { 26 offset = unsafe.objectFieldOffset(this.getClass().getField("a")); 27 this.a = a; 28 } 29 30 //获取变量a的内存地址 31 @SneakyThrows 32 public long getOffset() { 33 offset = unsafe.objectFieldOffset(this.getClass().getField("a")); 34 return offset; 35 } 36 37 @SneakyThrows 38 public int compareAndSwap(Integer curr, Integer expect) { 39 offset = getOffset(); 40 do { 41 } while (!unsafe.compareAndSwapInt(this, offset, curr, expect));//如果判断失败,则自旋等待 42 return getA(); 43 } 44 45 @SneakyThrows 46 private final static Unsafe getUnsafe() { 47 Field declaredField = Unsafe.class.getDeclaredField("theUnsafe"); 48 declaredField.setAccessible(true); 49 return (Unsafe) declaredField.get(null); 50 } 51}
可以很容易的发现线程t1会先打印出等待。。。 ,直到main线程修改共享变量为1之后才继续运行。其实在此处,大家可能也很容易就发现一个问题,假使在t1循环判断期间,有一个线程偷偷将共享变量a变为其他值,然后再偷偷变回1,线程t1是绝对发现不了的,这也就是我们常说的多线程的ABA问题。解决ABA主流就是引入版本号(这也是jdk源码中解决ABA问题的方式),从Java1.5 开始,JDK 的 atomic 包里提供了一个类 AtomicStampedReference 用来解决 ABA 问题。详细请看AtomicStampedReference源码中的compareAndSet()方法。
