Caffeine 结合了调度程序和执行程序服务

Roi*_*eck 2 java caching java-8 caffeine-cache

我在以下配置中使用咖啡因:

    Cache<String, String> cache = Caffeine.newBuilder()
                .executor(newWorkStealingPool(15))
                .scheduler(createScheduler())
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .maximumSize(MAXIMUM_CACHE_SIZE)
                .removalListener(this::onRemoval)
                .build();


    private Scheduler createScheduler() {
        return forScheduledExecutorService(newSingleThreadScheduledExecutor());
    }
Run Code Online (Sandbox Code Playgroud)

我是否正确地假设该onRemoval方法将在 ForkJoinPool 上执行newWorkStealingPool(15),并且只会调用调度程序来查找需要驱逐的过期条目?

这意味着它会像这样:

  1. 调用单线程调度程序(每约 1 秒)
  2. 找到所有要驱逐的过期条目
  3. newWorkStealingPool(15)对缓存构建器中的定义中的每个被逐出的条目执行 onRemoval 吗?

我没有找到解释此行为的文档,所以我在这里询问

总氮

Ben*_*nes 5

您的假设很接近,只是在实践中稍微优化了一些。

  1. 缓存读取和写入在底层哈希表上执行,并附加到内部环形缓冲区。
  2. 当缓冲区达到阈值时,就会提交一个任务来Caffeine.executor调用Cache.cleanUp
  3. 当此维护周期运行时(在锁定状态下),
    • 缓冲区被耗尽并根据逐出策略重播事件(例如 LRU 重新排序)
    • 任何可驱逐的条目都将被丢弃,并且任务将被提交给Caffeine.executorcall RemovalListener.onRemoval
    • 计算下一个条目到期之前的持续时间并将其提交给调度程序。这是由定速器保护的,因此通过确保计划任务之间发生约 1 秒来避免过度调度。
  4. 当调度程序运行时,将提交一个任务来Caffeine.executor调用Cache.cleanUp(请参见#3)。

调度程序执行最少量的工作,所有处理都会推迟到执行程序。由于使用 O(1) 算法,维护工作很便宜,因此根据使用活动,维护工作可能经常发生。它针对小批量工作进行了优化,因此计划调用之间强制执行约 1 秒的延迟有助于每次调用捕获更多工作。如果下一个过期事件发生在遥远的将来,那么调度程序在此之前不会运行,尽管调用线程可能会由于其在缓存上的活动而触发维护周期(请参阅#1,2)。