Java 并发与原子类

标签 java multithreading synchronization atomic java-threads

据我所知,当尝试从多个线程执行相同的操作时,对 Java 并发 API 的原子类的操作是一个接一个地执行的,以下程序的输出对我来说似乎不一致。

public class VisitorCounterAtomic {

    private AtomicInteger visitorCount = new AtomicInteger(0);

    public void visitAndPrint() {
        System.out.println("Total Visitors: " + visitorCount.incrementAndGet());
    }

    public static void main(String... args) {
        ExecutorService service = null;
        VisitorCounterAtomic counter = new VisitorCounterAtomic();

        try {
            service = Executors.newFixedThreadPool(20);
            for (int i = 0; i < 10; i++)
                service.submit(() -> counter.visitAndPrint());
        } finally {
            if (null != service) service.shutdown();
        }
    }
}

输出:

Total Visitors: 1
Total Visitors: 4
Total Visitors: 2
Total Visitors: 5
Total Visitors: 3
Total Visitors: 6
Total Visitors: 7
Total Visitors: 8
Total Visitors: 9
Total Visitors: 10

我的预期输出:

Total Visitors: 1
Total Visitors: 2
Total Visitors: 3
Total Visitors: 4
Total Visitors: 5
Total Visitors: 6
Total Visitors: 7
Total Visitors: 8
Total Visitors: 9
Total Visitors: 10

我知道我可以通过使用 synchronization block 生成预期的输出,但我需要解释为什么不只使用原子变量生成预期的输出。

我的推理就像是 - 无论线程执行顺序如何,它都会在另一个线程递增并打印原子变量的值之前递增和打印。

最佳答案

实际顺序与AtomicInteger没有任何关系。
AtomicInteger 保证值可以自动更新。它不保证线程按顺序执行。
实际上,ExecutorService 实例以异步方式处理任务。
所以你不能有一个可预测的任务完成顺序。
实际上,您在执行 incrementAndGet()println() 之间存在竞争条件:

public void visitAndPrint() {        
    System.out.println("Total Visitors: " + visitorCount.incrementAndGet());
}

例如假设:

  • 线程 A 执行 visitorCount.incrementAndGet()(计数器 = 1)但不执行 println()
  • 线程 A 暂停
  • 线程 B 执行 visitorCount.incrementAndGet()(计数器 = 2)和 println()
  • 线程 A 恢复。 println()被执行

结果:

Total Visitors: 2

Total Visitors: 1

通过同步方法,您应该有预期的顺序:

public synchronized void visitAndPrint() {        
    System.out.println("Total Visitors: " + visitorCount.incrementAndGet());
}

关于Java 并发与原子类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49968404/

相关文章:

java - 使用 Spring Security 进行两级身份验证

multithreading - delphi xe下服务应用程序中的线程不起作用

ios - 使用 "performSelectorOnMainThread"在 iOS 上进行上下文切换

Eclipse/Aptana 文件同步解决方案

c++ - 在不阻塞的情况下唤醒多个等待线程的最便宜方法

数据库表的Java格式化日期输出

java - 取消从 InputStream 的读取

java - 在 ByteBuddy rebase 期间拦截构造函数

c# - 我真的锁定了这些东西吗

java - 使用 System.out.println(Thread.currentThread().getName() + ""+count);导致同步