当前位置: 首页 > 面试题库 >

不可变物体是否可以不受不当出版的影响?

沈永新
2023-03-14
问题内容

这是来自JCiP的示例。

public class Unsafe {
    // Unsafe publication 
    public Holder holder;

    public void initialize() {
        holder = new Holder(42);
    }
}

public class Holder {
    private int n;

    public Holder(int n) {
        this.n = n;
    }
    public void assertSanity() {
        if (n != n) {
            throw new AssertionError("This statement is false.");
        }
    }
}

在第34页上:

[15]这里的问题不是Holder类本身,而是Holder没有正确发布。但是,可以通过将n字段声明为final来使Holder免受不适当发布的影响,这将使Holder不变。

从这个答案:

final的规范(请参阅@andersoj的答案)保证当构造函数返回时,将对final字段进行适当的初始化(从所有线程可见)。

从维基:

例如,在Java中,如果已经内联了对构造函数的调用,则一旦分配了存储空间,但是在内联的构造函数初始化对象之前,可以立即更新共享变量。

我的问题是:

因为:(可能不正确,我不知道。)

a)可以在内联构造函数初始化对象之前立即更新共享变量。

b)只有在构造函数返回时,才能保证对final字段进行正确的初始化(从所有线程可见)。

另一个线程是否可能看到默认值holder.n?(即,另一个线程holderholder构造函数返回之前获取对它的引用。)

如果是这样,那么您如何解释以下声明?

通过将n字段声明为final,可以使Holder免受不适当发布的影响,这将使Holder不可变

编辑: 从JCiP。不可变对象的定义:

在以下情况下对象是不可变的:
x构造后无法修改其状态;

x其所有字段均为最终字段; [12]和

x构造正确(该参考在构造期间不会逸出)。

因此,根据定义,不可变对象没有“ this引用转义”问题。对?

但是如果不声明为易失性,它们是否会遭受双重检查锁定模式的乱序写入?


问题答案:

一个不可变的对象,例如String,对于所有读者来说似乎都具有相同的状态,而不管它的引用如何获得,即使同步不正确并且没有先发生后关系。

这是通过finalJava5中引入的字段语义实现的。通过最后一个字段进行的数据访问具有更强的内存语义,如jls-17.5.1中所定义。

在编译器重新排序和内存障碍方面,处理最终字段时存在更多约束,请参阅《JSR-133
Cookbook》
。您担心的重新排序不会发生。

是的-可以通过包装器中的final字段进行双重检查锁定;没有volatile要求!但是这种方法不一定更快,因为需要两次读取。

请注意,此语义适用于各个最终字段,而不适用于整个对象。例如,String包含一个可变字段hash;但是,String由于其公共行为仅基于final字段,因此被认为是不变的。

final字段可以指向可变对象。例如,String.valuea char[]是可变的。要求不可变的对象是最终字段的树是不切实际的。

final char[] value;

public String(args) {
    this.value = createFrom(args);
}

只要我们不修改value构造函数退出后的内容,就可以了。

我们可以value按任何顺序修改构造函数中的内容,没关系。

public String(args) {
    this.value = new char[1];
    this.value[0] = 'x';  // modify after the field is assigned.
}

另一个例子

final Map map;
List list;

public Foo()
{
    map = new HashMap();
    list = listOf("etc", "etc", "etc");
    map.put("etc", list)
}

通过 final字段 进行 的任何访问都将是不可变的,例如foo.map.get("etc").get(2)

通过最终字段进行访问 不会 - foo.list.get(2)通过不正确的发布是不安全的,即使它读取的是同一目的地。

这些就是设计动机。现在让我们看看JLS如何在jls-17.5.1中对其进行形式化

freeze在构造函数的出口处定义了一个动作,与在final字段的分配中定义的动作相同。这使我们可以在构造函数内部的任何地方编写以填充内部状态。

不安全发布的常见问题是缺少“事前”(hb)关系。即使读取看到写入,它也不会与其他操作建立任何关系。但是,如果易失性读取看到易失性写入,则JMM会hb在许多操作之间建立顺序。

final领域的语义想要做同样的事情,即使是正常的甚至通过不安全的出版物读取和写入,也就是。为此mc,在读取可见的任何写入之间添加一个存储链()顺序。

deferences()为了限制了语义访问 通过 最后一个字段。

让我们重新Foo查看示例以了解其工作原理

tmp = new Foo()

    [w] write to list at index 2

    [f] freeze at constructor exit

shared = tmp;   [a]  a normal write

// Another Thread

foo = shared;   [r0] a normal read

if(foo!=null) // [r0] sees [a], therefore mc(a, r0)

    map = foo.map;          [r1] reads a final field

    map.get("etc").get(2)   [r2]

我们有

hb(w, f), hb(f, a), mc(a, r1), and dereferences(r1, r2)

因此w是可见的r2

本质上,通过Foo包装器,可以通过不安全的发布安全地发布地图(本身是可变的)……如果这是有道理的。

我们可以使用包装器建立最终字段的语义,然后丢弃它吗?喜欢

Foo foo = new Foo();   // [w] [f]

shared_map = foo.map;  // [a]

有趣的是,JLS包含足以排除此类用例的子句。我猜想它已经被削弱了,所以允许更多的内部线程优化,即使对于最终字段也是如此。

注意,如果this在冻结操作之前泄漏了,则不能保证最终字段的语义。

但是,通过冻结构造函数,我们 可以 在冻结动作 之后 安全地泄漏this到构造函数中。 __

-- class Bar

final int x;

Bar(int x, int ignore)
{
    this.x = x;  // assign to final
}  // [f] freeze action on this.x

public Bar(int x)
{ 
    this(x, 0);
    // [f] is reached!
    leak(this); 
}

就此x而言,这是安全的;冻结动作x是在x分配了构造函数的地方定义的。这可能只是为了安全泄漏而设计的this



 类似资料:
  • 问题内容: C#核心库中是否内置可以为我提供不可变字典的内容? 类似于 Java的 东西: 只是为了澄清一下,我并不是要阻止键/值本身被更改,而只是希望字典的结构不会停止更改。如果将IDictionary的任何mutator方法称为(),我都会希望它们快速而响亮地失败。 问题答案: 不,但是包装器很简单: 显然,如果要允许修改值,可以更改上面的this []设置器。

  • 我想创建一个包含XFA数据的PDF,但我不想在查看PDF时看到XFA数据,但是,我希望可以通过以下代码访问它: 我怎么能这样呢? 谢谢

  • 问题内容: 我知道这可能很愚蠢,但是很多地方都声称Java中的Integer类是不可变的,但是下面的代码: 毫无困难地执行(给出预期的结果)6。因此有效地改变了a的值。这不是说Integer是可变的吗?第二个问题和一些小问题:“不变的类不需要复制构造函数”。有人在乎解释原因吗? 问题答案: 不可变并不意味着永远不能等于另一个值。例如,也是不可变的,但是我仍然可以这样做: 并没有改变,而是变成了一个

  • 问题内容: 在我们的应用程序中,我们需要具有只能分配一次的字段。 最初,我们想到封装字段并将设置程序设为私有。但是,引起一些问题: 如果没有公共设置者,Hibernate是否仍然可以从数据库映射字段? 我是否可以剥离设置器并使字段仅在实体构造函数中可变? 最后,是否有任何标准的JPA方法使字段不变? 提前致谢。 问题答案: 广告。1:我相信,如果将注释放在字段而不是在getter上,则JPA会将普

  • 我尝试遵循在工具栏中创建自定义TextView的教程(https://guides.codepath.com/android/using-the-app-toolbar#custom-title-view)。我想做的是能够使用Java动态更改TextView中的文本。但是,问题是我不能并且没有显示错误消息。 XML 中的工具栏代码: 活动类中的工具栏Java代码: Styles.xml: 一个简单

  • 问题内容: 我正在尝试使用Spring 3.0.6返回JSON响应,但是得到了406响应“ Not Acceptable”,其描述为:“此请求所标识的资源仅能够生成具有以下特征的响应:请求“接受”标头()。” 我知道之前曾问过一个非常类似的问题,但尽管进行了许多测试,但我无法使它适用于我的项目,而且我不知道自己在做什么错。 在我的Maven pom.xml中,执行以下操作: 在web.xml中,我