繁体   English   中英

AtomicInteger 在 java 中无法正常工作

[英]AtomicInteger is not working properly in java

尽管我使用 AtomicBoolean 和 AtomicInteger 我有作家和读者线程,但我可以在阅读器线程中看到重复的值,请帮助我找出我的代码有什么问题。

package automic;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class AutomicTest {

    public volatile AtomicBoolean isStopped = new AtomicBoolean(false);
    public AtomicInteger  count =  new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException {
        AutomicTest test = new AutomicTest();
        
        Thread writerThread = new Thread(() ->{
            while(!test.isStopped.get()) {
                test.count.incrementAndGet();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        
        Thread readerThread = new Thread(() ->{
            while(!test.isStopped.get()) {
                System.out.println("Counter :"+test.count.get());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        
        writerThread.start();
        readerThread.start();
        
        Thread.sleep(4000);
        
        test.isStopped.getAndSet(true);
        
        writerThread.join();
        readerThread.join();
    }
}



Counter :1
Counter :2
Counter :3 // duplicate
Counter :3 // duplicate
Counter :4
Counter :5
Counter :7
Counter :8
Counter :9
Counter :10
Counter :11
Counter :12 // duplicate
Counter :12 // duplicate
Counter :13
Counter :15 // duplicate
Counter :15 // duplicate
Counter :17
Counter :18
Counter :19
Counter :20
Counter :21
Counter :22
Counter :23
Counter :24
Counter :25
Counter :26
Counter :27
Counter :28
Counter :29
Counter :30
Counter :31
Counter :32
Counter :33
Counter :34
Counter :35
Counter :36
Counter :37
Counter :38
Counter :39
Counter :40

从中获得的两大收获是:

  • Thread.sleep(100)并不意味着“睡眠 100 毫秒,精确到纳秒”。 它不太精确,取决于内部操作系统时钟的粒度和准确性、本地线程调度以及计算机上运行的其他任务。 即使是睡眠-唤醒周期也需要一些(惊人的高)时间。
  • 当多个线程独立合作时,原子是好的。 如果您需要它们以某种方式依赖,和/或对其他线程的操作做出反应,那么原子不是您想要的。 您需要使用实际的同步机制。

因此,您不能使用sleep()和 atomics 来安排两个线程在完美平衡的滴答声周期中运行。

这发生在您的代码中:

  1. 写入器线程在时间 100 写入值 1,然后进入睡眠状态。
  2. 读取器线程在时间 100 读取值 1,然后进入睡眠状态。 它们都可以轻松地在相同的毫秒数上运行,在多核系统上它们甚至可以在完全相同的时间运行,而且它们可能确实如此。
  3. 阅读器在时间 200.0 唤醒并再次读取值 1。
  4. Writer 在时间 200.02 醒来并写入值 2。糟糕,我们刚刚得到一个副本。

请注意,线程甚至可以翻转,在这种情况下,您会在序列中看到缺少的数字,有时您会看到。 为了平衡线程以在完美的 ABAB 方案中运行,您可以执行以下操作:

public class AutomicTest {

    private volatile boolean isStopped = false;

    private final CyclicBarrier barrier = new CyclicBarrier(2);
    private int count = 0;

    public static void main(String[] args) throws InterruptedException {
        AutomicTest test = new AutomicTest();

        Thread writerThread = new Thread(() -> {
            while (!test.isStopped) {
                test.count++;
                try {
                    test.barrier.await();
                    Thread.sleep(100);
                } catch (InterruptedException | BrokenBarrierException ignored) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });

        Thread readerThread = new Thread(() -> {
            while (!test.isStopped) {
                try {
                    test.barrier.await();
                    System.out.println("Counter: " + test.count);
                    Thread.sleep(100);
                } catch (InterruptedException | BrokenBarrierException ignored) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });

        writerThread.start();
        readerThread.start();

        Thread.sleep(4000);

        test.isStopped = true;

        writerThread.join();
        readerThread.join();
    }

}

这里的关键是一个CyclicBarrier ,它是:

一种同步辅助工具,它允许一组线程相互等待以达到共同的障碍点。 CyclicBarriers 在涉及固定大小的线程组的程序中很有用,这些线程组必须偶尔相互等待。 屏障被称为循环的,因为它可以在等待线程被释放后重新使用。

在这种情况下,屏障被设置为有两个同步方 - 写入器和读取器:

  • Writer首先写入它的值,然后等待各方到达屏障(换句话说,它等待Reader读取值)。
  • Reader 首先等待各方到达屏障(换句话说,它等待 Writer 写入新值),然后才读取该值。

在此方案中, count数值的可见性由CyclicBarrier强制执行,因此您甚至不需要AtomicInteger 进一步来说:

调用await()之前线程中的操作发生在从其他线程中的相应await()成功返回之后的 [...] 操作。

哦, isStopped也不需要AtomicBoolean ,一个volatile就足够了。 但它会以任何一种方式工作。 抱歉,我知道这应该是练习原子的任务,但如果您需要线程相互等待,它们不是一个好工具。


脚注:当您删除sleep()调用时,上述机制仍然不完全正确。 原因是一旦发布,Reader 会在下一次循环迭代中与 Writer 竞争。 要解决这个问题,Writer 必须等待前一个 Reader 完成,而 Reader 必须等待其 Writer 完成。 这可以通过使用第二个屏障或者可能是我在上面的示例中故意没有使用的Phaser来实现,因为它更高级,您需要在继续使用 Phasers 之前学习 CyclicBarriers 和 CountDownLatches。 还需要调整关闭机制。 祝你好运!

编辑:我实际上编写了 no- sleep()双相位器解决方案,发现它更容易阅读(如果您不关心通常应该的长时间运行任务中断!)并且比等效的要快得多CyclicBarrier 解决方案。 所以我们今天都学到了一些东西。 这里是:

public class AutomicTest {

    private volatile boolean isStopped = false;

    private final Phaser valueWritten = new Phaser(2);
    private final Phaser valueRead = new Phaser(2);
    private int count = 0;

    public static void main(String[] args) throws InterruptedException {
        AutomicTest test = new AutomicTest();

        Thread writerThread = new Thread(() -> {
            while (!test.isStopped) {
                // wait for the previous value to be read
                test.valueRead.arriveAndAwaitAdvance();
                test.count++;
                // acknowledge the write
                test.valueWritten.arrive();
            }
        });

        Thread readerThread = new Thread(() -> {
            while (!test.isStopped) {
                // wait for the value to be written
                test.valueWritten.arriveAndAwaitAdvance();
                System.out.println("Counter: " + test.count);
                // acknowledge the read
                test.valueRead.arrive();
            }
        });

        writerThread.start();
        readerThread.start();
        test.valueRead.arrive(); // start the writer

        Thread.sleep(4000);

        test.isStopped = true;
        test.valueRead.forceTermination();
        test.valueWritten.forceTermination();

        writerThread.join();
        readerThread.join();
    }

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM