Atomic 学习
次访问
首先声明:http://blog.csdn.net/hsuxu/article/details/9467651 这篇文章写得特别详实,看了好多博客,只有这一篇是从硬件底层过来解释的。
眼前有景道不得,崔颢题诗在上头。
虽然,有这样一篇博客足矣,但是一来觉得,还需要自己消化,二来,个人觉得这篇文章的排版不是很工整。出于这个目的,写了这篇博客。
概述
这个包里面提供了一组原子变量类。
Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。
其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。
原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。
原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。
在了解具体实现之前,有必要先知道 CAS 的知识,因为它是整个包的前提。
关于CAS,此处只介绍一句话:
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
更多关于 CAS 的知识请参见本人其他博客。
具体介绍
java.util.concurrent.atomic中的类可以分成4组:
- 标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- 复合变量类:AtomicMarkableReference,AtomicStampedReference
第一组 AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。
此处仅以 AtomicInteger 作为第一组的代表介绍:
AtomicInteger 常用方法如下:
- int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
boolean compareAndSet(int expect, int update) :如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
- void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
/**
* Atomically adds the given value to the current value.
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
}
当没有使用 AtomicInteger时,仅用 valatile 关键字,并不能保证内存同步,这个在之前也做过介绍。
下面请看自己亲测的一个例子:
package lee;
import java.util.concurrent.atomic.AtomicInteger;
public class Atomic {
private static volatile int i = 0;
private static final int THREAD_NUM = 50;
public static void increase() {
i++;
}
public static void main(String[] args) {
// AtomicInteger ai = new AtomicInteger();
Thread[] threads = new Thread[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 100000; i++) {
increase();
}
}
});
threads[i].start();
}
while( Thread.activeCount() > 1)
Thread.yield();
System.out.println("累加的结果是:" + i);
}
}
开启 50 个线程,每个线程都对int型整数 i 加10000次,期望结果就是 500000。但是结果呢?
累加的结果是:4394704
显然与预期结果相差还不少。原因就在于i++这个操作不具有原子性。
那么采用 AtomicInteger 包装之后呢?
package lee;
import java.util.concurrent.atomic.AtomicInteger;
public class Atomic {
private static AtomicInteger i = new AtomicInteger(0);
private static final int THREAD_NUM = 50;
public static void increase() {
i.getAndIncrement();
}
public static void main(String[] args) {
// AtomicInteger ai = new AtomicInteger();
Thread[] threads = new Thread[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 100000; i++) {
increase();
}
}
});
threads[i].start();
}
while( Thread.activeCount() > 1)
Thread.yield();
System.out.println("累加的结果是:" + i);
}
}
结果是:
累加的结果是:5000000
可以看出来 getAndIncrement() 这个方法确实具有原子性。
看一下它的源码是怎么实现的?
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
此方法在一个无限循环中,不断尝试将一个比当前值大1的新值赋给自己,如果失败了,那说明在执行“获取-设置”操作时,已经有了修改,于是再次循环进行下一步操作,直到设置成功为止。