Java 不稳定和同步
Java volatile and synchronized
我知道 volatile 关键字刷新所有不可见数据,即如果某个线程读取 volatile 变量,所有潜在的不可见 variables/references(不仅是将要读取的变量)将是好的(可见即具有正确的值)在阅读之后。正确的?但是 synchronized 呢?是一样的吗?例如,如果在同步块中我们读取 3 个变量,所有其他变量是否可见?
如果一个线程从非同步块更改某个变量的值(例如将变量 "age" 从 2 设置为 33)并且在该线程死后会发生什么?该值可以写入线程堆栈,但主线程可能不会看到此更改,后台线程将死亡并且新值已消失且无法检索?
最后一个问题,如果我们有 2 个后台线程,并且我们知道我们的主线程将在它们中的每一个都死掉之前(以某种方式)得到通知,并且我们的线程将等待它们完成他们的工作并在此之后继续,我们如何确保所有变量更改(由后台线程进行)对主线程可见?我们可以在后台线程完成后放置同步块还是?我们不希望每次在该线程死后都使用同步块访问从后台线程更改的变量(因为这是开销),但我们需要拥有正确的值?但是读取一些伪造的volatile变量或使用伪造的同步块(如果它刷新所有数据)只是为了刷新所有数据是不自然的。
我希望我的问题得到很好的解释。
提前致谢。
读取 volatile 变量的值会在一个线程的写入和另一个线程的读取之间创建一种先行关系。
参见http://jeremymanson.blogspot.co.uk/2008/11/what-volatile-means-in-java.html:
The Java volatile modifier is an example of a special mechanism to
guarantee that communication happens between threads. When one thread
writes to a volatile variable, and another thread sees that write, the
first thread is telling the second about all of the contents of memory
up until it performed the write to that volatile variable.
同步块也会创建先行关系。参见 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html
An unlock (synchronized block or method exit) of a monitor
happens-before every subsequent lock (synchronized block or method
entry) of that same monitor. And because the happens-before relation
is transitive, all actions of a thread prior to unlocking
happen-before all actions subsequent to any thread locking that
monitor.
这对可见性有相同的影响。
如果在没有任何类型的同步的情况下写入值,则无法保证其他线程会看到该值。如果你想跨线程共享值,你应该添加某种同步或锁定,或者使用 volatile。
从你的问题似乎暗示的层面完全涵盖整个主题需要的不仅仅是 whosebug.com 答案,所以 我建议找一本关于多线程编程的好书.
volatile
保证对限定变量的读写访问相对于对同一volatile
变量1 的其他访问是完全有序的。
它通过防止 volatile
读取和写入访问与先前或未来的指令重新排序并强制执行写入线程访问之前的所有副作用对读取线程可见来实现这一点。
这意味着 volatile 变量的读取和写入就像您在代码中看到的那样,就像一次执行一个指令一样,仅当前一个的所有副作用完成并且每个人都可见时才开始下一个其他线程。
为了更好地理解这意味着什么以及为什么这是必要的,请查看我在 上的这个问题。
注意 Java 中的 volatile
比 volatile
强得多 C 或 C++。它确实比通常将 read/write 访问作为优化目的的副作用处理更多。这意味着这并不简单地意味着每次都从内存中读取变量,Java volatile
是内存屏障。
synchronized
块简单地保证代码块的独占(即一次一个线程)执行。
这并不意味着所有线程看到的内存访问都是相同的顺序,一个线程可以首先看到对受保护的共享数据结构的写入,然后是对锁的写入!
1 在某些情况下,发出完整的内存屏障,这可能会强制写入和读取线程 T 生成的所有易失性变量对 T 程序顺序中的其他线程可见。请注意,这不足以实现同步,因为线程间访问之间仍然没有关系。
没有
共享变量不在任何线程的堆栈上,可以复制它们的值,但变量独立于任何线程存在。
当一个线程优雅地结束时,写入完成并可以被检索(再次注意内存排序)。
如果线程强行终止,它可能在任何地方被中断。无论如何,如果线程在写入阴影 var 之前停止(通过直接分配或通过复制堆栈上的本地值),那么实际的 Java 分配将被实现,然后写入永远不会发生。
你不需要synchronized
,只需要volatile
因为主线程只读而后台线程只写(不同的变量)。
您的所有问题都在 the java.util.concurrent package documentation 中得到了解答。
- But what about synchronized?
文档是这样说的:
An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.
.
- The value could be written in the thread stack, but main thread maybe will not see this change, the background thread will die and the new value is gone and can not be retrieved?
如果值是写在线程堆栈中,那么我们就是在谈论一个局部变量。除了声明该变量的方法之外,局部变量在任何地方都不可访问。所以如果线程死了,当然栈不存在,局部变量也不存在。如果您谈论的是对象的字段,那么它存储在堆中。如果没有其他线程引用该对象,并且该引用无法从任何静态变量访问,那么它将被垃圾回收。
- and our thread will wait both of them to finish their work and will continue after that, how we can assure that all variables changes(which are made by the background threads) will be visible to the main thread
文档说:
All actions in a thread happen-before any other thread successfully returns from a join on that thread.
所以,由于主线程等待后台线程死亡,所以它使用了join(),后台线程所做的每一个动作在join()之后主线程都是可见的returns。
我知道 volatile 关键字刷新所有不可见数据,即如果某个线程读取 volatile 变量,所有潜在的不可见 variables/references(不仅是将要读取的变量)将是好的(可见即具有正确的值)在阅读之后。正确的?但是 synchronized 呢?是一样的吗?例如,如果在同步块中我们读取 3 个变量,所有其他变量是否可见?
如果一个线程从非同步块更改某个变量的值(例如将变量 "age" 从 2 设置为 33)并且在该线程死后会发生什么?该值可以写入线程堆栈,但主线程可能不会看到此更改,后台线程将死亡并且新值已消失且无法检索?
最后一个问题,如果我们有 2 个后台线程,并且我们知道我们的主线程将在它们中的每一个都死掉之前(以某种方式)得到通知,并且我们的线程将等待它们完成他们的工作并在此之后继续,我们如何确保所有变量更改(由后台线程进行)对主线程可见?我们可以在后台线程完成后放置同步块还是?我们不希望每次在该线程死后都使用同步块访问从后台线程更改的变量(因为这是开销),但我们需要拥有正确的值?但是读取一些伪造的volatile变量或使用伪造的同步块(如果它刷新所有数据)只是为了刷新所有数据是不自然的。
我希望我的问题得到很好的解释。 提前致谢。
读取 volatile 变量的值会在一个线程的写入和另一个线程的读取之间创建一种先行关系。
参见http://jeremymanson.blogspot.co.uk/2008/11/what-volatile-means-in-java.html:
The Java volatile modifier is an example of a special mechanism to guarantee that communication happens between threads. When one thread writes to a volatile variable, and another thread sees that write, the first thread is telling the second about all of the contents of memory up until it performed the write to that volatile variable.
同步块也会创建先行关系。参见 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html
An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.
这对可见性有相同的影响。
如果在没有任何类型的同步的情况下写入值,则无法保证其他线程会看到该值。如果你想跨线程共享值,你应该添加某种同步或锁定,或者使用 volatile。
从你的问题似乎暗示的层面完全涵盖整个主题需要的不仅仅是 whosebug.com 答案,所以 我建议找一本关于多线程编程的好书.
volatile
保证对限定变量的读写访问相对于对同一volatile
变量1 的其他访问是完全有序的。
它通过防止 volatile
读取和写入访问与先前或未来的指令重新排序并强制执行写入线程访问之前的所有副作用对读取线程可见来实现这一点。
这意味着 volatile 变量的读取和写入就像您在代码中看到的那样,就像一次执行一个指令一样,仅当前一个的所有副作用完成并且每个人都可见时才开始下一个其他线程。
为了更好地理解这意味着什么以及为什么这是必要的,请查看我在
注意 Java 中的 volatile
比 volatile
强得多 C 或 C++。它确实比通常将 read/write 访问作为优化目的的副作用处理更多。这意味着这并不简单地意味着每次都从内存中读取变量,Java volatile
是内存屏障。
synchronized
块简单地保证代码块的独占(即一次一个线程)执行。
这并不意味着所有线程看到的内存访问都是相同的顺序,一个线程可以首先看到对受保护的共享数据结构的写入,然后是对锁的写入!
1 在某些情况下,发出完整的内存屏障,这可能会强制写入和读取线程 T 生成的所有易失性变量对 T 程序顺序中的其他线程可见。请注意,这不足以实现同步,因为线程间访问之间仍然没有关系。
没有
共享变量不在任何线程的堆栈上,可以复制它们的值,但变量独立于任何线程存在。
当一个线程优雅地结束时,写入完成并可以被检索(再次注意内存排序)。
如果线程强行终止,它可能在任何地方被中断。无论如何,如果线程在写入阴影 var 之前停止(通过直接分配或通过复制堆栈上的本地值),那么实际的 Java 分配将被实现,然后写入永远不会发生。你不需要
synchronized
,只需要volatile
因为主线程只读而后台线程只写(不同的变量)。
您的所有问题都在 the java.util.concurrent package documentation 中得到了解答。
- But what about synchronized?
文档是这样说的:
An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.
.
- The value could be written in the thread stack, but main thread maybe will not see this change, the background thread will die and the new value is gone and can not be retrieved?
如果值是写在线程堆栈中,那么我们就是在谈论一个局部变量。除了声明该变量的方法之外,局部变量在任何地方都不可访问。所以如果线程死了,当然栈不存在,局部变量也不存在。如果您谈论的是对象的字段,那么它存储在堆中。如果没有其他线程引用该对象,并且该引用无法从任何静态变量访问,那么它将被垃圾回收。
- and our thread will wait both of them to finish their work and will continue after that, how we can assure that all variables changes(which are made by the background threads) will be visible to the main thread
文档说:
All actions in a thread happen-before any other thread successfully returns from a join on that thread.
所以,由于主线程等待后台线程死亡,所以它使用了join(),后台线程所做的每一个动作在join()之后主线程都是可见的returns。