java Guava CacheBuilder 移除监听器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10626720/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 01:53:36  来源:igfitidea点击:

Guava CacheBuilder removal listener

javacachingguava

提问by mawia

Please show me where I'm missing something.

请告诉我我遗漏了什么。

I have a cache build by CacheBuilder inside a DataPool. DataPool is a singleton object whose instance various thread can get and act on. Right now I have a single thread which produces data and add this into the said cache.

我在 DataPool 中有一个由 CacheBuilder 构建的缓存。DataPool 是一个单例对象,它的实例各种线程都可以获取和操作。现在我有一个线程来生成数据并将其添加到所述缓存中。

To show the relevant part of the code:

显示代码的相关部分:

 private InputDataPool(){

    cache=CacheBuilder.newBuilder().expireAfterWrite(1000, TimeUnit.NANOSECONDS).removalListener(
            new RemovalListener(){
                {
                    logger.debug("Removal Listener created");
                }
                                public void onRemoval(RemovalNotification notification) {
                                    System.out.println("Going to remove data from InputDataPool");
                                    logger.info("Following data is being removed:"+notification.getKey());
                                    if(notification.getCause()==RemovalCause.EXPIRED)
                                    {
                                        logger.fatal("This data expired:"+notification.getKey());
                                    }else
                                    {
                                        logger.fatal("This data didn't expired but evacuated intentionally"+notification.getKey());
                                    }

                                }}
                    ).build(new CacheLoader(){

                        @Override
                        public Object load(Object key) throws Exception {
                                logger.info("Following data being loaded"+(Integer)key);
                                Integer uniqueId=(Integer)key;
                                return InputDataPool.getInstance().getAndRemoveDataFromPool(uniqueId);

                        }

                    });
}

public static InputDataPool getInstance(){
        if(clsInputDataPool==null){
            synchronized(InputDataPool.class){
                if(clsInputDataPool==null)
                {
                    clsInputDataPool=new InputDataPool();
                }
            }
        }
    return clsInputDataPool;
}

From the said thread the call being made is as simple as

从所述线程进行的调用非常简单

 while(true){
 inputDataPool.insertDataIntoPool(inputDataPacket);
     //call some logic which comes with inputDataPacket and sleep for 2 seconds.
}

and where inputDataPool.insertDataIntoPool is like

哪里 inputDataPool.insertDataIntoPool 就像

inputDataPool.insertDataIntoPool(InputDataPacket inputDataPacket){ 
 cache.get(inputDataPacket.getId());
}

Now the question is, the element in cache is supposed to expire after 1000 nanosec.So when inputDataPool.insertDataIntoPool is called second time, the data which has been inserted first time will be evacuated as it must have got expired as the call is being after 2 seconds of its insertion.And then correspondingly Removal Listener should be called. But this is not happening. I looked into cache stats and evictionCount is always zero, no matter how much time cache.get(id) is called.

现在的问题是,缓存中的元素应该在 1000 纳秒后过期。所以当 inputDataPool.insertDataIntoPool 被第二次调用时,第一次插入的数据将被清除,因为它必须在调用之后过期插入后 2 秒。然后相应地应调用 Removal Listener。但这并没有发生。我查看了缓存统计信息,evictionCount 始终为零,无论调用 cache.get(id) 多长时间。

But importantly, if I extend inputDataPool.insertDataIntoPool

但重要的是,如果我扩展 inputDataPool.insertDataIntoPool

  inputDataPool.insertDataIntoPool(InputDataPacket inputDataPacket){ 
 cache.get(inputDataPacket.getId());
    try{
     Thread.sleep(2000);
   }catch(InterruptedException ex){ex.printStackTrace();
     }
cache.get(inputDataPacket.getId())
}

then the eviction take place as expected with removal listener being called.

然后驱逐按预期发生,并调用移除侦听器。

Now I'm very much clueless at the moment where I'm missing something to expect such kind of behaviour. Please help me see,if you see something.

现在我非常无能为力,因为我缺少一些可以期待这种行为的东西。请帮我看看,如果你看到了什么。

P.S. Please ignore any typos.Also no check is being made, no generic has been used, all as this is just in the phase of testing the CacheBuilder functionality.

PS 请忽略任何错别字。此外,没有进行检查,没有使用泛型,因为这只是在测试 CacheBuilder 功能的阶段。

Thanks

谢谢

回答by JB Nizet

As explained in the javadoc and in the user guide, There is no thread that makes sure entries are removed from the cache as soon as the delay has elapsed. Instead, entries are removed during write operations, and occasionally during read operations if writes are rare. This is to allow for a high throughput and a low latency. And of course, every write operation doesn't cause a cleanup:

正如 javadoc 和用户指南中所解释的,没有任何线程可以确保在延迟结束后立即从缓存中删除条目。相反,在写入操作期间删除条目,如果写入很少,则偶尔在读取操作期间删除。这是为了实现高吞吐量和低延迟。当然,每个写操作都不会导致清理:

Caches built with CacheBuilder do not perform cleanup and evict values "automatically," or instantly after a value expires, or anything of the sort. Instead, it performs small amounts of maintenance during write operations, or during occasional read operations if writes are rare.

The reason for this is as follows: if we wanted to perform Cache maintenance continuously, we would need to create a thread, and its operations would be competing with user operations for shared locks. Additionally, some environments restrict the creation of threads, which would make CacheBuilder unusable in that environment.

使用 CacheBuilder 构建的缓存不会“自动”或在值过期后立即执行清理和驱逐值,或任何类似的操作。相反,它会在写入操作期间或偶尔的读取操作(如果写入很少)期间执行少量维护。

这样做的原因是:如果我们想持续进行Cache维护,我们需要创建一个线程,它的操作会与用户操作争夺共享锁。此外,某些环境会限制线程的创建,这会使 CacheBuilder 在该环境中无法使用。

回答by M2je

I had the same issue and I could find this at guava's documentation for CacheBuilder.removalListener

我有同样的问题,我可以在番石榴的 CacheBuilder.removalListener 文档中找到这个

Warning: after invoking this method, do not continue to use this cache builder reference; instead use the reference this method returns. At runtime, these point to the same instance, but only the returned reference has the correct generic type information so as to ensure type safety. For best results, use the standard method-chaining idiom illustrated in the class documentation above, configuring a builder and building your cache in a single statement. Failure to heed this advice can result in a ClassCastException being thrown by a cache operation at some undefined point in the future.

警告:调用此方法后,请勿继续使用此缓存构建器参考;而是使用此方法返回的引用。在运行时,它们指向同一个实例,但只有返回的引用才具有正确的泛型类型信息,以确保类型安全。为获得最佳结果,请使用上面类文档中说明的标准方法链习语,在单个语句中配置构建器并构建缓存。不注意这个建议可能会导致 ClassCastException 在未来的某个未定义点被缓存操作抛出。

So by changing your code to use the builder reference that is called after adding the removalListnener this problem can be resolved

因此,通过更改您的代码以使用在添加 removeListnener 后调用的构建器引用,可以解决此问题

 CacheBuilder builder=CacheBuilder.newBuilder().expireAfterWrite(1000, TimeUnit.NANOSECONDS).removalListener(
            new RemovalListener(){
                {
                    logger.debug("Removal Listener created");
                }
                                public void onRemoval(RemovalNotification notification) {
                                    System.out.println("Going to remove data from InputDataPool");
                                    logger.info("Following data is being removed:"+notification.getKey());
                                    if(notification.getCause()==RemovalCause.EXPIRED)
                                    {
                                        logger.fatal("This data expired:"+notification.getKey());
                                    }else
                                    {
                                        logger.fatal("This data didn't expired but evacuated intentionally"+notification.getKey());
                                    }

                                }}
                    );
   cache=builder.build(new CacheLoader(){

                        @Override
                        public Object load(Object key) throws Exception {
                                logger.info("Following data being loaded"+(Integer)key);
                                Integer uniqueId=(Integer)key;
                                return InputDataPool.getInstance().getAndRemoveDataFromPool(uniqueId);

                        }

                    });

This problem will be resolved. It is kind of wired but I guess it is what it is :)

这个问题将得到解决。它有点有线,但我想它是这样的:)