gpt4 book ai didi

java - 为什么缓存元素不会立即过期?

转载 作者:行者123 更新时间:2023-11-30 06:13:18 25 4
gpt4 key购买 nike

我有我认为的简单使用Guava缓存。但是,这种行为对我来说并不直观。我有一个 POJO,Foo 具有属性 Id (Integer)。在检索 Foo 的实例时,我使用 Integer 作为缓存的键。如果我将三个项目放入缓存中,并 hibernate 足够长的时间以使所有内容都过期,那么无论键值如何,我都希望有相同的行为。问题是我根据使用的 key 看到了不同的行为。我将三个对象放入缓存:1000、2000 和 3000。

[main] INFO CacheTestCase - 3000 creating foo, 1000
[main] INFO CacheTestCase - 3000 creating foo, 2000
[main] INFO CacheTestCase - 3000 creating foo, 3000
[main] INFO CacheTestCase - 3000 Sleeping to let some cache expire . . .
[main] INFO CacheTestCase - 3000 Continuing . . .
[main] INFO CacheTestCase - 3000 Removed, 1000
[main] INFO CacheTestCase - 3000 Removed, 2000
[main] INFO CacheTestCase - 3000 creating foo, 1000
[main] INFO CacheTestCase -

请注意,在上面的运行中,键为 3000 的 Foo 实例并未从缓存中删除。下面是相同代码的输出,但我使用的不是 key 3000,而是 4000。

[main] INFO CacheTestCase - 4000 creating foo, 1000
[main] INFO CacheTestCase - 4000 creating foo, 2000
[main] INFO CacheTestCase - 4000 creating foo, 4000
[main] INFO CacheTestCase - 4000 Sleeping to let some cache expire . . .
[main] INFO CacheTestCase - 4000 Continuing . . .
[main] INFO CacheTestCase - 4000 Removed, 1000
[main] INFO CacheTestCase - 4000 Removed, 2000
[main] INFO CacheTestCase - 4000 Removed, 4000
[main] INFO CacheTestCase - 4000 creating foo, 1000

当然,我做了一些非常愚蠢的事情。这是我的 MCVE:

package org.dlm.guava;

import com.google.common.cache.*;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
* Created by dmcreynolds on 8/17/2015.
*/
public class CacheTestCase {
static final Logger log = LoggerFactory.getLogger("CacheTestCase");
String p = ""; // just to make the log messages different
int DELAY = 10000; // ms
@Test
public void testCache123() throws Exception {
p = "3000";
LoadingCache<Integer, Foo> fooCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(100, TimeUnit.MILLISECONDS)
.removalListener(new FooRemovalListener())
.build(
new CacheLoader<Integer, Foo>() {
public Foo load(Integer key) throws Exception {
return createExpensiveFoo(key);
}
});

fooCache.get(1000);
fooCache.get(2000);
fooCache.get(3000);
log.info(p + " Sleeping to let some cache expire . . .");
Thread.sleep(DELAY);
log.info(p + " Continuing . . .");
fooCache.get(1000);
}


private Foo createExpensiveFoo(Integer key) {
log.info(p+" creating foo, " + key);
return new Foo(key);
}


public class FooRemovalListener
implements RemovalListener<Integer, Foo> {
public void onRemoval(RemovalNotification<Integer, Foo> removal) {
removal.getCause();
log.info(p+" Removed, " + removal.getKey().hashCode());
}
}

/**
* POJO Foo
*/
public class Foo {
private Integer id;

public Foo(Integer newVal) {
this.id = newVal;
}

public Integer getId() {
return id;
}
public void setId(Integer newVal) {
this.id = newVal;
}
}
}

最佳答案

来自 CacheBuilder 的 Javadoc :

If expireAfterWrite or expireAfterAccess is requested entries may be evicted on each cache modification, on occasional cache accesses, or on calls to Cache.cleanUp(). Expired entries may be counted by Cache.size(), but will never be visible to read or write operations.

有一件事是说,一旦过期,如果您尝试阅读任何过期的条目,您将看到它们不再存在。因此,例如,尽管您没有看到 3000 的条目在您的 RemovalListener 中被删除,但如果您调用了 fooCache.get(3000),它必须先加载那个值(那时你会看到旧值被删除)。所以从缓存 API 的用户的角度来看,旧的缓存值已经消失了。

您在示例中看到特定行为的原因非常简单:出于并发原因,缓存被分段。条目根据它们的哈希码分配一个段,每个段就像一个小的独立缓存。所以大多数操作(例如 fooCache.get(1000))只会对单个段进行操作。在您的示例中,10002000 显然分配给同一段,而 3000 则在另一个段中。 4000,在您的第二个版本中,被分配到与 10002000 相同的段,因此它与其他两个一起被清理当写入 1000 的新值时。

在大多数实际使用中,段通常应该经常被命中,过期的条目将被定期清理,这不会成为问题。但是,除非您对缓存调用 cleanUp(),否则无法保证确切的时间

关于java - 为什么缓存元素不会立即过期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32056938/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com