i++不是原子操作,原因是:
i++操作分为三个阶段(读、改、写):
1) 内存到寄存器
2) 寄存器自增
3) 写回内存
这三个阶段可以被中断分离。
下面的代码可以验证i++不是原子性操作
public class AtomicTest {
public static void main(String[] args) {
AtomicDemo atomicDemo = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(atomicDemo).start();
}
}
}
class AtomicDemo implements Runnable{
private int serialNum = 0;
// private AtomicInteger serialNum = new AtomicInteger(0);
public int getSerialNum() {
// return serialNum.incrementAndGet();
return serialNum++;
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNum());
}
}
运行多次,可能出现打印输出同样serialNum,这说明在A线程进行serialNum++的读改操作,而尚未写入时,另一个线程B进行了读改操作,由此证明i++的读改写操作不具有原子性。
针对这个问题,JDK1.5后java.util.concurrent.atomic包提供了常用的原子变量,可以解决上述问题。
AtomicInteger底层采用CAS算法(Compare-And-Swap),保证数据的原子性。
CAS算法是硬件对于并发操作共享数据的支持,CAS包含三个操作数:
V:内存值
A:预估值
B:更新值
当且仅当V==A时,才会执行V=B,否则不做任何操作。