1.概述
- Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。
- 原子类其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法(同步的工作更多的交给了硬件),从而避免了synchronized的高开销,执行效率大为提升
- 虽然基于CAS的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小,型如计数器这样的需求用起来才有效,否则也不会有锁的存在了
- 在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。
2.代码详解
现在我们来分析一下Atomic包中AtomicInteger的源代码,其它类的源代码在原理上都比较类似。
- 有参构造函数:从构造函数函数可以看出,数值存放在成员变量value中;
- 成员变量value声明为volatile类型,说明了多线程下的可见性,即任何一个线程的修改,在其它线程中都会被立刻看到
public AtomicInteger(int initialValue) {
value = initialValue;
}
- compareAndSet方法(value的值通过内部this和valueOffset传递)
- 这个方法就是最核心的CAS操作
- 接收2个参数,一个是期望数据(expected),一个是新数据(new);如果atomic里面的数据和期望数据一致,则将新数据设定给atomic的数据,返回true,表明成功;否则就不设定,并返回false。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
- getAndSet方法,在该方法中调用了compareAndSet方法
- 本质是get( )操作,然后做set( )操作。尽管这2个操作都是atomic,但是他们合并在一起的时候,就不是atomic
- 如果在执行if(compareAndSet(current,newValue)之前其它线程更改了value的值,那么导致value的值必定和current的值不同,compareAndSet执行失败,只能重新获取value的值,然后继续比较,直到成功。
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
- i++的实现
- 一般来说自增和自减都不是原子操作,其中包含有3个操作步骤:第一步,读取i;第二步,加1或减1;第三步,写回内存
- 为了实现原子性,必须在for循环中比较值value是否被修改
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
- ++i的实现
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
- 实例:使用AutomicInteger实现随机数字生成器
puclic class AutomicRandom{
private AutomicInteger seed;
AutomicRandom(int seed){
this.seed = new AutomicInteger(seed);
}
public int nextInt(){
while(true){
int s = seed.get();
int nextSeed = calculateNext(s);
if(seed.compareAndSet(s,nextSeed)){
//对比新旧数据是否一致,一致则设置
//新的数字成功设置到原子类对象里面
int remainder = s % n;
return remainder > 0 ? remainder : remainder + n;
}
}
}
}
3. 总结
- 在中低程度的竞争下,原子类提供更高的伸缩性;在高强度的竞争下,锁能更好的帮助我们避免竞争。(来自《并发编程实战》)
- 所以,我们要视情况而定,若资源竞争规模不大,控制粒度较小,使用原子类比使用锁更好,能提高效率与性能
注意:本文归作者所有,未经作者允许,不得转载