确保缓存在其自身

时间:2015-11-10 06:01:54

标签: java android multithreading concurrency rx-java

我构建了一个通用缓存,通过委托给ValueGenerator组件来获取未命中的值。在内部,它有一个已经获得的值的映射,以及另一个用于飞行中请求的映射,以便它们可以在订阅者之间重复使用。

这是我尝试使其线程安全之前的简化代码。我的问题将随之而来。

public class NetworkCache<K, V> {
    private final Map<K, V> mValues;
    private final Map<K, Observable<V>> mRequests;

    private final ValueGenerator<K, V> mValueGenerator;

    public NetworkCache(ValueGenerator<K, V> valueGenerator) {
        mValues = new HashMap<>();
        mRequests = new HashMap<>();
        mValueGenerator = valueGenerator;
    }

    public Observable<V> get(final K key) {
        V value = mValues.get(key);

        if (value != null) {
            // We already had the value
            return Observable.just(value);
        }

        Observable<V> requestObservable = mRequests.get(key);

        if (requestObservable == null) {
            // New request to fetch the value
            requestObservable = mValueGenerator.generate(key);

            // Store in-flight request for potential re-use
            mRequests.put(key, requestObservable);

            requestObservable.subscribe(new Subscriber<V>() {
                @Override
                public void onCompleted() { mRequests.remove(key); }

                @Override
                public void onError(Throwable e) { mRequests.remove(key); }

                @Override
                public void onNext(V value) { mValues.put(key, value); }
            });
        }

        return requestObservable;
    }

    public interface ValueGenerator<K, V> {
        Observable<V> generate(K key);
    }
}

现在,我正在考虑如何在并发场景下破解这种情况。我认为重点应放在Map中查询并在get()回调中进行修改的两个subscribe

我认为假设/强制执行此类只能在主线程上调用是合理的。但是,ValueGenerator应该能够在不同的线程上安排其工作,因为我的用例实际上是网络请求。

我看到3个选项,我希望帮助找出使用哪个选项。

1。使用ConcurrentHashMap代替HashMap

构造函数将更改为:

    public NetworkCache(ValueGenerator<K, V> valueGenerator) {
        mValues = new ConcurrentHashMap<>();
        mRequests = new ConcurrentHashMap<>();
        mValueGenerator = valueGenerator;
    }

通过这种方法,我不知道它是否足够和/或是否过度。

2。观察主线程上的ValueGenerator调用

对我来说,这意味着所有地图操作都会在主线程上发生(假设NetworkCache仅在那里使用),即使ValueGenerator使用了subscribeOn(Schedulers.io())。这意味着它是线程安全的。

if (requestObservable == null) {
    // New request to fetch the value
    requestObservable = mValueGenerator
        .generate(key)
        .observeOn(AndroidSchedulers.mainThread());
...

3。同步对地图的每次访问

我将继续使用HashMapget方法将成为以下方法。在这里,正确的方法是在地图本身上同步吗?我是否需要阻止每次操作,或只是put&amp; remove

public Observable<V> get(final K key) {
    V value;

    synchronized (mValues) {
        value = mValues.get(key);
    }

    if (value != null) {
        return Observable.just(value);
    }

    Observable<V> requestObservable;

    synchronized (mRequests) {
        requestObservable = mRequests.get(key);
    }

    if (requestObservable == null) {
        requestObservable = mValueGenerator.generate(key);

        synchronized (mRequests) {
            mRequests.put(key, requestObservable);
        }

        requestObservable.subscribe(new Subscriber<V>() {
            @Override
            public void onCompleted() {
                synchronized (mRequests) {
                    mRequests.remove(key);
                }
            }

            @Override
            public void onError(Throwable e) {
                synchronized (mRequests) {
                    mRequests.remove(key);
                }
            }

            @Override
            public void onNext(V value) {
                synchronized (mValues) {
                    mValues.put(key, value);
                }
            }
        });
    }

    return requestObservable;
}

关于利用率的一些背景知识:对于不同的密钥,缓存的get方法将被快速连续1-10次调用。那个事件很少发生,但可能会在几秒钟内发生。当第二系列的呼叫到来时,与第一系列的观察结果混合在一起,我担心执行。

2 个答案:

答案 0 :(得分:1)

我会使用一个ConcurrentMapAsyncSubject

执行此操作
public class RequestCache<K, V> {
    final ConcurrentMap<K, AsyncSubject<V>> values;
    final Function<? super K, ? extends Observable<? extends V>> valueGenerator;

    public RequestCache(
            Function<? super K, ? extends Observable<? extends V>> valueGenerator) {
        this.values = new ConcurrentHashMap<>();
        this.valueGenerator = valueGenerator;
    }

    public Observable<V> get(K key) {
        AsyncSubject<V> result = values.get(key);
        if (result == null) {
            result = AsyncSubject.create();

            AsyncSubject<V> current = values.putIfAbsent(key, result);
            if (current == null) {
                Observable<? extends V> source = valueGenerator.apply(key);

                source.subscribe(result);
            } else {
                result = current;
            }
        }

        return result;
    }
}

这完全是线程安全的,并且每个键仅调用valueGenerator一次。

答案 1 :(得分:0)

我认为你应该同步整个getter和setter函数。