Анализ исходного кода Guava (принцип кэширования [второй этап])

Java задняя часть исходный код опрос
Анализ исходного кода Guava (принцип кэширования [второй этап])

предисловие

над "Анализ исходного кода Guava (принцип кэширования)проанализировано вGuava Cacheродственные принципы.

упоминается в концеМеханизм рециркуляции, уведомление о времени удаленияДругой контент, многие друзья тоже очень заинтересованы, на этот раз я буду анализировать и анализировать эти два контента.

Прежде чем начать, давайте изучим две функции, которые поставляются с Java, и у Guava есть определенные приложения.

Ссылки в Java

Ява в первую очередьЦитировать.

Общая JVM раньше была основана наАлгоритмы анализа достижимостиНайдите объект, который необходимо переработать, и оцените, является ли статус выживания объекта таким же, как引用Связанный.

До JDK 1.2 это было очень просто: состояние объекта было толькоЦитироватьа такжене цитируетсяДва отличия.

Такое деление не очень удобно для сборки мусора, потому что всегда будут какие-то объекты, состояние которых находится между ними.

Поэтому после версии 1.2 были добавлены четыре новых состояния для более тонкого разделения ссылочных отношений:

  • Сильная ссылка: этот тип объекта является наиболее распространенным, например, **A a = new A();** Это типичная сильная ссылка; такая сильная ссылочная связь не может быть удалена сборщиком мусора.
  • Мягкая ссылка: такая ссылка указывает на какой-то полезный, но ненужный объект, который необходимо удалить, прежде чем произойдет сборка мусора.
  • Слабая ссылка (Weak Reference): это ссылочное отношение, которое слабее, чем мягкая ссылка, а также хранит второстепенные объекты. При сборке мусора такие объекты собираются независимо от того, достаточно ли текущей памяти.
  • Фантомная ссылка: это самая слабая ссылочная связь, и получить объект по ссылке невозможно, его единственная функция — уведомлять, когда он перерабатывается.

обратный вызов события

Обратный вызов события на самом деле является распространенным шаблоном проектирования, таким как упомянутый ранее.NettyИспользуется эта конструкция.

Вот демо, попробуйте следующие функции:

  • Звонящий задает уведомителю вопрос.
  • Способ задавать вопросы асинхронный, а затем делать что-то еще.
  • Уведомитель получает проблему и выполняет расчет, а затем перезванивает вызывающей стороне, чтобы сообщить результат.

Обратные вызовы реализованы с использованием интерфейсов в Java, поэтому вам необходимо определить интерфейс:

public interface CallBackListener {

    /**
     * 回调通知函数
     * @param msg
     */
    void callBackNotify(String msg) ;
}

Вызывающий вызывает Notifier для выполнения вопроса и передает интерфейс при вызове:

public class Caller {

    private final static Logger LOGGER = LoggerFactory.getLogger(Caller.class);

    private CallBackListener callBackListener ;

    private Notifier notifier ;

    private String question ;

    /**
     * 使用
     */
    public void call(){

        LOGGER.info("开始提问");

		//新建线程,达到异步效果 
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    notifier.execute(Caller.this,question);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        LOGGER.info("提问完毕,我去干其他事了");
    }
    
    //隐藏 getter/setter
    
}    

Notifier получает вопрос, выполняет вычисления (операция, занимающая много времени) и, наконец, отвечает (интерфейс обратного вызова, сообщающий вызывающей стороне результат).

public class Notifier {

    private final static Logger LOGGER = LoggerFactory.getLogger(Notifier.class);

    public void execute(Caller caller, String msg) throws InterruptedException {
        LOGGER.info("收到消息=【{}】", msg);

        LOGGER.info("等待响应中。。。。。");
        TimeUnit.SECONDS.sleep(2);


        caller.getCallBackListener().callBackNotify("我在北京!");

    }

}

Имитация выполнения:

    public static void main(String[] args) {
        Notifier notifier = new Notifier() ;

        Caller caller = new Caller() ;
        caller.setNotifier(notifier) ;
        caller.setQuestion("你在哪儿!");
        caller.setCallBackListener(new CallBackListener() {
            @Override
            public void callBackNotify(String msg) {
                LOGGER.info("回复=【{}】" ,msg);
            }
        });

        caller.call();
    }

Окончательный результат выполнения:

2018-07-15 19:52:11.105 [main] INFO  c.crossoverjie.guava.callback.Caller - 开始提问
2018-07-15 19:52:11.118 [main] INFO  c.crossoverjie.guava.callback.Caller - 提问完毕,我去干其他事了
2018-07-15 19:52:11.117 [Thread-0] INFO  c.c.guava.callback.Notifier - 收到消息=【你在哪儿!】
2018-07-15 19:52:11.121 [Thread-0] INFO  c.c.guava.callback.Notifier - 等待响应中。。。。。
2018-07-15 19:52:13.124 [Thread-0] INFO  com.crossoverjie.guava.callback.Main - 回复=【我在北京!】

Такой смоделированный обратный вызов асинхронного события завершается.

Использование гуавы

Guava использует две вышеупомянутые функции для достиженияПереработка цитата такжеУведомление об удалении.

Цитировать

Можно использовать при инициализации кеша:

  • CacheBuilder.weakKeys()
  • CacheBuilder.weakValues()
  • CacheBuilder.softValues()

Для определения эталонной связи между ключами и значениями.

В приведенном выше анализе видно, что кэшReferenceEntryЭто похоже на Entry HashMap для хранения данных.

Давайте посмотрим на определение ReferenceEntry:

  interface ReferenceEntry<K, V> {
    /**
     * Returns the value reference from this entry.
     */
    ValueReference<K, V> getValueReference();

    /**
     * Sets the value reference for this entry.
     */
    void setValueReference(ValueReference<K, V> valueReference);

    /**
     * Returns the next entry in the chain.
     */
    @Nullable
    ReferenceEntry<K, V> getNext();

    /**
     * Returns the entry's hash.
     */
    int getHash();

    /**
     * Returns the key for this entry.
     */
    @Nullable
    K getKey();

    /*
     * Used by entries that use access order. Access entries are maintained in a doubly-linked list.
     * New entries are added at the tail of the list at write time; stale entries are expired from
     * the head of the list.
     */

    /**
     * Returns the time that this entry was last accessed, in ns.
     */
    long getAccessTime();

    /**
     * Sets the entry access time in ns.
     */
    void setAccessTime(long time);
}

Содержит множество часто используемых операций, таких как ссылка на значение, ссылка на ключ, время доступа и т. д.

согласно сValueReference<K, V> getValueReference();Реализация:

Различные реализации с сильными и слабыми ссылками.

То же самое верно и для ключей:

При использовании этой конструкции слабоссылочные ключи и значения удаляются сборщиком мусора.

Конечно, мы также можем явно переработать:

  /**
   * Discards any cached value for key {@code key}.
   * 单个回收
   */
  void invalidate(Object key);

  /**
   * Discards any cached values for keys {@code keys}.
   *
   * @since 11.0
   */
  void invalidateAll(Iterable<?> keys);

  /**
   * Discards all entries in the cache.
   */
  void invalidateAll();

Перезвоните

Изменил предыдущий пример:

loadingCache = CacheBuilder.newBuilder()
        .expireAfterWrite(2, TimeUnit.SECONDS)
        .removalListener(new RemovalListener<Object, Object>() {
            @Override
            public void onRemoval(RemovalNotification<Object, Object> notification) {
                LOGGER.info("删除原因={},删除 key={},删除 value={}",notification.getCause(),notification.getKey(),notification.getValue());
            }
        })
        .build(new CacheLoader<Integer, AtomicLong>() {
            @Override
            public AtomicLong load(Integer key) throws Exception {
                return new AtomicLong(0);
            }
        });

Результаты:

2018-07-15 20:41:07.433 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 当前缓存值=0,缓存大小=1
2018-07-15 20:41:07.442 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 缓存的所有内容={1000=0}
2018-07-15 20:41:07.443 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - job running times=10
2018-07-15 20:41:10.461 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 删除原因=EXPIRED,删除 key=1000,删除 value=1
2018-07-15 20:41:10.462 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 当前缓存值=0,缓存大小=1
2018-07-15 20:41:10.462 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 缓存的所有内容={1000=0}

Видно, что при удалении кеша наша кастомная функция будет вызвана обратно и будет сообщена причина удаления.

Так как же Гуава это делает?

По данным LocalCachegetLiveValue()Когда будет установлено, что срок действия кеша истек, он будет следовать отношениям вызова здесь:

removeValueFromChain()середина:

enqueueNotification()Метод перенесет повторно использованный кеш (включая ключ, значение) и причину повторного использования в ранее определенный интерфейс события и добавит его влокальная очередьсередина.

Таким образом, при инициализации не возникает события обратного вызова.

Однако учащиеся, которые использовали очереди, должны быть в состоянии догадаться, что, поскольку очереди написаны здесь, должно быть потребление.

Вернемся к кэшу:

Выполнено в наконецpostReadCleanup()метод; на самом деле, он потребляет очередь только сейчас:

Если вы продолжите следить, вы обнаружите, что очередь здесь потребляется, а ранее упакованное сообщение об удалении вызывается для нашего пользовательского события, таким образом завершая обратный вызов события.

Суммировать

Весь приведенный выше исходный код:

GitHub.com/crossover J я…

Анализируя исходный код Guava, мы можем изучить лучшие методы проектирования и реализации и даже попытаться написать его самостоятельно.

В Гуаве еще много мощных улучшений, которые стоит изучить еще раз.

Дополнительный

Недавно я обобщил некоторые знания, связанные с Java, и заинтересованные друзья могут поддерживать их вместе.

адрес:GitHub.com/crossover J я…

Добро пожаловать, чтобы обратить внимание на публичный аккаунт, чтобы общаться вместе: