匿名 类 可以完全不可变吗?
Can anonymous classes be completely immutable?
在 Java 并发实践一书中,有一个几乎不可变对象的示例,如果发布不当,它有失败的风险:
// Taken from Java Concurrency In Practice
// p.51 Listing 3.15: Class at risk of failure if not properly published.
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.");
}
}
// p.50 Listing 3.14: Publishing an object without adequate synchronization. Don't do this.
class Client {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
}
如果我对书中的章节理解正确,将final
添加到Holder
class的n
字段将使对象完全不可变并消除有机会抛出 AssertionError
,即使它仍然在没有足够同步的情况下发布,就像在 Client
class.
中所做的那样
现在我想知道匿名 classes 在这方面的表现如何。请看下面的例子:
public interface IHolder {
void assertSanity();
}
class IHolderFactory {
static IHolder create(int n) {
return new IHolder() {
@Override
public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
};
}
}
class IHolderClient {
public IHolder holder;
public void initialize() {
// is this safe?
holder = IHolderFactory.create(42);
}
}
它是在没有充分同步的情况下发布的,就像书中的例子一样,但不同的是现在 Holder
class 已经成为一个接口,并且有一个静态工厂方法 returns 实现接口的匿名 class,匿名 class 使用方法参数 n
.
我的问题是:是否有机会从我抛出的后一个示例中得到 AssertionError
?如果存在,使其完全不可变并消除问题的最佳方法是什么?如果它像下面这样以函数式方式编写,它会改变什么吗?
class IHolderFactory {
static IHolder create(int n) {
return () -> {
if (n != n)
throw new AssertionError("This statement is false.");
};
}
}
使用匿名 class 或 lambda 并不重要,这里有 zero 同步机制来正确发布引用;因此,此代码可以抛出该异常。
换句话说,您必须满足一些工具和条件才能使您的代码安全:这些工具和条件使用 final
、volatile
或某种 synchronized
或 locks
,等等。由于您使用 none,因此不提供任何保证。
Java 语言规范 仅在 使用特殊语义时提供这些保证,例如您在第一个示例中显示的 final
。还有其他方法,但使对象不可变是最简单、最琐碎的方法。 This is the best article 我知道这个主题,你可能想读一读。
这是一个非常棘手的问题。
JLS, §17.4.1. Shared Variables 说:
Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.
这似乎与您可以在线程之间共享的内部 class 或 lambda 表达式中使用它们的事实相矛盾,但这些构造捕获变量的值并使用该值。然而,这个过程并没有很好地说明。
我能找到的唯一提及是 §15.27.2 解释(有效的)最终要求:
The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.
实际上,捕获的值存储在内部 class 或运行时为 lambda 表达式生成的 class 的合成 final
字段中。所以你永远不会看到当前实现的错误。
然而,这在任何地方都没有指定。语言规范说 little about the bytecode format 而虚拟机规范对语言结构说得很少。
因此,局部变量、形式方法参数和异常处理程序参数被明确排除在 JMM 之外,并且它们捕获的值在 JMM 的考虑中不是变量,甚至没有在 JMM 中提及。问题是那是什么意思。
它们是否通常不受数据竞争的影响(我的解释),或者它们是否不安全并且我们根本无法从 JMM 获得任何保证?在后一种情况下,这甚至意味着我们无法保证它们的安全,因为任何安全发布机制的安全都来自 JMM 的保证,而 JMM 并不涵盖我们的案例。值得注意的是,JMM 也不涵盖外部 this
引用,也不涵盖实例对 getClass()
.
返回的 Class
对象的隐式引用。
因此,虽然我认为它们不受数据竞争的影响,但我希望能更明确地说明这一点。
在 Java 并发实践一书中,有一个几乎不可变对象的示例,如果发布不当,它有失败的风险:
// Taken from Java Concurrency In Practice
// p.51 Listing 3.15: Class at risk of failure if not properly published.
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.");
}
}
// p.50 Listing 3.14: Publishing an object without adequate synchronization. Don't do this.
class Client {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
}
如果我对书中的章节理解正确,将final
添加到Holder
class的n
字段将使对象完全不可变并消除有机会抛出 AssertionError
,即使它仍然在没有足够同步的情况下发布,就像在 Client
class.
现在我想知道匿名 classes 在这方面的表现如何。请看下面的例子:
public interface IHolder {
void assertSanity();
}
class IHolderFactory {
static IHolder create(int n) {
return new IHolder() {
@Override
public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
};
}
}
class IHolderClient {
public IHolder holder;
public void initialize() {
// is this safe?
holder = IHolderFactory.create(42);
}
}
它是在没有充分同步的情况下发布的,就像书中的例子一样,但不同的是现在 Holder
class 已经成为一个接口,并且有一个静态工厂方法 returns 实现接口的匿名 class,匿名 class 使用方法参数 n
.
我的问题是:是否有机会从我抛出的后一个示例中得到 AssertionError
?如果存在,使其完全不可变并消除问题的最佳方法是什么?如果它像下面这样以函数式方式编写,它会改变什么吗?
class IHolderFactory {
static IHolder create(int n) {
return () -> {
if (n != n)
throw new AssertionError("This statement is false.");
};
}
}
使用匿名 class 或 lambda 并不重要,这里有 zero 同步机制来正确发布引用;因此,此代码可以抛出该异常。
换句话说,您必须满足一些工具和条件才能使您的代码安全:这些工具和条件使用 final
、volatile
或某种 synchronized
或 locks
,等等。由于您使用 none,因此不提供任何保证。
Java 语言规范 仅在 使用特殊语义时提供这些保证,例如您在第一个示例中显示的 final
。还有其他方法,但使对象不可变是最简单、最琐碎的方法。 This is the best article 我知道这个主题,你可能想读一读。
这是一个非常棘手的问题。
JLS, §17.4.1. Shared Variables 说:
Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.
这似乎与您可以在线程之间共享的内部 class 或 lambda 表达式中使用它们的事实相矛盾,但这些构造捕获变量的值并使用该值。然而,这个过程并没有很好地说明。
我能找到的唯一提及是 §15.27.2 解释(有效的)最终要求:
The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.
实际上,捕获的值存储在内部 class 或运行时为 lambda 表达式生成的 class 的合成 final
字段中。所以你永远不会看到当前实现的错误。
然而,这在任何地方都没有指定。语言规范说 little about the bytecode format 而虚拟机规范对语言结构说得很少。
因此,局部变量、形式方法参数和异常处理程序参数被明确排除在 JMM 之外,并且它们捕获的值在 JMM 的考虑中不是变量,甚至没有在 JMM 中提及。问题是那是什么意思。
它们是否通常不受数据竞争的影响(我的解释),或者它们是否不安全并且我们根本无法从 JMM 获得任何保证?在后一种情况下,这甚至意味着我们无法保证它们的安全,因为任何安全发布机制的安全都来自 JMM 的保证,而 JMM 并不涵盖我们的案例。值得注意的是,JMM 也不涵盖外部 this
引用,也不涵盖实例对 getClass()
.
Class
对象的隐式引用。
因此,虽然我认为它们不受数据竞争的影响,但我希望能更明确地说明这一点。