JUC并发包中有AtomicInteger、AtomicIntegerArray、AtomicLong、AtomicBoolean等原子变量操作类,他们原理都类似,本文主要分析为什么需要原子操作类以及AtomicLong类的实现原理。
为什么需要原子变量操作类?
public class Test { private static long count = 0L; private static void increase() { count++; } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[10]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new Runnable() { public void run() { for (int j = 0; j < 1000; j++) { increase(); } } }); threads[i].start(); } for (Thread thread : threads) { thread.join(); } System.out.println(count); } }
上方的代码创建一个普通long类型的变量count,启动10个线程分别对count进行1000次递增操作。我们知道count++解析为count=count+1,这个操作并不是原子性的,多线程执行这个操作必然会出现问题,如果按照正常的并发情况,执行结果应该是10000,但是实际情况是每次执行的结果都是小于10000的数字。为什么呢?
有人肯定会说,使用volatile来修饰count变量是否能解决这个问题,这里顺便来说下volatile关键字的作用及使用场景。
volatile作用
- 当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存,通俗来讲就是,线程A对一个volatile变量的修改,对于其它线程来说是可见的,即线程每次获取volatile变量的值都是最新的
- 禁止指令重排序
volatile使用场景
- 对变量的写操作不依赖当前值
- 该变量没有包含在具有其它变量的不变式中
private static volatile long count = 0L;
用volatile关键字修饰count后再次测试,发现执行结果依然小于10000。
分析volatile使用场景发现必须满足对变量的写操作不应该依赖当前值,而count++明显是依赖当前值的,所以多线程下执行count++是无法通过volatile保证结果准确性的。
那既然无法通过volatile关键字来保证准确性,我们就可以尝试使用AtomicLong原子变量操作类来进行操作,代码如下
public class Test { private static AtomicLong count = new AtomicLong(0L); private static void increase() { count.incrementAndGet(); } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[10]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new Runnable() { public void run() { for (int j = 0; j < 1000; j++) { increase(); } } }); threads[i].start(); } for (Thread thread : threads) { thread.join(); } System.out.println(count); } }
我们发现不管怎么执行,最后的结果都是10000,这是因为AtomicInteger.incrementAndGet()方法是原子性的。
AtomicLong原理分析
首先,我们来看一下一下AtomicLong的属性
public class AtomicLong extends Number implements java.io.Serializable { //通过Unsafe.getUnsafe()方法,获取unsafe实例 private static final Unsafe unsafe = Unsafe.getUnsafe(); //存放变量value的偏移量 private static final long valueOffset; //判断JVM是否支持Long类型无锁CAS static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); //VMSupportsCS8()方法是native方法 private static native boolean VMSupportsCS8(); //静态代码块获取value在AtomicLong中的偏移量 static { try { valueOffset = unsafe.objectFieldOffset (AtomicLong.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } //实际使用的变量值,可以看到这里也是使用volatile修饰的,保证多线程下的内存可见性 private volatile long value; //构造函数 public AtomicLong(long initialValue) { value = initialValue; } public AtomicLong() { } }
然后我们来看下AtomicLong的主要方法,都是使用Unsafe来实现的
//调用unsafe方法原子性设置value的值,并返回设置之前的值 public final long getAndSet(long newValue) { return unsafe.getAndSetLong(this, valueOffset, newValue); } //getAndSetLong()方法是使用CAS方式设置value值的 //其中getLongVolatile()方法和compareAndSwapLong()方法是native方法 public final long getAndSetLong(Object var1, long var2, long var4) { long var6; //每个线程获取到变量的当前值,然后使用CAS方式设置新的值,如果设置失败,则循环继续尝试,直到成功为止 do { var6 = this.getLongVolatile(var1, var2); } while(!this.compareAndSwapLong(var1, var2, var6, var4)); return var6; } //如果当前值等于期望值,则原子性设置value为新的值 public final boolean compareAndSet(long expect, long update) { return unsafe.compareAndSwapLong(this, valueOffset, expect, update); } //调用unsafe方法,原子性设置当前值自增1,返回修改前的值 public final long getAndIncrement() { return unsafe.getAndAddLong(this, valueOffset, 1L); //调用unsafe方法,原子性设置当前值自减1,返回修改前的值 public final long getAndDecrement() { return unsafe.getAndAddLong(this, valueOffset, -1L); } //调用unsafe方法,原子性设置当前值加上参数值,返回修改前的值 public final long getAndAdd(long delta) { return unsafe.getAndAddLong(this, valueOffset, delta); //调用unsafe方法,原子性设置当前值自增1,返回修改后的值 public final long incrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L; } //调用unsafe方法,原子性设置当前值自减1,返回修改后的值 public final long decrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L; } //调用unsafe方法,原子性设置当前值加上参数值,返回修改后的值 public final long addAndGet(long delta) { return unsafe.getAndAddLong(this, valueOffset, delta) + delta; }
可以看到,AtomicLong的操作都是通过Unsafe类来实现的