Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/394.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/heroku/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 线程如何查看安全初始化对象的过期引用_Java_Multithreading_Thread Safety - Fatal编程技术网

Java 线程如何查看安全初始化对象的过期引用

Java 线程如何查看安全初始化对象的过期引用,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我一直试图弄清楚,安全发布的不可变对象是如何通过过时引用被观察到的 public final class Helper { private final int n; public Helper(int n) { this.n = n; } } class Foo { private Helper helper; public Helper getHelper() { return helper; } public void setHelper(i

我一直试图弄清楚,安全发布的不可变对象是如何通过过时引用被观察到的

public final class Helper {
private final int n;

  public Helper(int n) {
    this.n = n;
  }
} 

class Foo {
  private Helper helper;

  public Helper getHelper() {
    return helper;
  }

  public void setHelper(int num) {
    helper = new Helper(num);
  }
} 
到目前为止,我可以理解Helper是不可变的,可以安全地发布。读取线程要么读取null对象,要么读取完全初始化的Helper对象,因为它在完全构造之前不可用。解决方案是将volatile放在我不理解的Foo类中。

引用自

这意味着,如果两个线程同时更新同一对象的一个变量,并且该变量未声明为volatile,则可能存在这样的情况:其中一个线程的缓存中有一个旧值

根据您的代码,可能会发生以下情况:

  • 线程1调用getHelper()并获取null
  • 线程2调用getHelper()并获取null
  • 线程1调用setHelper(42)
  • 线程2调用setHelper(24)
在这种情况下,您的问题在于哪个线程将使用哪个Helper对象。关键字
volatile
至少可以解决缓存问题

引用

这意味着,如果两个线程同时更新同一对象的一个变量,并且该变量未声明为volatile,则可能存在这样的情况:其中一个线程的缓存中有一个旧值

根据您的代码,可能会发生以下情况:

  • 线程1调用getHelper()并获取null
  • 线程2调用getHelper()并获取null
  • 线程1调用setHelper(42)
  • 线程2调用setHelper(24)

在这种情况下,您的问题在于哪个线程将使用哪个Helper对象。关键字
volatile
至少可以解决缓存问题

多个线程同时读取变量
helper
。至少,您必须使其
易失性
,否则编译器将开始将其缓存在线程的本地寄存器中,并且对变量的任何更新可能不会反映在主内存中。使用
volatile
,当线程开始读取共享变量时,它将清除其缓存并从全局内存中获取新值。当它完成读取时,它会将缓存中的内容刷新到主内存中,以便其他线程可以获得更新的值。

多个线程同时读取变量
helper
。至少,您必须使其
易失性
,否则编译器将开始将其缓存在线程的本地寄存器中,并且对变量的任何更新可能不会反映在主内存中。使用
volatile
,当线程开始读取共享变量时,它将清除其缓存并从全局内存中获取新值。当它完成读取时,它会将缓存中的内容刷新到主内存中,以便其他线程可以获得更新的值。

发布对不可变对象的引用这一事实与此无关

如果您正在从多个线程读取引用的值,那么如果您关心使用最新值的所有线程,则需要确保写入发生在读取之前

before是语言规范中精确定义的术语,特别是关于Java内存模型的部分,它允许线程进行优化,例如,不总是更新主内存中的内容(速度很慢),而是将它们保存在本地缓存中(这要快得多,但可能会导致线程对“同一”变量持有不同的值)。之前发生的是一种关系,它可以帮助您推断在使用这些优化时多个线程如何交互

除非您确实创建了“发生在之前”关系,否则无法保证您会看到最新的值。在您显示的代码中,
helper
的写入和读取之间没有这种关系,因此您的线程不能保证看到
helper
的“新”值。它们可能会看到,但可能不会

确保写入发生在读取之前的最简单方法是使
helper
成员变量
final
:确保写入
final
字段的值发生在构造函数结束之前,因此所有线程始终看到字段的正确值(前提是
未在构造函数中泄漏)

显然,将其设置为最终版并不是一个选项,因为您有一个setter。因此您必须采用其他机制

从表面上看代码,最简单的选择是使用(最终)
AtomicInteger
而不是
Helper
类:写入
AtomicInteger
肯定会在后续读取之前发生。但我想实际的Helper类可能更复杂

所以,你必须在建立关系之前先建立这种关系。实现这种关系的三种机制是:

  • 使用
    AtomicReference
    :这与
    AtomicInteger
    具有类似的语义,但允许您存储引用类型的值。(感谢您指出这一点,@Thilo)
  • 使字段
    易失性
    :这保证了最近写入的值的可见性,因为它会导致写入刷新到主存(而不是从线程缓存读取),并从主存读取。它有效地阻止JVM进行此特定优化
  • 访问同步块中的字段。最简单的方法是同步getter和setter方法。重要的是,您不应该在
    helper
    上同步,因为此字段正在更改

    • 发布对不可变对象的引用这一事实与此无关

      如果您正在从多个线程读取引用的值,那么如果您关心所有线程使用最多的