这种非标准Java同步模式是否有效?

Nay*_*uki 7 java multithreading synchronization java-memory-model happens-before

假设我有两个运行方式:

  • 线程A在更新共享图像的像素的同时执行计算
  • 线程B定期读取图像并将其复制到屏幕上

线程A快速执行工作,比如说每秒100万次更新,所以我怀疑经常在锁/互斥锁/监视器上锁定和解锁是个坏主意.但是如果没有锁定并且无法建立从线程A到线程B的先发生关系,那么通过Java内存模型(JMM规范),线程B根本不能保证看到A对图像的任何更新.

所以我认为最小的解决方案是线程A和B在同一个共享锁上定期同步,但在synchronized块内部实际上不执行任何工作 - 这就是使模式非标准和可疑的原因.用半实半伪代码来说明:

class ComputationCanvas extends java.awt.Canvas {

    private Object lock = new Object();
    private int[] pixels = new int[1000000];

    public ComputationCanvas() {
        new Thread(this::runThreadA).start();
        new Thread(this::runThreadB).start();
    }

    private void runThreadA() {
        while (true) {
            for (1000 steps) {
                update pixels directly
                without synchornization
            }
            synchronized(lock) {}    // Blank
        }
    }

    private void runThreadB() {
        while (true) {
            Thread.sleep(100);
            synchronized(lock) {}    // Blank
            this.repaint();
        }
    }

    @Override
    public void paint(Graphics g) {
        g.drawImage(pixels, 0, 0);
    }
}
Run Code Online (Sandbox Code Playgroud)

以这种方式添加空同步块是否正确地实现了将数据从线程A传输到线程B的效果?还是有其他一些我无法想象的解决方案?

小智 1

是的,它有效。但它的效果很糟糕。

仅当作者的释放发生在读者的获取之前时发生才有效。您的实现假设您正在编写的任何内容都将在后续读取/更新之前完成ThreadB。通过synchronized导致你的数据一直被刷新会导致性能问题,尽管我不能肯定地说到什么程度。当然,您已经使同步变得更细粒度,您测试过了吗?

更好的解决方案可能使用单例/传输 SPSC(单个生产者/单个消费者)队列来存储写入线程的当前快照,并在更新时使用它。

int[] data = ...
Queue<int[]> queue = new ...

// Thread A
while (true) {
    for (1000 iterations or so) {
        ...
    }
    queue.add(data);
}

// Thread B
while (true) {
    int[] snapshot = queue.take(); 
    this.repaint();
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是不需要busywait,只需等待队列阻塞或者直到下一次写入即可。您可以跳过没有时间更新的写入。您不需要依赖任意线程调度程序来为您计划数据刷新。

请记住,线程安全数据结构非常适合在线程之间传递数据。

编辑:哎呀,忘了说,根据更新的进行情况,您可能需要使用数组副本来防止数据因未缓存的随机写入而出现乱码。