java匿名类可以完全不可变吗?
在《实践中的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
类的n
字段将使对象完全不可变,并消除了抛出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);
}
}
它是在没有足够的同步的情况下发布的,就像书中的例子一样,但区别在于现在Holder
类已经成为一个接口,有一个静态工厂方法返回一个实现接口的匿名类,而匿名类使用方法参数n
我的问题是:有没有可能从后一个例子中得到AssertionError
?如果有,什么是使其完全不变并消除问题的最佳方法?如果它是以下面这样的函数方式编写的,它会改变什么吗
class IHolderFactory {
static IHolder create(int n) {
return () -> {
if (n != n)
throw new AssertionError("This statement is false.");
};
}
}
# 1 楼答案
这是一个非常棘手的问题
JLS, §17.4.1. Shared Variables说:
这似乎与您可以在线程之间共享的内部类或lambda表达式中使用它们的事实相矛盾,但这些构造捕获变量的值并使用该值。然而,这个过程并没有得到很好的说明
我能找到的唯一一件事是在§15.27.2中解释(有效的)最终要求:
实际上,捕获的值存储在内部类或运行时为lambda表达式生成的类的合成
final
字段中。因此,您永远不会看到当前实现的错误然而,这一点在任何地方都没有具体说明。语言规范说明了little about the bytecode format,而虚拟机规范几乎没有说明语言结构
因此,局部变量、形式化方法参数和异常处理程序参数被明确地从JMM中排除,它们捕获的值在JMM中不是变量,甚至没有在JMM中提及。问题是这意味着什么
它们通常不会受到数据竞争(我的解释)的影响,还是不安全,而且我们根本没有从JMM得到任何保证?在后一种情况下,这甚至意味着我们无法使它们安全,因为任何安全的发布机制都是从JMM的担保中获得安全的,JMM的担保不包括我们的情况。值得注意的是,JMM也不包括外部
虽然我认为它们对数据竞争有免疫力,但我希望指定更为明确。this
引用,也不包括实例对getClass()
返回的Class
对象的隐式引用