Koh*_*aki 5 java concurrency multithreading language-lawyer
在 Java Concurrency In Practice 一书中,有一个几乎不可变对象的例子,如果没有正确发布,它有失败的风险:
// 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);
}
}
Run Code Online (Sandbox Code Playgroud)
如果我正确理解了书中的章节,添加final到类的n字段Holder将使对象完全不可变并消除AssertionError抛出的机会,即使它仍然像在Client类中所做的那样没有足够的同步发布。
现在我想知道匿名类在这方面的表现如何。请看下面的例子:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
就像书中的例子一样,它在没有足够同步的情况下发布,但不同的是现在Holder类变成了一个接口,并且有一个静态工厂方法,它返回一个实现该接口的匿名类,而匿名类使用方法参数n.
我的问题是:有没有机会AssertionError从我的后一个例子中得到?如果有,让它完全不可变并消除问题的最佳方法是什么?如果它以如下功能方式编写,它会改变什么吗?
class IHolderFactory {
static IHolder create(int n) {
return () -> {
if (n != n)
throw new AssertionError("This statement is false.");
};
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个非常棘手的问题。
局部变量(第14.4 节)、形式方法参数(第8.4.1 节)和异常处理程序参数(第14.20 节)永远不会在线程之间共享,并且不受内存模型的影响。
这似乎与您可以在可以在线程之间共享的内部类或 lambda 表达式中使用它们的事实相矛盾,但这些构造捕获变量的值并使用该值。然而,这个过程并没有很好地说明。
我能找到的唯一提及是在解释(有效)最终要求的§15.27.2 中:
对有效最终变量的限制禁止访问动态变化的局部变量,其捕获可能会引入并发问题。
实际上,捕获的值存储在final内部类或运行时为 lambda 表达式生成的类的合成字段中。所以你永远不会看到当前实现的错误。
然而,这没有在任何地方指定。语言规范很少涉及字节码格式,虚拟机规范很少涉及语言结构。
因此,局部变量、形式方法参数和异常处理程序参数被明确排除在 JMM 之外,它们捕获的值在 JMM 看来不是变量,甚至没有在那里提及。问题是这意味着什么。
它们通常不受数据竞争的影响(我的解释)还是它们不安全并且我们根本没有得到 JMM 的任何保证?在后一种情况下,它甚至意味着我们无法使它们安全,因为任何安全的发布机制都从不涵盖我们案例的 JMM 保证中获得安全。值得注意的是,JMM 也不涵盖外部this引用,也不涵盖实例对由Class返回的对象的隐式引用getClass()。
因此,虽然我认为它们不受数据竞争的影响,但我希望能更明确地说明这一点。