【问题标题】:JAVA threads (different stacks) synchronizationJAVA线程(不同栈)同步
【发布时间】:2012-01-31 04:01:04
【问题描述】:

我有一个关于由多个线程执行的代码同步的问题:

据我所知,每个线程都有自己的堆栈,因此,非静态变量存在于每个线程的内存中的不同位置(对于 X 个线程,有 X 个堆栈包含所有非静态变量)。 那么为什么需要同步任何东西呢?

我的意思是,如果线程执行的代码包含某个类变量 v1,那么每个线程都有自己的 v1“实例”(不同的内存地址),并且没有其他线程可以“触摸”它……不是是吗?

【问题讨论】:

    标签: java multithreading synchronization thread-safety stack


    【解决方案1】:

    只有基本类型,例如int,才能保证在堆栈上分配。对象和数组通常都存储在堆中,除非Escape Analysis 确定对象的范围是“限制在过程范围内”。

    【讨论】:

    • 实际上,语言规范并没有说明在哪里分配对象,现代 JVM 如果可以的话,在堆栈上分配对象方面做得很好..
    • 我的意思是在Java VM conceptual堆栈上,在分配行为等方面。JVM 可以利用Escape Analysis在堆栈上分配对象如果对象“永远不会逃脱它的基本块”。但我不介意修改我的答案以更好地反映该细节
    【解决方案2】:

    非静态变量存在于每个线程内存的不同位置

    这不是真的,所以回答

    如果线程执行的代码包含一些类变量v1,那么每个线程都有自己的v1“实例”(不同的内存地址),没有其他线程可以“触摸”它......不是这样吗

    没有。线程可以接触到由其他线程分配和修改的对象实例,程序员有责任确保这不会影响程序的正确性。

    类成员变量存在于每个类实例的内存中的一个位置,而不是每个线程。确实,在memory barriers 之间(想想synchronized 的开始{ 和结束}),线程可能拥有对象状态的缓存,但这与语言强制不同每个线程存储。 “每个线程的内存”是它的堆栈,它不包含对象成员* - 仅对对象的引用。

    最好的想法是每个对象在堆上都有一个位置,但可能同时发生涉及该内存位置的多个读取和|写入。

    如果你听说线程在堆的不同部分分配对象,我知道你会如何得出你所做的结论。一些 JVM 进行了优化,它们会执行 thread-local allocation,但这不会阻止其他线程访问这些对象。

    线程局部分配

    如果真正如清单 1 所示实现分配器,共享 heapStart 字段将很快成为一个重要的并发瓶颈,因为每次分配都涉及获取保护该字段的锁。为避免此问题,大多数 JVM 使用线程本地分配块,其中每个线程从堆中分配更大的内存块,并从该线程本地块中按顺序为小型分配请求提供服务。因此,线程必须获取共享堆锁的次数大大减少,提高了并发性。

    * - JVM 优化可能允许某些对象为allocated on the stack

    【讨论】:

    • Afaik Hotspot 确实在堆栈上分配对象,如果它可以证明明显的条件(即仅限于本地对象,这对于某些 EA 来说相当容易保证)。
    • @Voo,我发现了一些对堆栈分配的引用并编辑了我的括号以添加一个链接。
    【解决方案3】:

    “非静态变量存在于不同的位置”不可能是正确的。在 Java 中,您永远不会直接了解“堆栈”的任何内容。您的所有类变量,静态或实例,都来自堆。但是,作为 Java 开发人员,您并不真正关心这一点。

    您不关心线程安全的唯一情况是您的类是不可变的(构造后不要更改)或者您从未在线程中做任何事情。如果您的类不属于这两类,则需要考虑使它们成为线程安全的。

    设计中的不变性越多,线程问题就越容易推理和克服。

    Nrj 的想法是正确的。

    【讨论】:

      【解决方案4】:

      一些关键点可以帮助你澄清疑惑-

      1. 对象总是在堆上分配。

      2. 类级变量在线程之间共享(同一对象的线程)

      3. 局部变量始终是线程安全的(如果不以非线程安全的方式暴露给外界)

      【讨论】:

      • (1) 不成立(寻找逃逸分析); (2)没有多大意义(“同一对象的线程”?!); (3) 这很明显,不是吗?我的意思是,如果某些东西不是“非线程安全的”,那么它一定是线程安全的。
      • @Bruno - 你能指点我一些与 (1) 相关的博客吗? (2) 如果两个线程属于两个不同的对象,那么每个线程都会看到不同的类数据——因此它适用于来自同一对象的线程。 (3) 局部变量可能在允许从局部方法中逃逸的事件中不是线程安全的。
      • 对于 (1),请查看 JSPerfUnkn0wn 的链接,了解该问题的另一个答案。对于(2),“线程属于对象”这句话完全没有意义,看起来你误解了执行线程的概念;我建议您阅读Java 并发实践。对于(3),从逻辑上考虑你的陈述:它没有添加任何相关信息,它相当于“X 总是线程安全的(除非它不是线程安全的)”,你能从中看到任何有用的信息吗?
      • @Bruno - 1 实际上是一个编译器优化,它 depends 。甚至链接的海报认为。 (2) 我已经看过这本书了,谢谢。 (3) 这对我来说很有意义。声明一个局部变量是线程安全的,直到它没有被转义。只有满足后面的条件,第一个才是真的。希望对您有所帮助。
      【解决方案5】:

      局部变量、原语和引用是隐式线程局部的。但是,引用的对象可以共享,当线程可以修改共享对象时,您很可能需要synchronised、Lock 或其他一些策略来确保线程安全。

      【讨论】:

        【解决方案6】:

        堆栈是线程安全的,而堆不是线程安全的,除非您同步代码。堆栈包含局部变量和方法参数(原始和引用),而堆包含对象。

        【讨论】:

          【解决方案7】:

          在同一个对象实例上,如果你的方法没有同步,则不能保证相同的代码不会在不同的线程中执行两次 --> 破坏!哪个是正确的值?

          至少,您希望将访问变量的方法声明为同步。如果您想要更细粒度的控制,您可以使用例如ReentrantReadWriteLock

          在对象实例上声明同步方法同步,所以这是安全的。

          【讨论】:

            【解决方案8】:

            堆栈是的(想想调用堆栈,局部变量),但类变量存在于堆中,您必须同步对它们的访问:)

            【讨论】:

              猜你喜欢
              • 2011-01-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多