1. 原子操作类的作用
当程序更新一个变量时,如果多个线程同时更新该变量,可能会得到期望以外的值。比如i=1, 线程A更新i+1, 同时线程B更新I+1,经过两个线程的操作,最终变量i的值可能不是3,而是2。因为线程A、B拿到的i的值都是1,这就是线程不安全的更新操作。我们可以用synchronized来解决这样的问题,synchronized可以保证多线程之间的同步,以保证多个线程不会同时操作变量i。
但是在JDK1.5开始,就提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了更为简单高效、线程安全的方式来更新一个变量的值。
2. 原子操作类基本分类
原子更新基本类型(3个)
AtomicBoolean 原子更新布尔类型
AtomicInteger 原子更新整型
AtomicLong 原子更新长整型
原子更新数组(3个)
AtomicIntegerArray 原子更新整形数组中的元素
AtomicLongArray 原子更新长整型数组中的元素
AtomicReferenceArray 原子更新引用类型数组中的元素
原子更新引用类型(3个)
AtomicReference 原子更新引用类型
AtomicReferenceFieldUpdater 原子更新引用类型中的字段
AtomicMarkableReference 原子更新带有标记位的引用类型
原子更新字段类(3个)
AtomicIntegerFieldUpdater 原子更新整形字段
AtomicLongFieldUpdater 原子更新长整型字段
AtomicStampedReference 原子更新带有版本号的引用类型
3. CAS方式实现原子操作基本原理
JVM中CAS操作主要是利用了处理器提供的CMPXCHG执行实现。基本的思路就是利用循环进行CAS操作,直到成功为止。CAS主要涉及到三个操作数,内存中的值(V)、旧的预期值(A)、需要修改的新值(B),当且仅当V==A时,才会将V值修改为B值,否则什么都不做,并且通过一个布尔值返回结果。伪代码如下:
//伪代码
boolean compareAndSwap(V,A,B){
for(;;){
if(V==A)
V=B;//替换旧值
}
}
4. CAS方式产生的问题(3个)
ABA问题: CAS操作时,检查值有没有变化,如果没有变化则更新,但是如果一个值原来是A,中间变成了B,然后又变为A,CAS进行检查时,就会发现它的值没有变化,但是实际上却已经变化了。解决ABA问题,可以在变量前加一个版本号,变量更新时,版本号就加1.
循环时间长,开销大:CAS采用的是自循的方式进行检查,如果长时间不成功,那么就会给CPU带来非常大的开销。
只能保证一个共享变量的原子操作:当对一个共享变量进行原子操作时,我们可以采用CAS的方式进行更新,但是如果对多个共享变量进行操作时,CAS就无法保证操作的原子性,那么这个时候就需要用锁来实现。
5. 原子操作类中主要的方法
boolean compareAndSet(int expect, int update) ;如果输入的值等于预期值,那么以原子的方式将该值设为输入的值。
int addAndGet(int delta);以原子的方式将输入的数值与实例中的值相加,并返回更新之后的值
int getAndAdd(int delta); 以原子的方式将输入的数值与实例中的值相加,并返回旧值
int getAndSet(int newValue);以原子方式设置为newValue的值,并返回旧值
通过阅读源码,可以发现CAS操作都是使用Unsafe类下的方法进行操作,而Unsafe类只提供了三种CAS方法:
compareAndSwapObject(this, valueOffset, expect, update);
compareAndSwapLong(this, valueOffset, expect, update);
compareAndSwapInt(this, valueOffset, expect, update);
所以,对于其他类型的原子操作,都是进行类型转换,将其类型转换为这三种类型,然后进行原子操作。如Boolean型的,先转成整整,然后在使用compareAndSwapInt进行操作;所以像char/float/double/short...等都可以按照这种思路实现。