J.U.C 系列之Atomic原子类
一 什么是原子类?
所谓原子类必然是具有原子性的类,原子性操作--原子操作,百度百科中给的定义如下
"原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切[1] 换到另一个线程)。
顾名思义,原子类就是一个一旦被执行就不能中断的类。
二 为什么需要原子类?
在看为什么需要原子类之前,我们看看普通Number类,在处理问题时可能存在的问题,这里我们通过Integer来演示
public class Main { private static Integer sum = 0; public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(new Task()).start(); } Thread.yield(); System.out.println(sum); } static class Task implements Runnable { @Override public void run() { for(int i = 0;i<100;i++){ sum++; } } } }
这段代码意思是开20个线程,每个线程对sum自加100次,理论上应该最后输出2000;但是事实上每次都是小于2000;
这是听说过volatile关键字的小伙伴可能会说,使用volatile来修饰sum,好,我们继续试验
public class Main { private static volatile Integer sum = 0; public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(new Task()).start(); } Thread.yield(); System.out.println(sum); } static class Task implements Runnable { @Override public void run() { for(int i = 0;i<100;i++){ sum++; } } } }
这是试验五次的输出
1766,1616,1859,1980,1800
还是都是小于2000,这是怎么回事,这里先提一下,volatile只能保证单个操作的原子性,而sum++,包括三个操作
sum = getSum() //读取sum temp = sum +1; //sum+1,赋给临时变量 sum = setSum(temp) //将sum写回
因此,即使是volatile也无法保证sum++的原子性,volatile只能保证单个操作的原子性,而++操作是复合操作,volatile变量会在后续章节详细讨论;
那么,在Atomic未出现之前,是如何处理i++在多线程环境下的线程安全问题,主要是通过Synchronize加锁来处理,处理过程复杂,性能低
JDK5.0之后出现的Java.util.concurrent.Atomic包中为我们提供了13中原子类,来保证单个原子变量复合操作的原子性。下面我们通过AtomicInteger的使用来认识一下原子类
三 原子类示例详解
AtomicInteger 字段
// setup to use Unsafe.compareAndSwapInt for updates //这里, unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。 private static final Unsafe unsafe = Unsafe.getUnsafe(); //valueOffset是用来记录value本身在内存的便宜地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较。 private static final long valueOffset; //value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)。 private volatile int value;
AtomicInteger 构造方法
/** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } /** * Creates a new AtomicInteger with initial value {@code 0}. */ public AtomicInteger() { }
AtomicInteger 并发安全实现
那么AtomicInteger是如何实现多线程的自增操作的线程安全的呢?核心思想就是CAS自旋;CAS:Compare And Swap 比较并交换。自旋:通过循环知道预期值和内存之相同,进行CAS操作,AtomicInteger的自增如下所示
public final int incrementAndGet() { for (;;) { //这里可以拿到value的最新内存值 int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } } public final boolean compareAndSet(int expect, int update) { //使用unsafe的native方法,实现高效的硬件级别CAS return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
AtomicInteger 其他常用方法
/** * 返回旧值,然后自增1 * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } /** * 返回旧值,然后自减1 * * @return the previous value */ public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); } /** * 返回旧值,然后 旧值+delta * * @param delta the value to add * @return the previous value */ public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); } /** * 先进行自增,返回自增后的值 * * @return the updated value */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } /** * 先进行自减,然后返回自减后的值 * * @return the updated value */ public final int decrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, -1) - 1; } /** *先在原值上加delta,再返回加之后的值 * * @param delta the value to add * @return the updated value */ public final int addAndGet(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta) + delta; }
注:其他Atomic类类似,不一一介绍;
四 Atomic 存在的问题
》 长时间自旋,导致CPU和资源的占用
》 只能保证单个原子变量的多线程安全操作,当然可以将多个变量封装成一个类,通过原子引用类型实现
》 ABA问题;使用AtomicStampedReference 原子更新带有版本号的引用类型解决