当程序更新一个变量时,如果是多线程同时更新这个变量,可能得到期望之外的值,比如变量i=1,A线程更新i+1,B线程也更新i+1,经过两个线程操作之后可能i不等于3,而是等于2。因为A、B线程在更新i时拿到的i都是1,这就是线程的不安全更新操作,通常我们会使用Synchronized来解决这个问题(也可以通过加锁的方式 ),synchronized(或加锁)会保证多线程不会同时更新变量i。
而在JDK的java.util.concurrent.atomic包中的原子类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
因为类型有很多种,所以在Atomic包中提供了13个类,4种类型的原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新属性(字段)。Atomic中的类基本都是使用Unsafe实现的包装类。
原子更新基本类型
使用原子的方式更新基本类型,Atomic包提供了以下三个类。
AtomicBoolean:原子更新布尔类型。
AtomicInteger原子更新整型。
AtomicLong原子更新长整型。
这三个类提供的方法基本一样,下面以AtomicInteger来讲解,AtomicInteger的常用方法如下。
getAndAdd
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
compareAndSet
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
getAndIncrement 以原子方式将当前值加1,注意,这里返回的是自增前的值
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
getAndSet 返回的是旧值(细心留意方法名可以发现,get在前面是获取旧值再操作,Get在后面是获取操作后的新值)
/**
* Atomically sets to the given value and returns the old value.
*
* @param newValue the new value
* @return the previous value
*/
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
示例
/** * @author perist * @date 2017/3/19 * @time 22:56 */ public class AtomicIntegerTest {
static AtomicInteger atomicInteger = new AtomicInteger(1); public static void main(String[] args) { System.out.println(atomicInteger.getAndIncrement()); System.out.println(atomicInteger.get()); }
}
该示例输出
1 2
Process finished with exit code 0
那么getAndIncrement 是如何实现原子操作的呢,?我们分析下其源码:
unsafe.getAndAddInt(this, valueOffset, 1);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; }
关键是调用compareAndSwapInt,该方法先检查当前数值是否等于var5,等于意味着AtomicInteger没有被其他线程修改过,则将AtomicInteger的当前值更新为var5+var4,如果不等于compareAndSwapInt返回false,程序会进入do…while重新进入操作。
Atomic包提供了3种基本类型的原子更新,但是Java的基本类型还有char,float,double等。那么问题来了如何更新其他的基本类型呢,?atomic包里的类基本都是Unsafe实现的,让我们看一下Unsafe源码:
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); public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
通过源码,我们发现Unsafe只提供了3种CAS操作,再看AtomicBoolean源码,发现它也是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS操作的,所以原子更新char,float,double也可以用类似的思路来解决。