Java 我的ExpirableLazyObject不是线程安全的吗?有比赛条件吗?

Java 我的ExpirableLazyObject不是线程安全的吗?有比赛条件吗?,java,multithreading,concurrency,parallel-processing,Java,Multithreading,Concurrency,Parallel Processing,不久前,我创建了一个Java类,它将惰性初始化封装到LazyObect中。它维护并发性和线程安全性,还提供了清除值的reset()方法 public final class LazyObject<T> { private volatile T value; private volatile boolean updated; private final Supplier<T> supplier; private LazyObject(Su

不久前,我创建了一个Java类,它将惰性初始化封装到
LazyObect
中。它维护并发性和线程安全性,还提供了清除值的
reset()
方法

public final class LazyObject<T> {

    private volatile T value;
    private volatile boolean updated;
    private final Supplier<T> supplier;

    private LazyObject(Supplier<T> supplier) {
        this.supplier = supplier;
    }
    public T get() {
        if (!updated) {
            synchronized(this) {
                if (!updated) {
                    value = supplier.get();
                    updated = true;
                }
            }
        }
        return value;
    }
    public void reset() {
        if (updated) {
            synchronized(this) {
                if (updated) {
                    updated = false;
                    value = null;
                }
            }
        }
    }
    public static <B> LazyObject<B> forSupplier(Supplier<B> supplier) {
        return new LazyObject<B>(supplier);
    }
}
它需要一个
供应商
和一些必要的参数来安排值的销毁。每次调用
get()
时,它都会延迟销毁。当然,我可以设计强制客户机通过GC处理值的创建和销毁,但我喜欢在内部管理实例的API

这样做的好处是,我可以将缓存对象保持足够长的时间以支持操作,并且可以自动地、定期地刷新参数

然而,我无法摆脱
get()
方法可能存在竞争条件的感觉,但我无法找出确切的原因。我不知道我是否需要一些同步块,或者我是否没有正确地识别原子性。但我为消除顾虑而进行的每个同步块都会极大地破坏并发性或引入新的竞争条件。我能看到的防止任何竞争条件(如果存在竞争条件)的唯一方法是同步整个方法。但这将破坏并发性。这里真的有问题吗

更新
已经确定存在比赛条件。我想我对如何解决这个问题有一些想法,但我想听听能有效实现这一点并最大化并发性的建议。

是的,存在竞争条件

T1:

T2:

T1:

T2:

在最后一步中,使用新值覆盖
scheduledRemoval
,而不取消该未来。任何后续调用将只看到
ENF
,而
NF
将不可访问且不可取消(但仍处于活动状态)

最简单的解决方案是通过原子参考:

private AtomicReference<ScheduledFuture<?>> scheduledRemoval;

public T get() { 
    ScheduledFuture<?> next = executor.schedule(() -> value.reset(), expirationDelay, timeUnit);
    ScheduledFuture<?> current = scheduledRemoval.getAndSet( next );
    if (current != null) {
            current.cancel(true);
    }

    T returnVal = value.get();

    return returnVal;
}
private AtomicReference next=executor.schedule(()->value.reset(),expirationDelay,timeUnit);
ScheduledFuture current=scheduledRemoval.getAndSet(下一步);
如果(当前!=null){
当前。取消(true);
}
T returnVal=value.get();
返回值;
}

请注意,您仍然可能遇到这样的情况:在您想要调用
current.cancel()
时,它已经被触发了。避免这种情况需要一些更复杂的信号,我不确定这是否值得。

是的,存在竞争条件

T1:

T2:

T1:

T2:

在最后一步中,使用新值覆盖
scheduledRemoval
,而不取消该未来。任何后续调用将只看到
ENF
,而
NF
将不可访问且不可取消(但仍处于活动状态)

最简单的解决方案是通过原子参考:

private AtomicReference<ScheduledFuture<?>> scheduledRemoval;

public T get() { 
    ScheduledFuture<?> next = executor.schedule(() -> value.reset(), expirationDelay, timeUnit);
    ScheduledFuture<?> current = scheduledRemoval.getAndSet( next );
    if (current != null) {
            current.cancel(true);
    }

    T returnVal = value.get();

    return returnVal;
}
private AtomicReference next=executor.schedule(()->value.reset(),expirationDelay,timeUnit);
ScheduledFuture current=scheduledRemoval.getAndSet(下一步);
如果(当前!=null){
当前。取消(true);
}
T returnVal=value.get();
返回值;
}

请注意,您仍然可能遇到这样的情况:在您想要调用
current.cancel()
时,它已经被触发了。避免这种情况需要一些更复杂的信号,我不确定这是否值得。

我知道这可能不是你想要听到的答案,但是你看过
SoftReference
类吗?不,我喜欢预建库。我会看看这是否解决了我的需要。但即使是这样,我也会好奇地阅读原始帖子的答案。我会想象,主要的竞争是你失去了对未来的引用(即,两个线程调用
get()
,两个
schedule()
,但只有最后一个未来被保存,因此不可能取消,并且
reset())
在计划的时间段到期时调用…)-那么,这是您想要的潜在竞争吗?这显然是您的选择,但是
SoftReference
的有用之处在于删除策略还考虑了剩余的空堆数量,而不仅仅是一个固定的计时器。是的,我现在正在研究它,这是一个引人注目的案例,只要它具有我需要的功能(或者可以围绕它创建)。稍后我可能会在上面发帖。我知道这可能不是你想听到的答案,但你看过
SoftReference
类吗?不,我喜欢预建库。我会看看这是否解决了我的需要。但即使是这样,我也会好奇地阅读原始帖子的答案。我会想象,主要的竞争是你失去了对未来的引用(即,两个线程调用
get()
,两个
schedule()
,但只有最后一个未来被保存,因此不可能取消,并且
reset())
在计划的时间段到期时调用…)-那么,这是您想要的潜在竞争吗?这显然是您的选择,但是
SoftReference
的有用之处在于删除策略还考虑了剩余的空堆数量,而不仅仅是一个固定的计时器。是的,我现在正在研究它,这是一个引人注目的案例,只要它具有我需要的功能(或者可以围绕它创建)。我以后可能会在上面发问,我就知道。我的直觉告诉了我这样的事情。基于这些信息,我想我必须同步整个
get()
方法。我说得对吗?我想不出别的办法来解决这个问题,除非。。。我在任务本身内部进行检查。也许我可以在任务取消之前传递一些标志或其他东西…好吧,太棒了,这正是我想要的。谢谢我就知道。我的直觉告诉了我这样的事情。基于这些信息,我想我必须同步整个
get()
方法。我说得对吗
    //cancel original future AGAIN 
    if (scheduledRemoval != null) {
            scheduledRemoval.cancel(true);
    }
//set new future (NF)
scheduledRemoval = executor.schedule(() -> value.reset(), expirationDelay, timeUnit);  
//set even newer future (ENF)
scheduledRemoval = executor.schedule(() -> value.reset(), expirationDelay, timeUnit);
private AtomicReference<ScheduledFuture<?>> scheduledRemoval;

public T get() { 
    ScheduledFuture<?> next = executor.schedule(() -> value.reset(), expirationDelay, timeUnit);
    ScheduledFuture<?> current = scheduledRemoval.getAndSet( next );
    if (current != null) {
            current.cancel(true);
    }

    T returnVal = value.get();

    return returnVal;
}