Извините за длинный вопрос, мне нужно представить окружение, иначе вы можете неправильно понять мою проблему.
Текущее состояние
У меня есть менеджер кэша , который дляданный объект класса K возвращает держатель, параметризованный типом V, представляющий значение, связанное с веб-службой, соответствующему K.
Holder
Классы Holder управляют извлечением, синхронизацией,и планирование следующей выборки, потому что кэш предназначен для нескольких параллельных вызовов.Данные, извлеченные веб-службой, имеют дату истечения срока действия (указана в заголовке), после чего владелец может извлечь их снова и снова планирует для следующего истечения срока действия.У меня есть 3 класса (для списка, карты и других), но все они используются одинаково.Класс Holder имеет 5 методов, 2 для прямого доступа и 3 для доступа IoC
- void waitData () ожидает, пока данные не будут выбраны хотя бы один раз.Внутренне is использует countdownlatch.
- V copy () ожидает выборки данных хотя бы один раз, затем возвращает копию кэшированного V. Простые элементы возвращаются, как они есть, в то время как более сложные (например, Mapцены в данном магазине, на которые ссылается идентификатор мебели), копируются в синхронизированном цикле (чтобы избежать другого извлечения () для повреждения данных)
- void follow (JavaFX.Listener ) регистрирует нового слушателяV будет уведомлено об изменениях в данных владельца.Если владелец уже получил данные, слушатель уведомляется об этих данных, как будто они новые.
- void unfollow (JavaFX.Listener ) отменяет регистрацию предварительно зарегистрированного слушателя.
- Наблюдаемый asObservable() возвращает наблюдаемый.Это позволяет использовать его, например, в графическом интерфейсе javafx.
Как правило, это позволяет мне выполнять такие вещи, как потоковая передача нескольких данных параллельно с достаточным временем, например
Stream.of(1l, 2l, 3l).parallel().map(cache::getPrice).mapToInt(p->p.copy().price).min();
или выполнятьГораздо более сложные привязки в javafx, например, когда цена зависит от количества предметов, которые вы хотите приобрести
Self Scheduling
Класс держателя содержит объект SelfScheduling , который отвечает зана самом деле получить данные, поместить их в держатель и перепланировать себя после истечения срока действия данных.
SelfScheduling использует ScheduledExecutorService в кэше, чтобы запланировать свой собственный метод fetch ().Она начинается с планирования самого себя через 0 мс, повторного планирования через 10 с в случае ошибки или после истечения срока действия, если были получены новые данные.Он может быть приостановлен, возобновлен, запущен при создании и может быть остановлен.
Это поведение, которое я хочу изменить. Я хочу, чтобы самоисполнитель удалил Holder из кэшапо истечении срока, если держатель не используется нигде в коде
Диспетчер кэша
Просто для информации, мой диспетчер кэша состоит из Map > cachedPrices для храненияданные кеша и метод getPrice (K), который синхронизирует кеш, если держатель отсутствует, создайте держатель, если требуется (двойная проверка, чтобы избежать ненужной синхронизации), и верните держатель.
Global Code
Вот пример того, как выглядит мой код
public class CacheExample {
public static class Holder<T>{
SimpleObjectProperty<T> data = new SimpleObjectProperty<>();
// real code removed
T copy() {
return null;
}
Observable asObservable() {
return null;
}
void follow(ChangeListener<? super T> listener) {
}
}
public static class SelfScheduled implements Runnable {
// should use enum
private Object state = "start";
public void schedule(long ms) {
// check state, sync, etc.
}
@Override
public void run() {
long next = fetch();
schedule(next);
}
public long fetch() {
// set the value in the holder
// return the next expiry
return 0;
}
}
public Map<Long, Holder<Object>> cachePrices = new HashMap<>();
public Holder<Object> getPrice(long param) {
Holder<Object> ret = cachePrices.get(param);
if (ret == null) {
// sync, re check, etc.
synchronized (cachePrices) {
ret = cachePrices.get(param);
if (ret == null) {
ret = new Holder<>();
// should be the fetch() call instead of null
makeSchedule(ret.data, null);
}
}
}
return ret;
}
public void makeSchedule(SimpleObjectProperty<Object> data, Runnable run) {
// code removed.
// creates a selfscheduler with fetch method and the data to store the
// result.
}
}
Ожидаемые изменения
Как я писал выше, я хочу изменить способ хранения данных в памяти в кеше.В частности, я не вижу смысла поддерживать огромное количество объектов самопланирования для извлечения данных, когда эти данные больше не используются.Если срок действия равен 5 с (некоторые веб-сервисы ARE), и я кеширую 1000 данных (это очень низкое значение), то это означает, что я буду делать 200 fetch () в секунду без всякой причины.
Что я ожидаюв том, что когда Holder больше не используется, самопланирование останавливается и вместо извлечения данных фактически удаляет держатель из кэша.пример:
Holder< Price > p = cache.getPrice(1);
// here if the fetch() is called it should fetch the data
p.copy().price;
// now the price is no more used, on next fetch() it should remove p from the cache.
// If that happens, and later I re enter that code, the holder and the selfscheduler will be re created.
Holder< Price > p2 = cache.getPrice(22);
mylist.add(p2);
// now there is a strong reference to this price, so the fetch() method will keep scheduling the selfscheduler
// until mylist is no more strongly referenced.
Неверно
Однако мои знания адекватных технологий ограничены в этой области.Насколько я понял, я должен использовать слабую ссылку в диспетчере кеша и самопланирование, чтобы знать, когда на держатель больше нет сильных ссылок (обычно запускают fetch (), проверяя, стала ли ссылка нулевой, и в этом случае просто остановите);Однако это приведет к тому, что держатель будет GC'd ДО следующего истечения срока, что мне не нужно: некоторые данные имеют очень длительный срок действия и используются только в простом методе, например cache.getShopLocation () не должен быть GC'dсразу после использования значения, возвращенного функцией copy ().
Таким образом, этот код неверен:
public class CacheExampleIncorrect {
public static class Holder<T>{
SimpleObjectProperty<T> data = new SimpleObjectProperty<>();
// real code removed
T copy() {
return null;
}
Observable asObservable() {
return null;
}
void follow(ChangeListener<? super T> listener) {
}
}
public static class SelfScheduled<T> implements Runnable {
WeakReference<Holder<T>> holder;
Runnable onDelete;
public void schedule(long ms) {
// check state, sync, etc.
}
@Override
public void run() {
Holder<T> h = holder.get();
if (h == null) {
onDelete.run();
return;
}
long next = fetch(h);
schedule(next);
}
public long fetch(Holder<T> h) {
// set the value in the holder
// return the next expiry
return 0;
}
}
public Map<Long, WeakReference<Holder<Object>>> cachePrices = new HashMap<>();
public Holder<Object> getPrice(long param) {
WeakReference<Holder<Object>> h = cachePrices.get(param);
Holder<Object> ret = h == null ? null : h.get();
if (h == null) {
synchronized (cachePrices) {
h = cachePrices.get(param);
ret = h == null ? null : h.get();
if (ret == null) {
ret = new Holder<>();
h = new WeakReference<>(ret);
// should be the fetch() call instead of null
SelfScheduled<Object> sched = makeSchedule(h, null);
cachePrices.put(param, h);
// should be synced on cachedprice
sched.onDelete = () -> cachePrices.remove(param);
}
}
}
return ret;
}
public <T> SelfScheduled<T> makeSchedule(WeakReference<Holder<Object>> h, Runnable run) {
// creates a selfscheduler with fetch method and the data to store the
// result.
return null;
}
}