协调两个线程时的并发问题

Concurrency issue when coordinating two threads

我试图将一个循环的工作分成两个线程。

我正在使用 ExecutorService 创建第二个线程,同时将主线程用作另一个线程。

我正在使用计数器在达到某个值时停止循环,但我无法在这两个线程之间同步计数器并且循环是 运行 一个额外的时间。

此外,我在主线程中使用 while 循环来了解所需计数何时达到打印结束时间,这也不起作用。

ExecutorService executorService = Executors.newFixedThreadPool(1);
    
    Clock clock = new Clock();

    int count = 5;
    AtomicInteger c = new AtomicInteger(1); 
    clock.start();
    
    executorService.execute(()->{
        while (c.get() <= count) {
            
            System.out.println("LOOP ONE" + "----PRINT_ME");
            c.incrementAndGet();
        }
    });
    
    while (c.get() <= count) {
        
        System.out.println("LOOP TWO" + "----PRINT_ME");
        c.incrementAndGet();
    }
    
    while(true) {
        if(c.get() == count) {
            System.out.println("STOPPED");
            clock.stop();
            break;
        }
    }

输出-

Clock started at -- 2020-07-25T12:32:59.267
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME

在上面的代码中,PRINT_ME 应该只打印 5 次(int count = 5;)但是却打印了 6 次(额外一次)。

我不确定这里发生了什么以及我们如何在两个线程之间共享计数。

PSI:如果我添加 Thread.sleep(2000);在两个循环中

executorService.execute(()->{
        while (c.get() <= count) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("LOOP ONE" + "----PRINT_ME");
            c.incrementAndGet();
        }
    });
    
    while (c.get() <= count) {
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("LOOP TWO" + "----PRINT_ME");
        c.incrementAndGet();
    }

输出是:

Clock started at -- 2020-07-25T12:54:15.596
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME

问题是您先轮询计数器,执行操作,然后递增计数器,表明作业已完成。

这会创建一个 window,其中两个线程可以同时执行工作并在完成后执行递增,但为时已晚。

为了更容易形象化,让我们假设所需的计数是 1,并且有两个线程在争抢要执行的任务。在您的实现中,两个线程都将从检查 c.get() <= count 开始,这对两者都是 true,然后执行导致您描述的问题的任务。

为了防止这种情况,线程需要先申请一个“空闲槽”,然后才执行任务。这可以通过在作业开始之前递增计数器来执行,然后在开始每个作业之前验证最大 count 条件:

    int count = 5;
    AtomicInteger c = new AtomicInteger(0);

    executorService.execute(()->{
        while (c.incrementAndGet() <= count) {
            // ...
    });

    while (c.incrementAndGet() <= count) {
            // ..
    }