2008-12-12 61 views
28

多年来,我试着了解处理内存模型和并发性的Java规范的part。我不得不承认我失败了。是的'我了解锁和“同步”,并等待()和通知()。我可以很好地使用它们,谢谢。我甚至对“易变”的含义有一个模糊的概念。但所有这些都不是来自语言规范 - 而是来自一般经验。Java内存模型 - 有人可以解释它吗?

这是我问的两个示例问题。我对特定的答案不太感兴趣,因为我需要了解答案是如何从规范中派生的(或者可能是我如何得出规范没有答案的结论)。

  • 什么是“volatile”呢?
  • 写入可变原子?它依赖于变量的类型吗?

回答

33

我不会尝试在这里实际回答你的问题 - 相反,我会将你重定向到我看到的关于此主题的建议的书籍:Java Concurrency in Practice

一个字的警告:如果有在这里的答案,预计其中不少人会是错的。我不打算发布详细信息的原因之一是因为我非常确定我会至少在某些方面出错。我的意思是,当我说所有认为他们能够回答这个问题的人确实有足够的严格把握时机的几率几乎为零时,他们对社区没有任何不尊重。 (乔·达菲最近发现有些是很惊讶的.NET存储模式。如果他能得到它错了,所以凡人能像我们一样。)


我将提供只是一个方面的一些见解,因为它经常被误解:

波动性和原子性之间存在差异。人们经常认为原子写入是不稳定的(即,如果写入是原子的,则不需要担心内存模型)。这不是真的。

波动性是关于一个线程执行读操作(逻辑上,在源代码中)是否会“看到”另一个线程所做的更改。

原子性是关于是否有任何机会,如果看到变化,只会看到部分变化。

例如,写入一个整数字段。这是保证是原子的,但不易变。这意味着,如果我们有(开始于foo.x = 0):

Thread 1: foo.x = 257; 
Thread 2: int y = foo.x; 

有可能为y为0或者257。这不会是任何其它值(例如256或1)由于原子性约束。但是,即使你知道在线程2中的代码在线程2中的代码执行后,可能会出现奇怪的缓存,内存访问“正在移动”等。使变量x易失性将解决此问题。

我会把其余的部分留给真正的诚实专家。

7

我不会试着在这里解释这些问题,而是请您参考Brian Goetz关于这个主题的优秀书。

这本书是“实践中的Java并发性”,可以在Amazon或任何其他排序良好的计算机文献商店中找到。

13
  • volatile变量可以被缓存线程局部,所以不同的线程可能会看到在同一时间不同的值;写入32位或更小的变量时保证为原子的(implied here);没有那么对于longdouble,虽然64位的JVM可能实现它们的原子操作
4

这是一个很好的链接,可以给你一点点深入的信息:

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

+0

感谢您的链接!它不会取代一本书(我会得到),但它确实给了我一个洞见:我习惯于考虑同步,而内存模型更多地关注重新排序。我需要学会区分这两者,并想到最后一个。 – Arkadiy 2008-12-12 18:40:04

0

一个概念可能是有用的:数据(数据)和副本。

如果你声明一个变量,让我们说一个字节,它驻留在内存中的某个地方,在一个数据段(粗略地说)。内存中有8位用于存储该信息。

但是,可能会有多个数据副本,在您的机器中移动。由于各种技术原因,例如线程的本地存储,编译器优化。如果我们有多个副本,它们可能不同步。

所以你应该始终记住这个概念。它不仅适用于java类字段,而且适用于cpp变量,数据库记录(记录状态数据被复制到多个会话等中)。变量,其隐藏/可见副本以及细微的同步问题将永远存在。

3

我最近发现an excellent article解释挥发性为:

首先,你要了解一点关于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

+1

这不是N,它是N + 1!看到我自己的答案:) – Arkadiy 2012-08-17 12:30:07

1

其他答案以上是完全正确的,你的问题是不适合心脏佯攻。然而,我理解你真正想要得到的东西的痛苦 - 因为我希望你回到java的世界编译器和低层前辈 - 即汇编,C和C++。

阅读有关不同种类的障碍('栅栏')。了解什么是内存屏障,以及它在何处是必要的,将帮助您直观地掌握volatile的作用。

0

另一个试图提供我从这里和其他来源的答案中得到的东西的总结(第一次尝试离基地很远,我希望这个更好)。

Java内存模型是关于将一​​个线程中写入内存的值传播给其他线程,以便其他线程在从内存中读取时可以看到它们。

总之,如果你获得一个互斥锁,任何释放该互斥锁的线程所写的任何东西都将对你的线程可见。

如果您读取一个易失性变量,在读取它之前写入该易失性变量的任何内容对于读取线程都是可见的。此外,写入变量之前写入变量的线程完成的任何易失性变量写入都是可见的。而且,在Java 1.5中,任何写操作都是可变的或不可变的,在写入volatile变量之前写入到volatile变量的任何线程上发生的任何写操作都将对您可见。

构建对象后,可以将其传递给另一个线程,并且所有最终成员都将在新线程中可见并完全构建。对非最终成员没有类似的保证。这使我认为赋予最终成员充当了写入volatile变量(内存围栏)。

线程在Runnable退出之前写入的任何内容对执行join()的线程都是可见的。线程在执行start()之前写入的任何内容都可以在生成的线程中看到。

另一件要提到的事情是:volatile变量和同步有一个很少提及的函数:除了刷新线程缓存并提供一次一个线程访问外,它们还可以防止编译器和CPU重新排列同步读写边界。

没有一个是新的,其他答案都表明它更好。我只是想写这个来清理我的头。