JAVA并发编程(4):原子性操作以及CAS操作
一.原子性操作
所谓原子性操作,是指执行一系列操作时,这些操作要么全部执行,要么全部不执行,不存在执行其中一部分的情况。
如果不能保证操作是原子的,那么在多线程情况下,就有可能会存在线程安全问题。
接下来用代码来验证非原子操作在多线程环境下的线程安全问题:
public class ThreadSafeTest {
private static final Logger LOGGER = LoggerFactory.getLogger(ThreadSafeTest.class);
private static int value = 0;
private static void increaseValue() {
for (int i = 0; i <100000;i++) {
value++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
increaseValue();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
increaseValue();
}
});
thread1.start();
thread2.start();
thread1.join();
thread1.join();
LOGGER.info("value:{}",value);
}
}
第一次运行输出:
20:25:16.027 [main] INFO com.example.demo.ThreadSafeTest - value:125427
第二次运行输出:
20:28:17.946 [main] INFO com.example.demo.ThreadSafeTest - value:129915
可以看到两次运行结果都不等于两个线程加起来的循环次数200000,这就是非原子性操作在多线程环境下的线程安全问题。
二.CAS操作
那么如何保证多个操作的原子性呢?最简单的办法是加锁,这里不讨论,除了加锁以外,还有一个更加轻量级的操作:CAS,CAS能够在不阻塞线程的情况下保证读-修改-写操作的原子性。
CAS即Compare And Swap,比较并交换,其是JDK提供的非阻塞原子性操作,底层通过硬件来保证比较-更新的原子性,主要包含三个步骤:
设在内存中有变量V,旧的预期值A,需要修改的新值B:
1.比较A与V是否相等;
2.如果相等,将B写入V;
3.返回操作是否成功;
这里认为A与V相等,则V没有被其他线程修改,否则认为V被其他线程修改过,将放弃这次修改,这个前提的存在也导致了ABA问题,暂不讨论。
JDK的Unsafe类提供了一系列的CAS操作,这里简单介绍
public final native boolean compareAndSwapLong(Object obj, long valueOffset, long expect, long update);
这个方法是native方法,由处理器保证该方法的原子性。
四个参数非别为:对象内存位置,对象中的变量偏移量,变量预期值以及变量新的值。
如果对象obj中内存偏移量为valueOffset的变量值为expect,则使用新的值update替换旧的值expect。
public final long getAndSetLong(Object var1, long var2, long var4) { long var6; do { var6 = this.getLongVolatile(var1, var2); } while(!this.compareAndSwapLong(var1, var2, var6, var4)); return var6; }
该方法首先获取当前变量值,再使用CAS操作设置新的值,使用while循环是考虑到多线程同时修改时,CAS操作可能会失败,失败时将会重试。
JAVA并发包中的AtomicLong等原子操作类都是基于Unsafe提供的CAS操作实现的原子性操作,代码都很简单,不再记录。