赞
踩
Atomic原子操作类是JUC包下的类,包含AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference等等,AtomicReference是可以自定定义类型的原子操作类。原理都是使用volatile和CAS(比较并交换)完成操作。
volatile是java关键字,轻量级的同步机制,用来保证变量的可见性,用于多线程。线程的操作是在线程私有的工作内存中,而变量的存储是在主内存中,所以线程修改变量时先把变量copy到工作内存中,操作完成后回写到主内存。volatile保证了变量可见性,当一个线程修改变量后,会将结果写到主内存中,并通知其他线程变量已被修改,需要重新从主内存中获取。
class MyCount { int n; AtomicInteger ai = new AtomicInteger(0); } public class AtomicTest { public static void main(String[] args) { MyCount myCount = new MyCount(); for (int i = 0; i < 30; i++) { new Thread(() -> { for (int j = 0; j < 500; j++) { myCount.n++; myCount.ai.getAndIncrement(); // 对应n++ } }).start(); } // 等待线程执行完成 try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("n=" + myCount.n); System.out.println("ai=" + myCount.ai); } } 运行结果 n=14869 ai=15000
由于n++不是原子操作(要么都执行,要么都不执行),分为3步:1 获取n值;2 值+1;3 结果赋值给n。如果两个线程同时执行,线程1取到n为1,并且准备将2赋值给n时,线程1被挂起,此时线程2获取n也为1,执行完成后将n改为2,线程1继续执行,将线程2的结果覆盖。导致最后执行结果不是15000。
而AtomicInteger.getAndIncrement()属于原子操作,运行结果为15000。我们来看一下AtomicInteger类的源码:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; // 可以理解为对象在内存中的偏移量 static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; public AtomicInteger(int initialValue) { value = initialValue; } public AtomicInteger() { } ... public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } ... }
AtomicInteger提供了很多操作方法,本质都是调用了Unsafe类的方法,追踪一下Unsafe类:
public final class Unsafe {
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
}
Unsafe在执行getAndAddInt方法时,使用了do while循环,相当于自旋。首先从主内存中获取最新的值,然后通过CAS比较刚获取的结果,如果一致则改为新值, 如果不一致,重新从内存中取并再次比较交换,直至成功。
Unsafe类是sun.misc包下的类,存在于JDK中lib目录下的rt.jar包中,内部封装了很多CAS方法,属于本地方法,是通过汇编语言操作CPU指令,防止执行被加塞的原子操作。
CAS类似于MySQL数据操作中的乐观锁,更新时比较旧值。CAS有以下缺点:
1. 空循环,do while失败一致循环操作
2. 只能对一个变量进行CAS
3. ABA问题(重点)
ABA问题:当操作数同时被多个线程做加和减时,线程1取到值后,改为2,线程2取到后改回了1,线程3取到1为1,然后改为2,线程3对线程1和线程2的操作无感知。解决此问题可以使用AtomicStampedReference类进行操作。
AtomicStampedReference原理就是用两个CAS进行判定操作,一个对变量操作, 另一个是stamp,应该每次操作固定加或者减,如果变量被ABA操作,但是stamp值已经发生改变,CAS会返回失败。
class MyCount { AtomicInteger ai = new AtomicInteger(0); AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(0, 0); } public class AtomicTest { public static void main(String[] args) { MyCount myCount = new MyCount(); int stamp = myCount.asr.getStamp(); new Thread(() -> { myCount.ai.compareAndSet(0, 1); myCount.ai.compareAndSet(1, 0); System.out.println(Thread.currentThread().getName() + "执行后 ai=" + myCount.ai); myCount.asr.compareAndSet(0, 1, stamp, stamp + 1); myCount.asr.compareAndSet(1, 0, myCount.asr.getStamp(), myCount.asr.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "执行后 asr=" + myCount.asr.getReference()); }, "Thread1").start(); new Thread(() -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } myCount.ai.compareAndSet(0, 1); System.out.println(Thread.currentThread().getName() + "执行后 ai=" + myCount.ai); boolean result = myCount.asr.compareAndSet(0, 1, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + "执行" + result + " asr=" + myCount.asr.getReference()); }, "Thread2").start(); } } 运行结果 Thread1执行后 ai=0 Thread1执行后 asr=0 Thread2执行后 ai=1 Thread2执行false asr=0
在CAS方法内,判断原reference、stamp是否和当前值一致, 如果不一致返回false;如果一致,判断新的reference、stamp未发生改变,返回成功, 如果发生了改变,使用Unsafe的本地方法调用CAS交换对象,返回结果
public class AtomicStampedReference<V> { private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; ... public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } ... private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); } ... }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。