java - 这种非标准的 Java 同步模式有效吗?

标签 java multithreading synchronization java-memory-model happens-before

假设我有两个这样运行的线程:

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

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

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

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);
    }
}

这样添加空同步块(synchronized block)是否能正确实现线程A向线程B传输数据的效果?还是有其他我无法想象的解决方案?

最佳答案

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

Happens before 只有当作者的释放发生在读者的获取之前时才有效。您的实现假定您正在编写的任何内容都将在后续从 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();
}

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

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

编辑:糟糕,忘了说根据更新的方式,您可能希望使用数组副本来防止数据因未缓存的随机写入而出现乱码。

关于java - 这种非标准的 Java 同步模式有效吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41928248/

相关文章:

java - 如何在计算期间存储数百万个 Double?

python - 通过使用多进程和多线程的 asyncio 提高 "Sending 100,000 request"的速度

c - unlocked_ioctl 与普通 ioctl

iphone - 构建通过 Web API 与服务器同步数据的 iPhone 应用程序的主要挑战是什么?

sql - Rails 用于创建和更新操作的唯一订单字段

java - 由于 Linux 中不同的挂载卷,文件移动抛出 IOException

java - 拖放 JTableHeader

java - 在 Selenium Webdriver 中循环运行 Java 代码

java - 生产者消费者模式设计问题

java - 多核环境下的数据同步(基于Java)