让我们有以下类:

public class MyClass {
  private final List<String> myList = new ArrayList<>(); //Not a thread-safe thing

  // Called on thread 1
  MyClass() {
    myList.add("foo");
  }

  // Called on thread 2
  void add(String data) {
    myList.add(data);
  }
}

它的格式是好还是不好?

我只找到了这个:

当一个对象的构造函数完成时,它被认为是完全初始化的.只有在对象完全初始化后才能看到对该对象的引用的线程,一定会看到该对象的最终字段的正确初始化值.

https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

这意味着"线程2"必须看到ArrayList,但可能会也可能不会看到它的内容,对吧?

换句话说,有没有可能有以下顺序:

T1:创建MyClassArrayList

T2:接入ArrayList

T1:将"foo"添加到列表中

推荐答案

你的 idea 是错误的.这是一个明智的思路,但是,JMM(JMM §17.5)的这一节:

它还将看到那些最终字段引用的任何对象或数组的版本,这些对象或数组至少与最终字段一样最新.

不同意这种分析.因此,假设您遵守JMM中那一节规定的规则,即:

Do not let the 100 ref escape from the constructor. e.g. if the constructor does 101 or 102, any code that uses that 100 reference does not get the guarantee.

那么,不仅直接值是安全的(对于原语来说,值是安全的,对于对象来说,是引用(即指针)),而且对象的字段/数组的槽也是安全的."安全"在这里的意思是:任何引用它的代码(除了通过上面的异常,您不能获得这种安全性)都不能像构造函数finishes之前那样观察处于状态的final个字段中的任何一个.

因此,您的代码片段格式良好.

当然,请注意,为构造函数调用赋值的行为本身并不包含在本文中.因此:

class Example {
  MyObject c;

  void codeInThread1() {
    c = new MyObject();
  }

  void codeInThread2() {
    System.out.println(c);
  }
}

可以导致线程2打印null(因为线程2将c观察为null)、even if代码已经清楚地指示线程1已经进展到远远超过c = new MyObject();的其他效果.JMM不保证一个线程的写入被另一个线程观察到,除非建立关系之前发生.

然而,JVM确实保证不会对对象引用和一定的一致性进行剪切写入:线程2只能观察到两件事.线程2可以观察到null或完全初始化的对象,即没有构造函数完成时的状态.

等一等!

编辑:不,不要坚持--你可以走了.请参见此问题的另一个答案,以解释为什么您的代码片段完全正确,并且无需担心对象after的Mutations 会将其分配给最终的字段.

从技术上讲,上面的说法几乎是不正确的.JMM说‘至少和最终字段一样是最新的’.这意味着对最终字段的实际写入是‘锁定’的行为--JMM保证您可以从该引用获得的任何状态"至少是最新的".然而,在您的代码片段中,这一时刻"太快了".因此,这是完成相同任务的技术上正确的方法:

public class MyClass {
  private final List<String> myList;
  // Called on thread 1
  MyClass() {
    var list = new ArrayList<>();
    list.add("foo");
    myList = list; // lock it in!
  }

  // Called on thread 2
  void add(String data) {
    myList.add(data);
  }
}

然而,我非常肯定不存在JVM+OS+体系 struct 的组合,您可以在‘myList指向空数组列表’的状态下观察到该对象.然而,JMM并没有采用JVM+OS+ARCH组合来消除该选项.上面的代码does消除了它(就像在中一样,允许您观察空列表的JVM+OS+ARCH组合被 destruct 了,您应该提交一个错误-因为它违反了§17.5的引号条款).

Java相关问答推荐

如何计算内循环的时间复杂度?

我想了解Java中的模块化.编译我的应用程序时,我有一个ResolutionException

ittext pdf延迟签名,签名无效

@ IdClass with @ Inheritance(策略= InheritanceType. SINGLE_TABLE)

S的字符串表示是双重精确的吗?

将不受支持的时区UT重写为UTC是否节省?

将关键字与正文中的_Allowed匹配,但带有__Signing可选后缀

呈现文本和四舍五入矩形时出现的JavaFX窗格白色瑕疵

Java流传输一个列表并创建单个对象

在Java 17中使用两个十进制数字分析时间时出错,但在Java 8中成功

@Rollback @ Transmission在验收测试中不工作

声明MessageChannel Bean的首选方式

Android Studio模拟器没有互联网

向Java进程发送`kill-11`会引发NullPointerException吗?

当我在Java中有一个Synchronized块来递增int时,必须声明一个变量Volatile吗?

没有Tomcat,IntelliJ如何在本地运行API?

如何设置默认序列生成器分配大小

try 添加;按流派搜索;在Web应用程序上,但没有;I don’我不知道;It’这个代码错了

ExecutorService:如果我向Executor提交了太多任务,会发生什么?

java.util.LinkedList()是如何成为MutableList的实例的?