【问题标题】:Why are objects visible to all threads, while a reading thread might not see a value written by another thread on a timely basis?为什么对象对所有线程都是可见的,而读取线程可能看不到另一个线程及时写入的值?
【发布时间】:2018-03-26 02:01:12
【问题描述】:

简而言之来自java

在 Java 中,一个进程中的所有 Java 应用程序线程都有自己的 堆栈(和局部变量),但共享一个堆。这使它 在线程之间共享对象非常容易,因为只需要 将引用从一个线程传递到另一个线程。

这导致了 Java 的一般设计原则——对象是 默认可见。如果我有一个对象的引用,我可以复制 并将其交给另一个线程,没有任何限制。一个爪哇 引用本质上是指向内存中某个位置的类型化指针——并且 线程共享相同的地址空间,因此默认可见 是一个自然模型。

来自 Java 并发实践

可见性是微妙的,因为可能出错的事情是如此之多 违反直觉的。在单线程环境中,如果你写一个 变量的值,然后在没有干预的情况下读取该变量 写入,您可以期望得到相同的值。这似乎只是 自然的。一开始可能很难接受,但当阅读和 写入发生在不同的线程中,事实并非如此。在 一般的, 无法保证读取线程会及时看到另一个线程写入的值,甚至根本没有。在 为了确保跨线程内存写入的可见性,您必须 使用同步。

当一个线程在没有同步的情况下读取一个变量时,它可能会看到一个陈旧的值。

那么为什么 Java in a Nutshell 说对象对所有线程都是可见的,而 Java Concurrency in Practice 却说不能保证读取线程及时看到另一个线程写入的值?它们似乎不一致。

谢谢。

【问题讨论】:

  • Java In A Nutshell 是错误的,或者至少掩盖了一些微妙但非常重要的细节。他们应该说“所有对象在某些时候都是可见的,即使那样你也可能只看到其中的一些——除非你小心如何将它们交给另一个线程。”
  • 所有对象都是可见的,但这并不意味着不同的线程会看到最新的值... ;) 阅读关于关键字 volatile 的移动
  • @alfasin 既然共享对象存储在线程共享的堆中,为什么有些线程可能看不到其他线程的最新值?
  • @Ben 因为线程可能缓存了这些对象的值
  • Nutshell 语句中的 Java 是正确的,这不是 Java 问题。它也适用于 C# 和 C++。有内存,可以访问(如果已提交)或不可以访问(如果保留)。并发这本书也是正确的,因为它谈到了数据竞争,当您并行读取和写入同一内​​存时会发生这种情况。

标签: java multithreading concurrency


【解决方案1】:

在 Java 中,一个进程中的所有 Java 应用程序线程都有自己的 堆栈(和局部变量),但共享一个堆。这使它 很容易在线程之间共享对象,因为只需要 将引用从一个线程传递到另一个线程。

这就引出了 Java 的一般设计原则——对象是 默认可见。

我认为这些陈述是严格正确的......但它们具有误导性,因为它们没有传达全部真相。例如,作者说“……对象默认可见”是什么意思。

在 Java JVM 上执行的任何线程实际上无法查看 JVM 堆上的所有对象。如果我们将可见性定义为“通过引用访问的能力”,那么线程只对对象具有可见性:

  • 其引用已发布到该线程
  • 其引用位于静态字段或线程有权访问的对象的字段中

事实上,Java并发编程中一个重要且常用的线程安全策略是线程限制。如果一个线程持有对只有它可以访问的对象的引用,并且该对象没有发布到任何其他线程,则该对象是线程安全的。该对象可以被它所在的线程安全地改变,而无需进一步考虑可见性和原子性……只要它是正确的线程限制。

换句话说,线程受限的对象,无论它在 JVM 堆上的哪个位置,由于不可访问,对于可能在同一 JVM 上运行的任何其他线程都是不可见的。

既然共享对象存储在线程共享的堆中,为什么 某些线程可能看不到其他线程的最新值?

在这个多核处理器时代,运行 JVM 的每个 CPU 都有自己的本地缓存级别,这是其他内核无法看到的。这就是为什么不保证写入一个线程中的变量的值对另一个线程可见的核心:Java 内存模型不保证一个线程写入的值何时对其他线程可见,因为它没有指定何时缓存值将从缓存写回内存。

事实上,当许多线程访问这些值时,这些值的非同步访问通常是陈旧的(或不一致的)。根据正在发生的状态转换,并发环境中的线程安全可能需要:

  • 互斥
  • 原子性保护
  • 可见性保证
  • 或以上所有

为了实现让你的程序正确的线程安全策略。

【讨论】:

    【解决方案2】:

    “那么为什么 Java in a Nutshell 说对象对所有线程都是可见的” --> 正如您的报价所说,在 Java 中,对象是在堆上分配的。可用于整个 JVM 的“全局”堆。而在其他语言(例如 C++)中,对象也可以分配在堆栈上。堆上的对象可以使用不同的堆栈传递给其他线程。堆栈上的对象只能在使用同一堆栈的线程上使用,因为堆栈的内容会发生变化,超出另一个线程的控制。

    “而 Java Concurrency in Practice 说不能保证读取线程及时看到另一个线程写入的值?” -> 这是另一个问题,因为这是关于内存值的地点。尽管它们是可访问的编译器,但 CPU 会尝试优化对该内存位置的读取或写入,并会通过假设“我是唯一一个读取和写入该内存位置的人”来大量缓存该值。所以如果一个线程修改了一个内存位置的值,另一个线程不知道它已经改变并且不会读取它新的。这使程序更快。通过声明变量volatile,您是在告诉编译器另一个线程可以随意更改该值,编译器将使用它来创建不缓存该值的代码。

    最后,多线程比添加volatile 或使用synchronized 困难得多,确实需要深入探讨您在使用多线程时遇到的问题。 p>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-01
      相关资源
      最近更新 更多