首页 > 解决方案 > 为什么 CyclicBarrier 在我的程序中并不总是按预期工作?

问题描述

我做了一个简单的程序来计算矩阵中的行总和。我想同时这样做,所以我使用了 CyclicBarrier。有时它会按预期工作,但有时会出现小错误,就像程序错过了一个或两个数字一样。

这是我的代码:

package synchronizacja;
import java.util.concurrent.CyclicBarrier;
public class SumyWierszamiSekwencyjnie {
private static final int ROWS = 10;
private static final int COLUMNS = 100;
private static  int sum;
private static CyclicBarrier barrier = new CyclicBarrier(COLUMNS, new Tmp());

private static class Tmp implements Runnable {
    @Override
    public void run() {
        System.out.println("The sum is: " + sum);
        sum = 0;
    }
}

private static class CountByColumns implements Runnable {
    private void CriticalSection(int wiersz) {
        sum += wiersz;
    }
    @Override
    public void run() {
        for (int i = 0; i < ROWS; i++) {
           CriticalSection(i);
           try { barrier.await(); }
           catch (Exception e) { System.out.println("Exception"); }
        }

    }
}

public static void main(String[] args) {
    for (int i = 0; i < COLUMNS; i++) {
        new Thread(new CountByColumns()).start();
    }
}
}

输出应该是

The sum is: 0
The sum is: 100
The sum is: 200
The sum is: 300
The sum is: 400
The sum is: 500
The sum is: 600
The sum is: 700
The sum is: 800
The sum is: 900

有时它是,但更多时候它会显示类似的东西

The sum is: 0
The sum is: 100
The sum is: 200
The sum is: 297
The sum is: 400
The sum is: 500
The sum is: 600
The sum is: 700
The sum is: 800
The sum is: 900

为什么会这样?我在想这可能是因为 Tmp 类中的 run() 不必是原子的,所以当前线程可能会在将 sum 设置为 0 之前跳到计算另一行的总和。如果是这样,我该如何防止这种情况?

标签: concurrencyjava.util.concurrent

解决方案


我在想这可能是因为 Tmp 类中的 run() 不必是原子的,所以当前线程可能会在将 sum 设置为 0 之前跳到计算另一行的总和。

我不这么认为,CB.await 的 javadoc指定

如果当前线程是最后到达的线程,并且在构造函数中提供了非空屏障动作,则当前线程在允许其他线程继续之前运行该动作。

有两个问题,第一个是潜在的多核可见性执行屏障操作的线程可能还没有看到 sum 的变化,因为它没有在核心本地缓存中更新。此外, += 运算符不是原子的,因此它可能会从 sum 中读取 20 并将其“2”添加到其中,并将 22 写入 sum,即使另一个线程在读取和写入之间将值更改为 23。要解决这两个问题,您需要使用 AtomicInteger 或 Varhandles (volatile 仅修复可见性问题。这里是有关原子变量的更多信息


推荐阅读