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