java - 匿名类可以完全不可变吗?

标签 java multithreading concurrency language-lawyer

在 Java Concurrency In Practice 一书中,有一个几乎不可变对象(immutable对象)的示例,如果没有正确发布,就有失败的风险:

// 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);
    }
}
如果我正确理解书中的章节,添加finaln Holder 的字段类将使对象完全不可变并消除获得 AssertionError 的机会即使它仍然在没有足够同步的情况下发布,就像它在 Client 中所做的那样,也会被抛出。类(class)。
现在我想知道匿名类在这方面的表现如何。请看下面的例子:
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类变成了接口(interface),有一个静态工厂方法返回实现接口(interface)的匿名类,匿名类使用方法参数n .
我的问题是:是否有机会获得 AssertionError从我的后一个例子抛出?如果有,使其完全不可变并消除问题的最佳方法是什么?如果它是以如下的功能方式编写的,它会改变什么吗?
class IHolderFactory {
    static IHolder create(int n) {
        return () -> {
            if (n != n)
                throw new AssertionError("This statement is false.");
        };
    }
}

最佳答案

这是一个非常棘手的问题。
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.


在实践中,捕获的值存储在合成 final 中。内部类或在运行时为 lambda 表达式生成的类的字段。因此,您将永远不会看到当前实现的错误。
然而,这并没有在任何地方指定。语言规范说 little about the bytecode format并且虚拟机规范对语言结构几乎没有说明。
因此,局部变量、形式方法参数和异常处理程序参数被明确排除在 JMM 之外,它们捕获的值在 JMM 方面不是变量,甚至没有被提及。问题是这意味着什么。
它们通常不受数据竞争的影响(我的解释)还是它们不安全并且我们根本没有从 JMM 那里得到任何保证?在后一种情况下,它甚至暗示我们无法保证它们的安全,因为任何安全的发布机制都可以从不包括我们的情况的 JMM 保证中获得安全性。值得注意的是,JMM 也没有覆盖外部 this引用也不是实例对 Class 的隐式引用getClass() 返回的对象.
因此,虽然我认为它们不受数据竞争的影响,但我希望能更明确地说明这一点。

关于java - 匿名类可以完全不可变吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66652250/

相关文章:

java - 使用多线程向 ArrayList 添加元素时,有时会给出 ConcurrentModificationException,有时则不会?

java - Java 中的线程状态机

java - 不同 JVM 之间共享的映射

c# - 在 C# 2.0+ 中,是否有必要锁定另一个线程将执行的闭包?

multithreading - (优化?)关于 GCC std::thread 的错误

go - sync.Map 是这个用例的正确工具吗?或者是 RW 互斥锁更好

java - 正则表达式仅在字符串之后匹配重复模式

java - 如何选择JTable的所有行?

java - Eclipse 补丁文件不适用于具有不同名称的同一项目

java - 两个线程写入同一个文件