【问题标题】:AtomicLong does not pass accurately between threadsAtomicLong 不能在线程之间准确传递
【发布时间】:2018-10-12 07:02:14
【问题描述】:

该练习与 Effective Java 中的第 78 项有关。即,我们创建两个线程,并行增加公共静态变量并将其打印出来。目标是为控制台生成一条不断增加的数字线。 AtomicLong 用于避免竞争条件,但有一个我无法解释的错误。 即,第一次调用

System.out.println(i.getAndIncrement());

JVM 不会读取最新的变量值。只有在第二次调用时才会读取。 Please see the console output with inconsistent output marked 有人可以建议我自己学习什么来清除这个错误吗?是时候阅读 JVM 规范了吗?

package com.util.concurrency.tick;
import java.util.concurrent.atomic.AtomicLong;

public class AtomicIncrementer implements Runnable {

  private String name;
  private static final int MAXI = 1000;
  private static final AtomicLong i = new AtomicLong(-1);

  public AtomicIncrementer(String name){
    this.name = name;
  }

  public void run(){
    while(i.get() < MAXI){
      System.out.println(name+ ". i = "
        +i.getAndIncrement());
    }
    System.out.println(name+" i = "+i.get()); 
  }

  public static void main(String[] args){;
         try {
              Thread t1 = new Thread(new AtomicIncrementer("A"));
              Thread t2 = new Thread(new AtomicIncrementer("B"));
              t1.start();
              t2.start();
            } catch (Exception e) {
            }
        System.out.println("Two incrementers launched");
   }
}

【问题讨论】:

  • 线程之间根本没有同步,因此无法预测值的打印顺序。仅仅因为AtomicLong 可以原子地获取和增加一个值并不会自动使您的线程中的循环和打印语句同步。

标签: java concurrency static atomic println


【解决方案1】:

基本上你没用过同步

您可以更改您的 run 方法,如下所示:

public void run(){

      synchronized(i){
    while(i.get() < MAXI){
      System.out.println(name+ ". i = "
        +i.getAndIncrement());
    }

    System.out.println(name+" i = "+i.get());
      }
  }

另外,您可以参考本教程以了解有关同步的更多信息:https://www.tutorialspoint.com/java/java_thread_synchronization.htm

【讨论】:

  • 如果我通过 i 进行同步,第一个线程会抓取该值并递增它直到结束,其他线程不做任何工作。但是,这仍然是预期的行为。我知道这个程序不能并行执行,只能按顺序或简单地单线程执行。
  • @ATShtirlitz 对,如果满意,您可以接受并投票。
【解决方案2】:

这可能是您想要做的:) 请检查 java 文档。您想在 2 个线程之间进行同步。基本上每个线程都会增加 1 圈,然后将计数器传递给另一个线程。

public class AtomicIncrementer implements Runnable {

    private static final int MAXI = 1000;
    private static final SynchronousQueue<Long> LONG_EXCHANGER = new SynchronousQueue<>();

    private final String name;

    private AtomicIncrementer(String name) {
        this.name = name;
    }

    @Override
    public void run() {

        try {

            while (true) {

                Long counter = LONG_EXCHANGER.take();
                if (counter >= MAXI) {
                    LONG_EXCHANGER.put(counter);
                    break;
                }

                System.out.println(name + ". i = " + (counter + 1));
                LONG_EXCHANGER.put(counter + 1);
            }

            Long counter = LONG_EXCHANGER.take();
            System.out.println(name + " final i = " + counter);
            LONG_EXCHANGER.put(counter);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(new AtomicIncrementer("A"));
        Thread t2 = new Thread(new AtomicIncrementer("B"));
        t1.start();
        t2.start();
        System.out.println("Two incrementers launched");

        try {
            LONG_EXCHANGER.put(-1L);
            t1.join();
            System.out.println("T1 ended");
            //this is needed for last thread to end
            LONG_EXCHANGER.take();
            t2.join();
            System.out.println("T2 ended");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

【讨论】:

    【解决方案3】:

    同步不是问题,因为 AtomicLong 就是为了这个。 相反,必须在线程切换时将共享字段从 & 更新到本地线程内存。为此存在volatile

    private static final volatile AtomicLong i = new AtomicLong(-1);
    

    其实我也不是很确定!对于long,情况很清楚, 但只有一个对象;它的长字段正在更新。

    【讨论】:

      【解决方案4】:

      这里有很多竞争条件。 以下事件顺序将导致您看到的结果:

      线程 A 一直运行到 i=198。然后是上下文切换,线程 B 运行以下命令:

      System.out.println(name+ ". i = "
              +i.getAndIncrement());
      

      在while循环中。 线程 B 创建字符串:

      “B.i = 198”

      但是在线程 B 有机会打印该字符串之前,线程 A 再次发生上下文切换。 所以线程 A 继续执行直到它打印出来

      A.我=204

      然后有一个上下文切换到线程 B,它从之前停止的地方恢复,它正在打印字符串:

      B. i = 198

      基本上,在获取i 的当前值并打印它之前,您有一个竞争条件。

      换句话说i.getAndIncrement(); 是一个原子操作。 但是,

      System.out.println(name+ ". i = "
              +i.getAndIncrement());
      

      不是原子操作。

      您在这里有多个操作。在伪代码中:

       1. tempInt = i.getAndIncrement();
       2. tempString1 = name + ". i = ";
       3. tempString2 = convertToString(tempInt);
       4. tempString3 = tempString1 + tempString2;
       5. print tempString3;
      

      这就是为什么你有如此混乱的输出:)

      如果你想深入了解这些概念,我推荐这个在线课程: https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

      它真的很便宜,你可以在几个小时内完成它。但它确实深入探讨了原子类的这些概念和注意事项。这是一个很好的时间投资。

      在您的情况下,您确实想要使用同步而不是 AtomicLong,因为您这里有一个多操作关键部分。

      希望对你有帮助。

      【讨论】:

      • 非常感谢您指出每个语句可能包含多个评估,线程从一个到另一个竞争。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-11
      • 1970-01-01
      • 2014-10-12
      • 1970-01-01
      相关资源
      最近更新 更多