java - Java 缓存整个对象还是仅缓存对象的一部分? (可见性问题)

标签 java multithreading caching concurrency thread-safety

我试图故意创建线程的可见性问题,但得到了意想不到的结果:

public class DownloadStatus {
    private int totalBytes;
    private boolean isDone;

    public void increment() {
        totalBytes++;
    }

    public int getTotalBytes() {
        return totalBytes;
    }

    public boolean isDone() {
        return isDone;
    }

    public void done() {
        isDone = true;
    }
}
public class DownloadFileTask implements Runnable {
    DownloadStatus status;

    public DownloadFileTask(DownloadStatus status) {
        this.status = status;
    }

    @Override
    public void run() {
        System.out.println("start download");
        for (int i = 0; i < 10_000; i++) { //"download" a 10,000 bytes file each time you run
            status.increment(); //each byte downloaded - update the status
        }
        System.out.println("download ended with: " + status.getTotalBytes()); //**NOTE THIS LINE**
        status.done();
    }
}
//creating threads, one to download, another to wait for the download to be done.
public static void main(String[] args) {
        DownloadStatus status = new DownloadStatus();

        Thread t1 = new Thread(new DownloadFileTask(status));
        Thread t2 = new Thread(() -> {
            while (!status.isDone()) {}
            System.out.println("DONE!!");
        });

        t1.start();
        t2.start();
}

因此,运行它会产生可见性问题 - 第二个线程不会看到更新的值,因为它在第一个线程写回之前已经缓存了它 - 这会导致无限(while)循环,第二个线程不断检查缓存的 isDone()。 (至少我认为它是这样工作的)。

我不明白的是,为什么当我注释掉第二个代码块中调用 status.getTotalBytes() 的行时,这个可见性问题就不再发生。 根据我的理解,两个线程都是从按原样缓存状态对象开始的,因此第二个线程应该不断检查他的缓存值(并且基本上看不到第一个线程更新的新值)。

为什么此行调用状态对象中的方法会导致此可见性问题? (更有趣的是 - 为什么不调用它来修复它?)

最佳答案

您所说的“可见性问题”实际上是数据竞争。

单个线程按照其写入的顺序查看其操作的效果。也就是说,如果您更新变量然后读取它,您将始终在该线程中看到更新的值。

从另一个线程的角度来看,一个线程的执行效果可能会有所不同。这主要与语言和底层硬件架构有关。编译器可以重新排序指令,延迟存储器写入,同时将值保留在寄存器中,或者可以在写入主存储器之前将值保留在高速缓存中。如果没有显式内存屏障,主内存中的值将不会被更新。这就是您所说的“可见性问题”。

System.println 中可能存在内存障碍。因此,当您执行该行时,截至该点的所有更新都将提交到主内存,并且其他线程可以看到它。请注意,如果没有显式同步,仍然无法保证其他线程会看到它,因为这些线程可能会重复使用它们之前为该变量获取的值。程序中没有任何内容告诉编译器/运行时这些值可能会被其他线程更改。

关于java - Java 缓存整个对象还是仅缓存对象的一部分? (可见性问题),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60762064/

相关文章:

具有 int 值的 JavaFX 绑定(bind)标签

java - Minecraft 服务器清除已从 JAR 插件加载的 .class 文件

Java jTDS 连接问题 Ubuntu 服务器

java - 是否有任何 "threshold"证明多线程计算是合理的?

java - 在线程中调用创建线程的类中的方法

java - 用于基于天的缓存过期的缓存或 MultiMap?

java - Spring Security 中基于角色的访问拒绝处理 - 如何?

java - Java-ExecutorService : How to re run “wait state” thread

java - EhCache Hibernate 二级缓存 maxBytesLocalHeap 慢

node.js - 如何在nodejs中创建一个应用级缓存对象