引言
??Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。
??因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,可分为4种类型的原子更新方式,分别是
- 原子更新基本类型
- 原子更新数组
- 原子更新引用
- 原子更新属性(字段)
Atomic包里的类基本都是使用Unsafe实现的包装类。
一、基本类型的原子操作类
Atomic 包提供了以下3个基本类型的原子操作类:
- AtomicBoolean: 原子更新布尔类型;
- AtomicInteger: 原子更新整形;
- AtomicLong: 原子更新长整形;
以上3个类提供的方法几乎一模一样,所以本文仅以AtomicInteger为例,常用方法如下:
- int addAndGet(int delta): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
- boolean compareAndSet(int expect,int update): 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值
- int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值,相当于
(i++)
的形式。 - void lazySet(int newValue): JDK6所增加,最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。关于该方法的更多信息可以参考并发编程网翻译的一篇文章《AtomicLong.lazySet是如何工作的?》,文章地址是“http://ifeve.com/howdoes-atomiclong-lazyset-work/”
- int getAndSet(int newValue): 以原子方式设置为newValue的值,并返回旧值。
@ Example1 ?AtomicInteger 使用例子
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
static AtomicInteger ai = new AtomicInteger(1);
public static void main(String[] args) {
System.out.println(ai.getAndIncrement());
System.out.println(ai.get());
}
}
运行结果:
1
2
其余的基本类的原子操作类的解决方案
??java 的基本类型一共有8个,然而JDK却只提供了三个,如何原子的更新其他的基本类型呢?特别是常用的char
,float
、double
。Atomic包里的类基本都是使用Unsafe实现的,让我们一起看一下Unsafe的源码.
/**
* 如果当前数值是expected,则原子的将Java变量更新成x
* @return 如果更新成功则返回true
*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
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);
??通过源码,可以发现Unsafe只提供了3种CAS方法,那么 AtomicBoolean
是如何实现的呢,我们再来看一下 AtomicBoolean
的源码。只给出关键部分
public class AtomicBoolean implements java.io.Serializable {
//存储值:1,0;1代表 true,0 代表 false
private volatile int value;
//Unsafe 对象
private static final Unsafe unsafe = Unsafe.getUnsafe();
public final boolean compareAndSet(boolean expect, boolean update) {
//boolean 类型 转换成 整形,true 对应 1,false 对应 0;
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
public final void set(boolean newValue) {
value = newValue ? 1 : 0
}
public final boolean get() {
return value != 0;
}
//.......
}
??从AtomicBoolean
的源码可以看出,它是先把 boolean
的值转换成整型来存储,再使用compareAndSwapInt进行CAS。所以其他基本类型的原子类型也可参照这个思路来实现。
??思路有了,接下来考虑怎么实现。byte
、char
与 int
的转换很容易,他们的原子操作类就很容易实现了。那float
,double
要怎样才能以整形或者长整形来存储值呢?
??Float
类中 提供了static两个方法:通过 Static int floatToIntBits(float value)
(根据 IEEE 754 浮点“单一格式”位布局,返回指定浮点值的表示形式)方法,便可以将float 的值以整形的方式存储下来。如果要取值,则通过 static float intBitsToFloat(int bits)
方法,将存储下来的整形的浮点值转换回原来的float
值。
??同理,Double
类也提供了类似的两个方法:static long doubleToLongBits(double value)
产生long
类型的浮点值。static double longBitsToDouble(long bits)
将long
的浮点值转换回double
值。
下面是 AtomicDouble 的实现例子:
@ Example2 ?AtomicDouble 的实现例子
import java.util.concurrent.atomic.AtomicLong;
public class AtomicDouble extends Number {
private AtomicLong bits;
public AtomicDouble() {
this(0d);
}
public AtomicDouble(Double value){
bits = new AtomicLong(Double.doubleToLongBits(value));
}
public final double get() {
return Double.longBitsToDouble(bits.get());
}
public final void set(double value) {
bits.set(Double.doubleToLongBits(value));
}
public final double addAndGet(double delta) {
long newBits = bits.addAndGet(Double.doubleToLongBits(delta));
return Double.longBitsToDouble(newBits);
}
public final double getAndAdd(double delta) {
long oldBits = bits.getAndAdd(Double.doubleToLongBits(delta));
return Double.longBitsToDouble(oldBits);
}
public final double getAndSet(double newValue) {
long oldBits = bits.getAndSet(Double.doubleToLongBits(newValue));
return Double.longBitsToDouble(oldBits);
}
public final boolean compareAndSet(double expect, double update) {
return bits.compareAndSet(Double.doubleToLongBits(expect), Double.doubleToLongBits(update));
}
public final boolean weakCompareAndSet(double expect, double update) {
return bits.weakCompareAndSet(Double.doubleToLongBits(expect), Double.doubleToLongBits(update));
}
public final void lazySet(double newValue) {
bits.lazySet(Double.doubleToLongBits(newValue));
}
@Override
public int intValue() { return (int) this.get(); }
@Override
public long longValue() { return (long) this.get(); }
@Override
public float floatValue() { return (float) this.get();}
@Override
public double doubleValue() { return this.get(); }
}
关于原子操作类实现其余猜想:
??除了上面的那种实现方案外,在 stackOverflow 上也发现两个原子操作类的实现方案:
- 利用
AtomicReference
类;如Float
的原子操作类可以是AtomicReference<Float>
。不可,经过测试,这种方法是不行的,因为像compareAndSet
这类方法比较的是对象的内存地址,而不会使用equal()
方法进行比较。 - 使用JDK1.8新增的方法:
DoubleAdder
、DoubleAccumulator
。没去深入了解,粗略看了下,这两个类都是适合于“多写少读”的情况。
二、数组的原子操作类
通过原子的方式更新数组里的某个元素,Atomic包提供了以下3个类:
- AtomicIntegerArray: 原子更新整型数组里的元素
- AtomicLongArray: 原子更新长整型数组里的元素
- AtomicReferenceArray: 原子更新引用类型数组里的元素
这几个类的方法几乎一样,以AtomicIntegerArray
为例,AtomicIntegerArray
类主要是提供原子的方式更新数组里的整型;
构造方法:
AtomicIntegerArray(int length): 创建给定长度的新 AtomicIntegerArray。
AtomicIntegerArray(int[] array): 创建与给定数组具有相同长度的新 AtomicIntegerArray,并从给定数组复制其所有元素。
其常用方法:
int addAndGet(int i, int delta): 以原子方式将输入值与数组中索引i的元素相加。
boolean compareAndSet(int i, int expect, int update): 如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。
@ Example3 ?AtomicIntegerArray 的使用例子
public class AtomicIntegerArrayTest {
static int[] value = new int[] { 1, 2 };
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
ai.getAndSet(0, 3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
运行结果:
3
1
AtomicIntegerArray类需要注意的是 ,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。
三、引用类型的原子操作类
?基本类型的原子操作类只能更新基本类型的值,不能更新引用类型的对象引用。Atomic包提供了以下三个类,可以原子方式更新的对象引用。
- AtomicReference: 原子更新引用类型。
- AtomicStampedReference: 原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。
- AtomicMarkableReference: 原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)
1. AtomicReference 介绍
构造方法
AtomicReference(): 使用 null 初始值创建新的 AtomicReference
。
AtomicReference(V initialValue): 使用给定的初始值创建新的 AtomicReference
。
常用的方法: 与前面介绍的类差不多,get()
、set(V newValue)
、compareAndSet(V expect, V update)
、getAndSet(V newValue)
;
@ Example4 ?AtomicReference 的使用例子
public class AtomicReferenceTest {
public static AtomicReference<user> atomicUserRef = new AtomicReference</user><user>();
public static void main(String[] args) {
User user = new User("conan", 15);
atomicUserRef.set(user);
User updateUser = new User("Shinichi", 17);
atomicUserRef.compareAndSet(user, updateUser);
System.out.println(atomicUserRef.get().getName());
System.out.println(atomicUserRef.get().getOld());
}
static class User {
private String name;
private int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
运行结果:
Shinichi
17
2. AtomicMarkableReference 与 AtomicStampedReference 介绍
??这两个类的作用与 AtomicReference
的作用非常相似,都是原子更新引用类型。但他们还有一个作用:能解决CAS过程中的ABA问题。
什么是ABA问题?
??ABA的问题就是:在多线程的环境下,线程1将A的值赋给了B,然后B的值又重新赋值了A。在这个过程中,A已经被修改了一次了,但是线程2不知道,在进行CAS时,认为A的值没有被修改过,所以就进行修改。当然,如果只对结果敏感,而对修改的次数不敏感,那么这个问题就无所谓了。
AtomicStampedReference 和 AtomicMarkableReference 是怎么解决ABA问题的?
??这两个类的解决方案也很简答,就是多维护了一个标志位记录修改的状态 或者 维护一个版本号记录修改的次数,然后进行CAS时,也会比较标志位或者版本号。简单看一下源码吧。
AtomicMarkableReference 用的是标志位mark
(布尔类型)来标志修改的状态,下面是关键部分源码:
//构造方法,initialRef 是 初始的引用类型,initialMark 是初始的标志位
public AtomicMarkableReference(V initialRef, boolean initialMark);
//CAS 不仅要比较引用类型,还要比较标志位
public boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedMark == current.mark &&
((newReference == current.reference &&
newMark == current.mark) ||
casPair(current, Pair.of(newReference, newMark)));
}
AtomicStampedReference 用的则是版本号stamp
(整形int) 来记录修改的次数,下面是关键部分的源代码:
//构造方法,初始引用类型 和 初始版本号
public AtomicStampedReference(V initialRef, int initialStamp);
//CAS 不仅要比较引用类型,还要比较版本号
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)));
}
@ Example5 ?AtomicStampedReference 的解决ABA问题的例子
看一个AtomicStampedReference
的例子。当需要执行一次更新当前用户的操作,这里故意将更新的用户就是当前用户,也就是更新后对象引用是没有变化的。线程A、线程B 都接到这个任务,但只能执行一次,意味着得有一个线程更新失败。User类的代码请查看上一个例子。
public class AtomicStampedReferenceTest {
public static void main(String[] args) throws InterruptedException {
User user = new User("小赫",25);
//初始版本为1
int stamp = 1;
//设置当前用户是 小赫
AtomicStampedReference<User> currentAtomicUser = new AtomicStampedReference<User>(user, stamp);
//更新当前用户,更新的目标用户就是当前用户
UpdateUserTask task = new UpdateUserTask(stamp, currentAtomicUser, user, user);
//线程A,线程B执行相同的更新任务,但更新操作只需要执行一次。
Thread threadA = new Thread(task,"ThreadA");
Thread threadB = new Thread(task,"ThreadB");
threadA.start();
threadB.start();
}
}
class UpdateUserTask implements Runnable{
private int stamp;
private AtomicStampedReference<User> currentAtomicUser;
private User newUser;
private User oldUser;
public UpdateUserTask(int stamp, AtomicStampedReference<User> currentAtomicUser,User newUser,User oldUser) {
this.stamp = stamp;
this.currentAtomicUser = currentAtomicUser;
this.newUser = newUser;
this.oldUser = oldUser;
}
@Override
public void run() {
//更新当前用户,版本号加一
boolean b = currentAtomicUser.compareAndSet(oldUser, newUser, stamp, stamp+1);
if(b){//更新执行成功
System.out.println("线程"+Thread.currentThread().getName()+": 成功执行更新操作");
}else{//更新失败
System.out.println("线程"+Thread.currentThread().getName()+": 当前用户是 "+ currentAtomicUser.getReference().getName()+" ,版本号已经过期,更新操作已经被其他线程完成");
}
}
}
运行结果:
线程ThreadB: 当前用户是 小赫 ,版本号已经过期,更新操作已经被其他线程完成
线程ThreadA: 成功执行更新操作
??从结果可以看出,当线程B执行任务时,尽管当前用户的对象引用没有改变,但版本号却已经改变了,线程B从而知道了更新操作已经被执行了,于是便不再执行更新。
四、 更新字段的原子操作类
如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:
- AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。通过静态工厂方法创建
newUpdater(Class<U> tclass, String fieldName)
; - AtomicLongFieldUpdater: 原子更新长整型字段的更新器。通过静态工厂方法创建
newUpdater(Class<U> tclass, String fieldName)
; - AtomicReferenceFieldUpdater 原子更新引用类型里的字段。比前两个方法的范围要广,可以更新任意类型的字段,通过静态的工厂方法创建
newUpdater(Class<U> tclass, Class<W> vclass, String fieldName)
;
原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。以上3个类提供的方法差不多,下面给出AtomicIntegerFieldUpdater
的例子:
@ Example6 ?AtomicIntegerFieldUpdater 例子
public class AtomicIntegerFieldUpdaterTest {
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
.newUpdater(User.class, "old");
public static void main(String[] args) {
User conan = new User("conan", 10);
System.out.println(a.getAndIncrement(conan));
System.out.println(a.get(conan));
}
public static class User {
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
运行结果:
10
11