Java内存模型 – 有人可以解释一下吗?
多年来,我一直试图理解Java规范中涉及内存模型和并发的部分。 我不得不承认我惨遭失败。 是的’我理解锁和“synchronized”以及wait()和notify()。 我可以使用它们,谢谢。 我甚至对“易变”的含义有一个模糊的想法。 但所有这些都不是源于语言规范 – 而是来自一般经验。
以下是我要问的两个示例问题。 我对特定答案并不是那么感兴趣,因为我需要理解答案是如何从规范中得出的(或者可能是我如何得出规范没有答案)。
- “挥发性”究竟做了什么?
- 写入变量primefaces? 它取决于变量的类型吗?
我不打算在这里尝试真正回答你的问题 – 相反,我会将你重定向到我看到推荐的关于这个主题的建议书: Java Concurrency in Practice 。
一句警告:如果这里有答案,可能会有很多错误。 我不打算发布细节的原因之一是因为我很确定至少在某些方面我会弄错。 我的意思是,当我说每个认为自己能够回答这个问题的人实际上有足够严谨来做到正确的时候几乎没有任何不尊重的社群。 (Joe Duffy最近发现了一些令人惊讶的.NET内存模型。如果他能弄错,那么凡人就像我们一样。)
我将在一个方面提供一些见解,因为它经常被误解:
波动性和primefaces性之间存在差异。 人们通常认为primefaces写是易失性的(即如果写是primefaces的,你不需要担心内存模型)。 这不是真的。
波动性是指一个执行读取的线程(逻辑上,在源代码中)是否“看到”另一个线程所做的更改。
primefaces性是关于是否有机会如果看到变化,只会看到部分变化。
例如,写入整数字段。 这保证是primefaces的,但不是易变的。 这意味着如果我们有(从foo.x = 0开始):
Thread 1: foo.x = 257; Thread 2: int y = foo.x;
y
可能为0或257.由于primefaces性约束,它不会是任何其他值(例如256或1)。 但是,即使您知道在“挂起时间”中线程2中的代码在线程1中的代码之后执行,也可能存在奇数缓存,内存访问“移动”等。使变量x
volatile将解决此问题。
我将把剩下的工作留给真正诚实的善良专家。
- 非易失
volatile
变量可以在线程本地缓存,因此不同的线程可以同时看到不同的值;volatile
阻止这个( 来源 ) - 写入32位或更小的变量保证是primefaces的( 这里隐含 ); 虽然64位JVM可能将它们实现为primefaces操作,但对于
long
和double
并非如此
我不会尝试在这里解释这些问题,而是向您推荐Brian Goetz关于此主题的优秀书籍。
这本书是“Java Concurrency in Practice”,可以在亚马逊或任何其他排序良好的商店中找到计算机文献。
这是一个很好的链接,可以为您提供一些深入的信息:
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
我最近发现了一篇很好的文章 ,解释了volatile:
首先,您必须了解Java内存模型的一些内容。 多年来我一直在努力解释一下这个问题。 截至今天,我能想到描述它的最佳方式是你是这样想象的:
Java中的每个线程都发生在一个单独的内存空间中(这显然是不真实的,所以请耐心等待这一点)。
您需要使用特殊机制来保证在这些线程之间进行通信,就像在消息传递系统上一样。
在一个线程中发生的内存写入可以“泄漏”并被另一个线程看到,但这绝不是保证。 如果没有明确的通信,您无法保证其他线程可以看到哪些写入,甚至无法保证看到它们的顺序。
Java volatile修饰符是保证线程之间进行通信的特殊机制的示例。 当一个线程写入一个volatile变量,而另一个线程看到该写入时,第一个线程告诉第二个线程关于内存的所有内容,直到它执行对该volatile变量的写入。
其他链接: http : //jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html http://www.javaperformancetuning.com/news/qotm030.shtml
上面的其他答案绝对正确,因为你的问题不是为了佯装。
但是,我理解你真正想要获得内幕的痛苦 – 为此我会指出你回到世界编译器和Java的低级前辈 – 即汇编,C和C ++。
阅读不同类型的障碍(’围栏’)。 了解内存障碍是什么以及在何处需要,将帮助您直观地了解volatile的作用。
一个概念可能会有所帮助:数据(数据)和副本。
如果你声明一个变量,让我们说一个字节,它驻留在内存中的某个地方,在数据段中(粗略地说)。 内存中有8位用于存储该信息。
但是,可以在您的计算机中移动数据的多个副本。 由于各种技术原因,例如线程的本地存储,编译器优化。 如果我们有多个副本,它们可能会不同步。
所以你应该始终牢记这一概念。 这不仅适用于java类字段,也适用于cpp变量,数据库记录(记录状态数据被复制到多个会话等)。 变量,隐藏/可见副本以及微妙的同步问题将永远存在。
另一个尝试从这里和其他来源的答案中提供我理解的事物的总结(第一次尝试相当远离基础。我希望这个更好)。
Java内存模型是将在一个线程中写入内存的值传播到其他线程,以便其他线程在从内存中读取时可以看到它们。
简而言之,如果您获得了对互斥锁的锁定,那么之前释放该互斥锁的任何线程所写的任何内容都将对您的线程可见。
如果您读取了一个volatile变量,那么在读取它之前写入该volatile变量的任何内容都会被读取线程看到。 此外,在写入变量之前写入变量的线程所做的任何对volatile变量的写操作都是可见的。 此外,在Java 1.5中,任何写入,无论是volatile还是not,都发生在写入volatile变量之前写入volatile变量的任何线程上。
构造一个对象后,您可以将它传递给另一个线程,并且所有最终成员将在新线程中可见并完全构造。 非最终成员没有类似的保证。 这让我认为对最终成员的赋值充当对volatile变量(内存栅栏)的写入。
线程在Runnable退出之前编写的任何内容都对执行join()的线程可见。 执行start()之前线程写的任何内容都将对生成的线程可见。
另外需要提及的是:volatile变量和同步具有很少提及的function:除了刷新线程缓存并提供一次一个线程访问外,它们还阻止编译器和CPU在同步边界上重新排序读取和写入。
这些都不是新的,其他答案都说得更好。 我只想写清楚我的头脑。