【问题标题】:Java volatile and synchronizedJava volatile 和 synchronized
【发布时间】:2015-07-18 11:17:37
【问题描述】:
  1. 我知道 volatile 关键字会刷新所有不可见数据,即,如果某个线程读取 volatile 变量,所有潜在的不可见变量/引用(不​​仅是要读取的变量)在之后都会正常(可见,即具有正确的值)这个阅读。正确的?但是同步呢?是一样的吗?例如,如果在同步块中我们读取 3 个变量,那么所有其他变量都会可见吗?

  2. 如果一个线程从非同步块中更改某个变量的值(例如将变量“年龄”从 2 设置为 33)并且在该线程死亡之后会发生什么?值可以写入线程栈,但是主线程可能看不到这个变化,后台线程会死掉,新的值没有了,无法取回?

  3. 1234563并且在那之后将继续,我们如何确保所有变量更改(由后台线程进行)将对主线程可见?我们可以在后台线程完成后放置同步块还是?我们不想每次在线程死后都使用同步块访问从后台线程更改的变量(因为它是开销),但是我们需要它们的正确值?但是读取一些假的 volatile 变量或使用假的同步块(如果它刷新所有数据)只是为了刷新所有数据是不自然的。

我希望我的问题得到很好的解释。 提前致谢。

【问题讨论】:

    标签: java multithreading concurrency synchronized volatile


    【解决方案1】:

    读取 volatile 变量的值会在一个线程的写入和另一个线程的读取之间创建发生前的关系。

    http://jeremymanson.blogspot.co.uk/2008/11/what-volatile-means-in-java.html:

    Java volatile 修饰符是一种特殊机制的示例 保证线程之间发生通信。当一个线程 写入一个 volatile 变量,另一个线程看到该写入, 第一个线程告诉第二个关于内存的所有内容 直到它执行对该 volatile 变量的写入。

    同步块也创建了happens-before关系。见http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html

    监视器的解锁(同步块或方法退出) 发生在每个后续锁之前(同步块或方法 同一个监视器的条目)。因为happens-before关系 是传递的,线程在解锁之前的所有动作 发生在任何线程锁定之后的所有操作之前 监视器。

    这对可见性有相同的影响。

    如果在没有任何同步的情况下写入值,则无法保证其他线程会看到该值。如果你想跨线程共享值,你应该添加某种同步或锁定,或者使用 volatile。

    【讨论】:

      【解决方案2】:

      the java.util.concurrent package documentation 解答了您的所有问题。

      1. 但是同步呢?

      文档是这样说的:

      监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁定(同步块或方法入口)之前。并且由于happens-before关系是可传递的,因此线程在解锁之前的所有动作都发生在任何线程锁定该监视器之后的所有动作之前。

      .

      1. 值可以写入线程栈,但是主线程可能看不到这个变化,后台线程会死掉,新的值没有了,无法取回?

      如果值被写入线程堆栈,那么我们正在谈论一个局部变量。局部变量在任何地方都不可访问,除非在声明该变量的方法中。所以如果线程死了,当然堆栈不存在,局部变量也不存在。如果您谈论的是对象的字段,则该字段存储在堆中。如果没有其他线程引用该对象,并且该引用无法从任何静态变量中访问,那么它将被垃圾回收。

      1. 我们的线程将等待它们完成工作并在此之后继续,我们如何确保所有变量更改(由后台线程进行)对主线程可见

      文档说:

      线程中的所有操作都发生在任何其他线程从该线程的连接成功返回之前。

      所以,由于主线程等待后台线程死亡,所以它使用join(),并且后台线程所做的每一个动作都会在join()返回后被主线程看到。

      【讨论】:

        【解决方案3】:

        从您的问题似乎暗示的层面全面涵盖整个主题需要的不仅仅是 StackOverflow.com 的答案,因此我建议寻找一本关于多线程编程的好书

        volatile 保证对限定变量的读写访问相对于对同一volatile 变量的其他访问是完全有序的1
        它通过防止volatile 读写访问与先前或将来的指令重新排序并强制写入线程访问之前的所有副作用对读取线程可见。

        这意味着 volatile 变量的读取和写入就像您在代码中看到的那样,就像指令一次执行一个一样,只有在前一个的所有副作用都完成并且对每个其他线程可见时才开始下一个.

        为了更好地理解这意味着什么以及为什么这是必要的,请查看我在difference between Memory Barriers and lock prefixed instruction 上提出的这个问题。

        注意,Java 中的 volatile比 C 或 C++ 中的 volatile 强得多。它确实比通常将读/写访问视为优化目的的副作用更能保证。这意味着这并不意味着每次都从内存中读取变量,Java volatile 是内存屏障。

        synchronized 块只是保证一个代码块的独占(即一次一个线程)执行。
        这并不意味着所有线程看到的内存访问都是相同的顺序,一个线程可以先看到对受保护的共享数据结构的写入,然后再看到对锁的写入!

        1 在某些情况下,如果发出完整的内存屏障,这可能会强制线程 T 对所有 volatile 变量的写入和读取将对 T 中的其他线程可见节目顺序。请注意,这不足以进行同步,因为线程间访问之间仍然没有关系。


        1. 没有。

        2. 共享变量不在任何线程的堆栈上,它们的值可以复制,但变量独立于任何线程存在。
          当一个线程优雅地终止时,写入完成并且可以被检索(再次注意内存排序)。
          如果线程被强制终止,它可能会在任何地方被中断。无论如何,如果线程在写入阴影变量之前停止(通过直接分配或通过复制堆栈上的本地值),则实际的 Java 分配被实现,那么写入永远不会发生。

        3. 您不需要synchronized,只需volatile,因为主线程只读取,后台线程只写入(不同的变量)。

        【讨论】:

        • 你能给我一本并发编程的书吗? “实践中的 Java 并发”是否足够好?我现在正在尝试阅读它,但它对我来说不是轻量级的。感谢您的回答和关注。
        • @DPM 应该可以,等等! :)
        • 谢谢!关于为 volatile 编写的 - “当一个线程写入 volatile 变量,而另一个线程看到该写入时,第一个线程告诉第二个线程所有内存内容,直到它执行对该 volatile 变量的写入”,这个意味着所有变量都将被“刷新”,即使用阅读器线程的真实值。 “刷新”所有这些不是很大的开销,不仅是声明为 volatile 的变量。提前致谢。
        • @DPM 这与 CPU 的工作方式有关。这意味着当写入指令尚未完成时,CPU 不能过早启动读取指令(是的,它可以在其“正确”时间之前启动指令)。这也意味着技术上称为“写入传播”,即如果写入是在某个 CPU 内部缓存中完成的,它们必须传播到所有其他 CPU 可见的内存。然而,这是由硬件以增量和异步方式完成的,因此该语句再次被解读为:“传播过程必须在开始读取之前完成”。
        • 首先,对volatile 变量的保证仅适用于同一变量的写入和后续读取。其次,当synchronized 块使用相同的对象作为监视器时,它们也有同样的保证。然后,并且只有那时,一个线程在离开synchronized 块之前所做的所有动作happens-before 在进入synchronized 块之后另一个线程所做的动作。这不仅仅是关于 CPU 的工作方式(它们的工作方式不同),而是 JVM 的优化器可以对您的代码做什么。
        猜你喜欢
        • 2011-03-31
        • 2013-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-08
        • 1970-01-01
        相关资源
        最近更新 更多