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操作实现的原子性操作,代码都很简单,不再记录。

posted @ 2022-01-04 19:52  取名好烦呀  阅读(116)  评论(0编辑  收藏  举报