AtomicLong 不能在线程之间准确传递

AtomicLong does not pass accurately between threads

该练习与 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");
   }
}

基本上你没用过同步.

您可以更改 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

这可能是您想要做的:) 请检查 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();
        }
    }

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

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

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

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

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

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

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

"B. i = 198"

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

A. i=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,因为你这里有一个多操作临界区。

希望对您有所帮助。