我有这个从REST API获取电影的FilmStore。
class ApiFilmsStore(private val tmdApi: TmdApi, private val converter: ApiFilmToFilmConverter) : FilmsStore {
override fun get(filmId: String): Observable<Film> {
return tmdApi.filmById(filmId).flatMap { apiFilm -> Observable.just(converter.convert(apiFilm)) }
.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
}
}
我还有另外一个装饰器,如果存在,则从缓存中返回数据:
class CacheableFilmsStore(private val origin: FilmsStore) : FilmsStore {
private val cache = ReactiveCache<Film>()
override fun get(filmId: String): Observable<Film> {
return cache[filmId].onErrorResumeNext({
origin[filmId].doOnNext { film ->
cache.put(filmId, film)
}
})
}
}
我有这个类ReactiveCache来管理内存数据:
class ReactiveCache<T> {
private val cache = LruCache<String, T>(4 * 1024 * 1024) //4MiB
operator fun get(key: String): Observable<T> {
return Observable.create { subscriber ->
//synchronized (this) { It doesn't work
val value = cache[key]
if (value == null) {
subscriber.onError(KeyNotExistsException())
} else {
subscriber.onNext(value)
subscriber.onCompleted()
}
//}
}
}
fun put(key: String, value: T) {
cache.put(key, value)
}
}
问题是存在竞争条件。当应用程序启动时,两个片段会检索同一部电影,并且两者都是从API获取电影,因为当第二部分要获得电影时,它还没有被放入缓存中。
我怎么能同步?我试图放synchronized
,但它不起作用。
答案 0 :(得分:0)
一种可能性是缓存要多播。
可能有更好的做法,但这是一个粗略的想法(请原谅Java):
<强> CacheableFilmsStore:强>
// A map since you'll likely want to separate this by key.
Map<String, Observable<T>> observableMap = new HashMap<>();
...get(...) {
if (observableMap.containsKey(key)) {
return observableMap.get(key);
} else {
Observable<T> observable = Observable.create(...)
.publish() // distributes results to all active subscribers
.refCount(); // cleans up resources established by `publish()`
observableMap.put(key, observable);
return observable;
}
}
方案:
如果此密钥的结果尚未在您的LruCache中,则两个片段最终都应该只收听一个API调用,而不是两个。
如果进行后续提取,则会立即点击您的缓存并完成。
当源观察结果完成(并且所有订阅者都收到其OnComplete)时,refCount
将导致ConnectableObservable
创建的publish()
断开连接并清除up - 但请注意它仍然可用。下次Observable对象(由refCount()
创建)订阅时,它将只是重新连接。