Java原子操作

示例

原子操作是“一次全部”执行的操作,在原子操作的执行过程中没有其他线程观察或修改状态的机会。

让我们考虑一个糟糕的例子

private static int t = 0;

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(400); // 高线程数是出于演示目的。
    for (int i = 0; i < 100; i++) {
        executorService.execute(() -> {
            t++;
            System.out.println(MessageFormat.format("t: {0}", t));
        });
    }
    executorService.shutdown();
}

在这种情况下,有两个问题。第一个问题是后递增运算符不是原子的。它包含多个操作:获取值,将值加1,设置值。这就是为什么如果运行示例,很可能t: 100在输出中看不到-两个线程可以同时获取值,对其进行递增和设置:假设t的值为10,并且两个线程在递增t。两个线程都将t的值设置为11,因为第二个线程在第一个线程完成对t的递增之前观察到t的值。

第二个问题是我们如何观测t。当我们打印t的值时,该线程的增量操作后,该值可能已经被另一个线程更改了。

为了解决这些问题,我们将使用java.util.concurrent.atomic.AtomicInteger,其中包含许多原子操作供我们使用。

private static AtomicInteger t = new AtomicInteger(0);

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(400); // 高线程数是出于演示目的。
    for (int i = 0; i < 100; i++) {
        executorService.execute(() -> {
            int currentT = t.incrementAndGet();
            System.out.println(MessageFormat.format("t: {0}", currentT));
        });
    }
    executorService.shutdown();
}

该incrementAndGet方法以AtomicInteger原子方式递增并返回新值,从而消除了先前的竞争条件。请注意,在此示例中,线路仍会乱序,因为我们不努力对println调用进行排序,并且这超出了此示例的范围,因为它需要同步,并且此示例的目标是显示如何用于AtomicInteger消除与状态有关的比赛条件。