一、什么是原子操作
一个或多个操作在CPU执行过程中不被中断的特性,这些操作的执行顺序不能被打乱。
举个例子:
//就是一个原子操作
int i = 1;
// 非原子操作,i++被分割成3步,第一步读取i的值,第二步计算i+1;第三部将最终值赋值给i
i++;
二、如何保证原子操作
锁和自旋CAS实现原子操作。
CAS是Compare and swap的简称,这个操作是硬件级别的操作,在硬件层面保证了操作的原子性。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。Java中的sun.misc.Unsafe
类提供了compareAndSwapInt
和compareAndSwapLong
等几个方法实现CAS。
参考自 https://www.cnblogs.com/54chensongxia/p/11910681.html
三、CAS实现原理
我们知道synchronized是一种独占锁、悲观锁,java.util.concurrent中借助了CAS指令实现了一种区别于synchronized的一种乐观锁 乐观锁概念为,每次拿数据的时候都认为别的线程不会修改这个数据,所以不会上锁,但是在更新的时候会判断一下在此期间别的线程有没有修改过数据,乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。
下面通过一张图来理解下cas(比较并交换):
通过上图可以看到cas会出现当一个线程修改数据后,造成其他线程读取不到期望值,从而引起死循环问题。以及只能保证一个共享变量的原子操作。除了这两个问题外还有个ABA问题没有体现出来,我们再画张图描述下,什么是ABA。
四、原子类操作示例
通过共享变量value自增例子,可以看到会产生并发问题。最终value值并不符合预期20000.
public class AtomicMain {
private static int value = 0;
public static void main(String[] args){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4,
5,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(() -> {
for (int j = 0; j < 4000; j++) {
value++;
}
});
}
threadPoolExecutor.shutdown();
// 主线程休眠,确保线程池执行完毕.
try {
Thread.sleep(3_000);
System.out.println("最终value:" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用原子类AtomicInteger自增,AtomicInteger提供原子操作来进行Integer的使用,每次执行结果都输出预期结果20000。
public class AtomicMain {
// 使用原子类AtomicInteger
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4,
5,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(() -> {
for (int j = 0; j < 4000; j++) {
// i++
atomicInteger.getAndIncrement();
// atomicInteger.incrementAndGet() ++i
}
});
}
threadPoolExecutor.shutdown();
// 主线程休眠,确保线程池执行完毕.
try {
Thread.sleep(3_000);
System.out.println("最终value:" + atomicInteger.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}