原子操作

原子操作是一次性执行的操作,在原子操作执行期间没有任何其他线程观察或修改状态的机会。

让我们考虑一个不好的例子

private static int t = 0;

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
    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 的值时,在该线程的增量操作之后,该值可能已被另一个线程更改。

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

private static AtomicInteger t = new AtomicInteger(0);

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
    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 来消除有关国家的种族状况。