同步块中的易失性变量赋值为null

时间:2017-11-27 17:24:41

标签: java lambda concurrency volatile

这是一个班级:

public class Refreshable<T> {
    private final Object lock = new Object();

    private final Supplier<Object>[] dynamicSettings;
    private final Supplier<T> constructor;

    private volatile Object[] instanceKey;
    private volatile T instance;

    @SafeVarargs
    public Refreshable(Supplier<T> constructor, Supplier<Object>... dynamicSettings) {
        this.constructor = constructor;
        this.dynamicSettings = dynamicSettings;
    }

    public T getInstance() {
        Object[] currentKey = getCurrentKey();
        if (!Arrays.equals(currentKey, instanceKey)) {
            synchronized (lock) {
                if (!Arrays.equals(currentKey, instanceKey)) {
                    instanceKey = currentKey;
                    instance = constructor.get();
                }
            }
        }
        return instance;
    }

    private Object[] getCurrentKey() {
        return Arrays.stream(dynamicSettings)
                .map(Supplier::get)
                .toArray();
    }
}

这个想法在名称中:当与其创建相关的参数值发生变化时返回新实例。 constructor始终是包装构造函数的lambda - 它不能返回null。但它可以抛出异常 在我们的项目中使用如下:

new Refreshable<Service>(
    () -> new Service(settings.getParam1(), settings.getParam2()),
    settings::getParam1,
    settings::getParam1);

settings返回的参数值可能随时间而变化。

长话短说,在一些负载测试下getInstance()在第一次调用时返回多个线程(在同一秒内有10个或更多个线程),返回如下所示的行:serviceRefreshable.getInstance().someMethod()。所以它是可刷新的null,或getInstance()的结果,以及错误在运行的后期没有重复的事实表明后者,因为refreshable只分配一次(它是一个单例bean)。

我理解constructor可以抛出异常并阻止分配instance,但它不在日志中(而NPE是)。

Arrays.equals()此处无法意外返回true,因为instanceKey最初为null且getCurrentKey()无法返回null。所有dynamicSettings个供应商都是包含此调用的lambda:

private <T> T getValue(String key, Class<T> type) {
    Object value = properties.get(key);
    if (value == null) {
        throw new ConfigurationException("No such setting: " + key);
    }
    if (!type.isInstance(value)) {
        throw new ConfigurationException("Wrong setting type" + key);
    }
    return (T) value;
}

这怎么可能?

我也理解通过将instance和instanceKy包装在一个对象中并将2个赋值减少到一个以使它们成为原子,以及检查instance == null以及比较数组,可以防止这种情况发生。 我不明白的是,导致constructor失败的异常导致地狱去了:)。

我甚至编辑了生产代码,在提供给其中一个constructor的{​​{1}} lambda中抛出RuntimeException。果然,我在日志中看到了异常,而不是NPE。没有任何类型的并发负载,但

0 个答案:

没有答案