【问题标题】:Java8: Issue on running a Thread exampleJava8:运行线程示例的问题
【发布时间】:2018-07-02 02:03:33
【问题描述】:

我在 winterbe.com 上看到了以下示例,该示例演示了原子变量的使用。

// From http://winterbe.com/posts/2015/05/22/java8-concurrency-tutorial-atomic-concurrent-map-examples/
public class Test_AtomicInteger {
  public static void main(String[] args) {
      AtomicInteger atomicInt = new AtomicInteger(0);

      ExecutorService executor = Executors.newFixedThreadPool(2);

      IntStream.range(0, 1000)
          .forEach(i -> {
              Runnable task = () ->
                  atomicInt.updateAndGet(n -> n + 2);
              executor.submit(task);
          });

      executor.shutdownNow();

      System.out.println(atomicInt.get());    // => 2000
  }
}

了解如何从线程安全场景中推导出预期值 2000。但是,当我尝试在 Eclipse IDE 上执行它时,每次运行它都会给出不同的输出值。想看看是否有人知道它为什么会这样。非常感谢。

【问题讨论】:

    标签: multithreading java-8 thread-safety atomic atomicinteger


    【解决方案1】:

    基本上,线程main所有执行的任务完成之前调用shutdownNow(即使不调用shutdownNow,您仍然看不到2000,因为您正在查询AtomicInteger 仍然在执行程序完成之前)。

    你真的想阻塞,直到你的执行程序完成或超时发生:

    executor.shutdown();
    executor.awaitTermination(100, TimeUnit.MILLISECONDS);
    

    如果您仔细查看该帖子的作者定义:

     public static void stop(ExecutorService executor) {
        try {
            executor.shutdown();
            executor.awaitTermination(60, TimeUnit.SECONDS);
        }
    
        ....
    

    【讨论】:

    • @Holger 已编辑,谢谢。我想用invokeAll 发布一些东西,只是不知道如何很好地做到这一点。我会假设 List<Callable<Integer>> list = Collections.nCopies(1000, () -> atomicInt.updateAndGet(n -> n + 2)) 需要一个演员表,因为它也可能是一个 Supplier<Integer>,但我猜目标类型也被考虑在内(因为 java-8 对吗?)因此不需要演员表
    • 确实,目标类型告诉它,否则,它可能是anything,而不仅仅是SupplierCallable。如果没有目标类型,lambda 将几乎无法使用,所以很明显为什么在 Java 8 中添加了它……
    • 感谢您的解释!
    【解决方案2】:

    正如其他人所说,shutdownNow() 不合适,因为它可能会导致排队的任务被放弃,同时不等待当前正在运行的任务完成。

    正确的顺序是shutdown() 后跟awaitTermination,但是,您可以更简单地执行相同的操作:

    AtomicInteger atomicInt = new AtomicInteger(0);
    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.invokeAll(Collections.nCopies(1000, () -> atomicInt.updateAndGet(n -> n + 2)));
    System.out.println(atomicInt.get());    // => 2000
    executor.shutdown(); // only for cleanup
    

    这里,invokeAll 会调用所有的任务,所有的任务都可以同时运行,并等待所有的任务完成。执行器甚至不需要关闭,但可以重复用于其他任务,但是一旦不再需要它就应该关闭它,以清理底层资源。

    Collections.nCopies 是获取相同元素的List 的最简单方法,甚至不需要存储来保存该数量的引用。

    由于invokeAll 需要Callables 而不是Runnables 的列表,因此任务将是Callables,但这不会影响此代码的语义。

    【讨论】:

      【解决方案3】:

      shutdownNow 的 JavaDoc 说:

      尝试停止所有正在执行的任务,停止处理 等待任务,并返回正在等待的任务列表 执行。

      此方法不等待主动执行的任务终止。 使用 awaitTermination 来做到这一点。

      因此,这不会等待您提交的所有任务完成,因此只需获取已成功运行的线程的结果即可。

      要关闭服务并等待一切完成,请将shutdownNow 替换为:

      executor.shutdown();
      
      executor.awaitTermination(10, TimeUnit.SECONDS);
      

      (您需要在某处从awaitTermination 捕获InterruptedException)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-11-04
        • 1970-01-01
        • 2012-11-12
        • 1970-01-01
        • 1970-01-01
        • 2020-07-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多