简而言之,来自java
在Java中,进程中的所有Java应用程序线程都有自己的 堆栈(和局部变量),但共享一个堆。这样做 非常容易在线程之间共享对象,因为所需要的只是 将引用从一个线程传递给另一个线程。
这导致了Java的一般设计原则 - 对象 默认情况下可见。如果我有一个对象的引用,我可以复制 把它交给另一个没有限制的线程。一个Java reference本质上是一个指向内存中位置的类型指针 线程共享相同的地址空间,默认情况下可见 是一种自然的模式。
来自Java Concurrency in Practice
可见性是微妙的,因为可能出错的事情就是如此 有悖常理。在单线程环境中,如果你写一个 值变量后来读取该变量而没有干预 写道,你可以期望得到相同的价值。这似乎只是 自然。它可能很难在第一次接受,但是当读取和 写入发生在不同的线程中,事实并非如此。在 一般, 无法保证阅读主题会及时看到另一个帖子写的值,甚至根本不会。 In 为了确保跨线程的内存写入的可见性,您必须 使用同步。
当线程在没有同步的情况下读取变量时,它可能会看到陈旧的值。
那么为什么Java在Nutshell中说对象对所有线程都是可见的,而Java Concurrency in Practice说不能保证读取线程能够及时看到另一个线程写入的值?他们看起来并不一致。
感谢。
答案 0 :(得分:1)
“那么为什么Java在Nutshell中说所有线程都可以看到对象” - > 正如你的引言所说,在Java中,对象是在堆上分配的。可用于整个JVM的“全局”堆。而在其他语言(例如C ++)中,也可以在堆栈上分配对象。堆上的对象可以使用不同的堆栈传递给其他线程。堆栈上的对象只能在使用相同堆栈的线程上使用,因为堆栈的内容将更改为超出另一个线程的控制。
“虽然Java Concurrency in Practice说不能保证读取线程能够及时看到另一个线程写入的值吗?” - >这是另一个问题,因为这是关于内存位置的值。虽然它们是可访问的编译器,但CPU会尝试优化读取或写入此内存位置,并通过假设“我是唯一一个读取和写入此内存位置”来高速缓存该值。因此,如果一个线程修改了内存位置的值,则另一个线程不知道它已更改并且不会读取它。这使程序更快。通过声明变量volatile
,您告诉编译器另一个线程可以随意更改该值,编译器将使用它来创建不缓存该值的代码。
最后,多线程比添加volatile
要困难得多,或者使用synchronized
,在使用多个问题时,我们需要深入研究您将会遇到的问题主题线程。
答案 1 :(得分:0)
在Java中,进程中的所有Java应用程序线程都有自己的 堆栈(和局部变量)但共享一个堆。这样做 很容易在线程之间共享对象,因为所需要的只是 将引用从一个线程传递给另一个线程。
这导致Java的一般设计原则 - 对象是 默认可见。
我认为这些陈述是完全正确的......但它们具有误导性,因为它们并没有传达全部真相。例如,当作者说" ...默认情况下可见对象时,作者的意思是什么。"
在Java JVM上执行的任何线程都没有事实上对JVM堆上所有对象的可见性。如果我们将可见性定义为"通过引用" 进行访问的能力,那么线程只能看到对象:
实际上,Java并发编程中一个重要且常用的线程安全策略是线程限制。如果一个线程持有对只有它有访问权限但没有发布到任何其他线程的对象的引用,那么该对象是线程安全的。该对象可以通过其被限制的线程安全地进行变异,而不需要进一步考虑可见性和原子性......只要它被正确地限制在线程中。
换句话说,一个线程受限的对象,无论它在JVM堆上的什么位置,由于不可访问而在同一个JVM上运行的任何其他线程都不可见。
由于共享对象存储在线程共享的堆中,为什么呢 某些线程可能看不到其他线程的最新值?
在这个多核处理器时代,运行JVM的每个CPU都有自己的本地缓存内存级别,而其他核心无法看到。这就解释了为什么写入一个线程中的变量的值不能保证对另一个线程可见的原因:Java内存模型不保证当一个线程写入的值对其他线程可见时,因为它没有指定何时缓存值将从缓存写回到内存中。
事实上,当许多线程访问这些值时,值的非同步访问通常是陈旧的(或不一致的)。根据正在发生的状态转换,许多线程可能访问相同值的并发环境中的线程安全性可能需要:
以实现允许程序正确的线程安全策略。