Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/379.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java是缓存整个对象还是只缓存对象的一部分?(可见性问题)_Java_Multithreading_Caching_Concurrency_Thread Safety - Fatal编程技术网

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

Java是缓存整个对象还是只缓存对象的一部分?(可见性问题),java,multithreading,caching,concurrency,thread-safety,Java,Multithreading,Caching,Concurrency,Thread Safety,我试图故意在线程中创建可见性问题,但得到了意想不到的结果: public class DownloadStatus { private int totalBytes; private boolean isDone; public void increment() { totalBytes++; } public int getTotalBytes() { return totalBytes; } pub

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

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;
    }
}
因此,运行此操作将产生可见性问题-第二个线程将看不到更新的值,因为它在第一个线程写回之前缓存了更新的值-这会导致无休止的(while)循环,第二个线程会不断检查缓存的isDone()。(至少我认为它是这样工作的)

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

为什么此行调用status对象中的方法会导致此可见性问题?(更有趣的是,为什么不称之为修复问题呢?

你所谓的“可见性问题”实际上是一场数据竞赛

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

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


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

这是两个线程之间的争用条件。代码中的
status.getTotalBytes()
语句与此无关。由调度程序决定运行哪个线程。在对println语句进行注释之后,您碰巧没有陷入无限循环。代码中的主要问题是增量和设置状态应该是原子操作,并替换run方法的定义,如下所示。第二,增量也不是原子操作。如果没有正确的同步,可能会出现不可预知的结果

@Override
    public void run() {
        System.out.println("start download");
        incrementAndSetStatus();
    }

    public synchronized  void  incrementAndSetStatus(){
        for (int i = 0; i < 100000; 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();
    }
@覆盖
公开募捐{
System.out.println(“开始下载”);
递增和设置状态();
}
public synchronized void incrementAndSetStatus(){
对于(inti=0;i<100000;i++){//“下载”一个10000字节的文件,每次运行
status.increment();//下载的每个字节-更新状态
}
System.out.println(“下载以“+status.getTotalBytes()”结束”);//**注意这一行**
状态。完成();
}

Re,“…两个线程都从缓存状态对象开始…”Java不缓存任何内容。如果有任何缓存正在进行(在任何比信用卡大的设备上,在一些更小的设备上,都会有缓存),那么是硬件在进行缓存,而硬件不知道一个“对象”在内存中的什么位置结束,下一个开始。另外,测试以发现内存可见性问题、竞争条件,等等是有问题的。如果您遵守所有规则,那么您的程序应该正常运行。但是如果你违反了规则,就不能保证你的程序会有问题。最糟糕的情况是,当你构建一个系统,测试****后,将其交付给客户,然后六个月后,每个人都得到了操作系统升级或其他东西,而在整个测试过程中潜伏在你的产品中的“内存可见性”问题突然变成了一件大事。(不要问我怎么知道!)事实上,
System.out.println()
是这些东西中“引起混乱的主要原因”,因为
PrintStream.println()
已经
同步了(这个)
。谢谢!是什么原因使您选择在设置isDone字段时添加
同步
?增量操作不是原子操作。随着完成,增量可能是竞争条件。
//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();
}
@Override
    public void run() {
        System.out.println("start download");
        incrementAndSetStatus();
    }

    public synchronized  void  incrementAndSetStatus(){
        for (int i = 0; i < 100000; 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();
    }