有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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) 个答案

  1. # 1 楼答案

    这是一个非常棘手的问题

    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.

    这似乎与您可以在线程之间共享的内部类或lambda表达式中使用它们的事实相矛盾,但这些构造捕获变量的值并使用该值。然而,这个过程并没有得到很好的说明

    我能找到的唯一一件事是在§15.27.2中解释(有效的)最终要求:

    The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.

    实际上,捕获的值存储在内部类或运行时为lambda表达式生成的类的合成final字段中。因此,您永远不会看到当前实现的错误

    然而,这一点在任何地方都没有具体说明。语言规范说明了little about the bytecode format,而虚拟机规范几乎没有说明语言结构

    因此,局部变量、形式化方法参数和异常处理程序参数被明确地从JMM中排除,它们捕获的值在JMM中不是变量,甚至没有在JMM中提及。问题是这意味着什么

    它们通常不会受到数据竞争(我的解释)的影响,还是不安全,而且我们根本没有从JMM得到任何保证?在后一种情况下,这甚至意味着我们无法使它们安全,因为任何安全的发布机制都是从JMM的担保中获得安全的,JMM的担保不包括我们的情况。值得注意的是,JMM也不包括外部this引用,也不包括实例对getClass()返回的Class对象的隐式引用

    虽然我认为它们对数据竞争有免疫力,但我希望指定更为明确。