CAS
CAS(Compare-And-Swap,比较并交换)操作是CPU中术语,它保证了操作的原子性。CAS指令需要三个操作数,分别是:
V:内存位置(也就是本次操作变量的内存地址);
A:旧的预期值;
B: 操作完成后的新值。
CAS指令执行时,当且仅当V符合旧预期值A时,处理器用新值B更新V的值,否则它就不执行更新,无论是否更新,都会返回V的旧值,整个CAS操作是一个原子操作。在JDK1.5之后,Java程序中才可以使用CAS操作,该操作由sun.misc.Unsafe类里的compareAndSwapXXX()方法包装提供,虚拟机在内部对这些方法做了特殊处理。在JDK1.5中提供了原子变量,如AtomicInteger,AtomicLong等,由并发大师Doug Lea操刀,提供了在单个变量上面不需要锁的线程安全性。现在就让我们走进AtomicInteger的世界中,探究它是如何实现的,并领略大师的风采。
AtomicInteger中原子操作的实现
通过这一小部分的分析,我们就弄明白了AtomicInteger中原子操作的实现,首先看看AtomicInteger中有哪些状态
在这一段代码中,我们需要注意三个方面,也就是AtomicInteger的三个字段:(1)unsafe字段,AtomicInteger包含了一个Unsafe类的实例,unsafe就是用来实现CAS的;
(2)value字段,表示当前对象代码的基本类型的值,AtomicInteger是int型的线程安全包装类,value就代码了AtomicInteger的值。注意,这个字段是volatile的。
(3)valueOfset,通过字面意思就可以看出来valueOfset是value在内存中的偏移量,也就是在内存中的地址,通过Unsafe.objectFieldOffset(Field f)获取。前面在讲CAS时,我们提到需要操作内存的位置,valueOfset就是这个位置。
AtomicInteger中的CAS
public final boolean compareAndSet(int expect,int update)就是CAS的具体实现,其他所有的原子操作都是依赖于此方法,前面已经提到了,Java中的CAS是通过Unsafe类实现的,这个方法就是通过调用Unsafe.compareAndSwapInt(this,valueOfset,expect,udate)方法来实现的,其中valueOffset就是要操作的内存地址,expect是旧值,update是新值,当且仅当valueOffset内存中的值等于expect时,才用update更新旧值。其代码如下:
AtomicInteger提供了好几个原子操作变量的方法: int | addAndGet(int delta)
将当前值与给定的值相加,并返回新值
|
boolean | compareAndSet(int expect, int update)
当且仅当value==expect,value=update
|
int | decrementAndGet()
自减1,并返回新值
|
int | getAndAdd(int delta)
将当前值与给定值相加,并返回相加之前的值(旧值)
|
int | getAndDecrement()
自减1,并返回旧值
|
int | getAndIncrement()
自增1,并返回旧值
|
int | getAndSet(int newValue)
将当前值与给定值相加,并返回旧值
|
int | incrementAndGet()
自增1,并返回新值
|
可以看到,每一种操作(自增,自减,加给定值)都提供了对称的操作,其中getAndXXX()方法是返回旧值,XXXAndGet()返回的是新值。这些操作的实现方式都是相同的,我们以getAndIncrement()为例,来讲解它是如何实现原子操作的。首先看getAndIncrement()的源码:
在上面的注释中已经说的比较明白了,但我还是愿意再啰嗦两句:(1)首先获得当前值,保存到current中,并把current的下一个值保存到next中,next=current+1
(2)调用compareAndSet(current,next)尝试自增,如果自增成功,则返回current;如果自增失败,则说明已经有其他线程修改value的值,current中的值和next中的值已经失效了,要重新获取当前值,重复刚才的CAS操作,直到成功位置。
可以看到,通过第(2)步的操作,确实实现了多线程下的安全,即使在自增的时候有其他线程修改了当前值,自增操作也不会覆盖已经修改的值,而是在当前最新值的基础上实现自增。演示一下不使用CAS的错误情况和这种实现方式的正确性
a.错误的自增,假设当前value=8,同时有两个线程t1,t2对value执行自增操作,执行顺序如下:
执行完成后,t2的值把t1的值给覆盖了,执行完成后,现在value=9,正确的结果应该是10。在下图中,通过CAS以同样的顺序执行,获得的结果就是正确的:
上面的分析基本上就概括了AtomicInteger实现的全部了。但是CAS并不是没有缺点,概括起来说,CAS就三个缺点:
(1)ABA问题,如果V的初始值是A,在准备赋值的时候检查到它仍然是A,那么能说它没有改变过吗?也许V经历了这样一个过程:它先变成了B,又变成了A,使用CAS检查时以为它没变,其实却变里;
(2)循环时间长,开销大,通过自旋CAS一直在消耗CPU
(3)只能保证一个共享变量的原子操作,当对多个共享变量操作时就无法保证原子性了。
AtomicInteger源码分析:
转载出处:点击打开链接
举个例子:(原创)
public class bingfa {
public static void main(String[] args) {
RunTest task = new RunTest();
new Thread(task).start();
new Thread(task).start();
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(task.count);
}
}
class RunTest implements Runnable{
//public int count; //输出结果不为2000
AtomicInteger count = new AtomicInteger(0); //输出结果为2000
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//count++;
count.addAndGet(1);
}
}
}