【问题标题】:Java Array Synchronization (Visibility)Java 数组同步(可见性)
【发布时间】:2012-11-20 19:45:13
【问题描述】:

我试图准确了解元素可见性如何在 java 中的数组上起作用。

给定班级:

class IntList {

    private final int[] array;

    public IntList(int[] array) {
        this.array = array;
    }

    public int[] readElements() {
        return Arrays.copyof(this.array, this.array.length);
    }

}    

以及以下用于创建实例的方法体:

int[] array = new int[length];
fillArrayWithRandomData(array); // puts data into the array from arbitrary source
return new IntList(array);

我想知道IntList 中的元素是否保证对获得对返回的IntList 的引用的其他线程可见?

我确信对数组的 REFERENCE 是可见的,因为它是最终的,但我似乎无法保证数组中的元素也可见。

注意:IntList 类没有允许修改数组的方法,并且数组引用不会发布到任何其他对象,我只是想知道构造后的可见性。

编辑:抱歉,在我的实际实现中,我的课程没有被称为 String。我把类名改成了IntList,因为好像太乱了。

编辑: 我要在这里给出的最终答案是肯定的,这些元素是可见的。
@MikeClark 找到了 JLS 的答案: JLS § 17.5 “final 字段的使用模型很简单:在该对象的构造函数中设置对象的 final 字段;并且不要将对正在构造的对象的引用写在另一个线程可以在对象之前看到它的地方。构造函数完成。如果遵循这个,那么当另一个线程看到该对象时,该线程将始终看到该对象最终字段的正确构造版本。它还将看到那些最终字段引用的任何对象或数组的版本至少与最终字段一样最新。”

再次感谢!

【问题讨论】:

  • 请不要将您的类称为内置类。字符串与 java.lang.String 冲突 - 可能会给您带来无穷无尽的问题。
  • @SamuelRossille “可见性”是讨论 [Java] 内存模型时经常使用的术语,这是一个与多线程高度相关的话题。例如,请参阅对 Java 内存模型的讨论,该讨论导致了修订后的 Java 5 JMM:ibm.com/developerworks/library/j-jtp03304。 “理解 JMM 所需的关键概念之一是可见性——你怎么知道如果线程 A 执行 someVariable = 3,其他线程会看到线程 A 写入的值 3?”
  • @TedHopp - 在提到跨线程所做的更改时,“可见性”一词也被大量使用。看这里:docs.oracle.com/javase/specs/jls/se5.0/html/memory.html
  • @MikeClark - 您应该将您的评论作为答案,因为当前的答案都没有完全正确。
  • @MikeClark 和 Brian 我不知道可见性的含义。无知导致了我的错误。发送信息。

标签: java arrays synchronization


【解决方案1】:

既然您在构造函数中填充数组,那么是的,任何对new String(int[] array) 的调用都会在它返回时初始化数组。 final 关键字还将保证在分配时对 array 参数的最新更改可见。

【讨论】:

  • 没有这样的构造函数String(int[])
  • @EvgeniyDorofeev - OP 的课程中​​有。 (不是java.lang.String。)
  • 是的,我知道如果我在构造函数中填充了数组,它将对其他线程可见,但重点是我在构造函数之前填充数组并简单地在构造函数中设置引用。
  • 没错,所以当在构造函数中设置引用时,数组已经被填充,它是完全相同的概念。
  • JLS § 17.5 “final 字段的使用模型很简单:在该对象的构造函数中设置对象的 final 字段;并且不要将正在构造的对象的引用写在另一个线程可以在对象的构造函数完成之前看到它。如果遵循这个,那么当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本。它还将看到版本这些最终字段引用的任何对象或数组至少与最终字段一样最新。"
【解决方案2】:

我发现这很有帮助http://jeremymanson.blogspot.ch/2009/06/volatile-arrays-in-java.html 所以基本上一切都发生在 volatile read 保证被其他线程看到之前。所以有一些技巧可以让它发生。 此外,jaa 提供原生 AtomicReference/Long/Integer/...Array 支持。这将确保更新对其他线程可见。

【讨论】:

    【解决方案3】:

    您的问题似乎混淆了许多不同的概念。

    • 可见性和final 没有任何关系
    • 可见性和线程彼此无关
    • private 变量在类外部对任何其他代码均不可见,无论它是否为最终变量
    • final 数组仍然可以更改其元素。只是对数组本身的引用是 final

    当您像这样构造 IntList 对象时(顺便说一句,感谢您更改名称):

    public IntList(int[] array) {
        this.array = array;
    }
    

    内部this.array 字段引用传递给构造函数的相同数组对象。仍然可以从 String 类之外修改数组:

    int[] array = {1, 2, 3};
    IntList list = new IntList(array);
    System.out.println(Arrays.toString(list.readElements()); // prints [1, 2, 3]
    array[1] = 0;
    System.out.println(Arrays.toString(list.readElements()); // prints [1, 0, 3]
    

    您可以通过在构造函数中复制数组来使您的类与此隔离:

    public IntList(int[] array) {
        this.array = Arrays.copyOf(array, array.length);
    }
    

    【讨论】:

    • final 确实与内存可见性有关。参见例如cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalRight 此外,JLS § 17.5 “final 字段还允许程序员在不同步的情况下实现线程安全的不可变对象。线程安全的不可变对象被所有线程视为不可变对象,即使使用数据竞争来传递对线程之间的不可变对象。” OP 显然是在谈论内存可见性,而不是词汇可见性(范围)。
    • @MikeClark 当然,不要误导,最终数组不是不可变的。确保数组不变性的唯一方法是使用范围可见性,以便可以进行公共访问以返回副本。
    • @Brian 说得好。 Java 数组是可变的,因此必须小心。但是,final 可用于保证数组内容的可见性,如果使用时要仔细注意 JLS 的约束:“[另一个线程] 还将看到那些引用的任何对象或数组的版本final 字段至少与 final 字段一样最新。”
    • @MikeClark - OP 没有说“内存可见性”;在我看来,这是一个关于数组元素是否可以在构造对象后更改的问题。 Java Language Specification 根据范围定义“可见”:“如果 d 的范围包括 p,并且 d 没有被任何p 的其他声明。”这与内存模型无关。
    • @TedHopp 他没有说“内存可见性”,但他明确暗示了这一点。他的声明“我想知道 IntList 中的元素是否保证对其他线程可见”。这正是Java 内存模型要回答的问题。我同意“可见性”是一个重载的术语,如果没有任何其他上下文线索,我会假设“可见性”一词是指范围界定。但是他提到线程,特别是对其他线程的数据可见性,将他的问题牢牢地放在 内存 可见性领域,而不是范围界定。
    猜你喜欢
    • 1970-01-01
    • 2019-03-19
    • 1970-01-01
    • 2017-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-16
    相关资源
    最近更新 更多