我已经实现了一种缓存bean来将数据对象缓存为EJB Singleton。我想知道这在EJB中是否正确:
@Singleton
public class MyCache {
int DEFAULT_CACHE_SIZE = 30;
int DEFAULT_EXPIRES_TIME = 60000;
long expiresTime = 0;
long lastReset = 0;
Cache cache = null;
....
@PostConstruct
void init() {
resetCache();
}
public void resetCache() {
cache = new Cache(DEFAULT_CACHE_SIZE);
lastReset = System.currentTimeMillis();
}
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
// test if cache is expired
if (expiresTime > 0) {
Long now = System.currentTimeMillis();
if ((now - lastReset) > expiresTime) {
logger.finest("...... Cache expired!");
resetCache();
}
}
return cache.get(key);
}
class Cache extends LinkedHashMap<String, Object> implements Serializable {
private static final long serialVersionUID = 1L;
private final int capacity;
public Cache(int capacity) {
super(capacity + 1, 1.1f, true);
this.capacity = capacity;
}
protected boolean removeEldestEntry(Entry<String, Object> eldest) {
return size() > capacity;
}
}
}
我的问题是:这是实现应用程序范围缓存机制的正确方法吗?
我的印象是缓存的内容意外更改。这会发生吗?例如,如果EJB被钝化了? 我在Payara41服务器上运行。
或者我必须使用:
cache = Collections.synchronizedMap(new Cache(DEFAULT_CACHE_SIZE));
代替:
cache = new Cache(DEFAULT_CACHE_SIZE);
答案 0 :(得分:1)
首先,由于未为您的bean指定并发管理,因此它归结为默认的“容器”。
根据EJB 3.1规范:
在设计Singleton会话Bean时,开发人员必须决定 Bean将使用容器托管还是Bean托管 并发。通常,将Singleton Bean指定为具有 容器管理的并发划分。如果没有,这是默认值 指定了并发管理类型。
然后,容器并发管理需要锁定类型的方法级规范。一旦不存在这些内容,就会应用默认的“写”:
默认情况下,如果没有并发锁定属性注释 为具有容器管理的Singleton bean的方法指定 并发划分,并发锁定的值 该方法的属性定义为Write。
以上内容意味着对bean方法的访问必须同步,甚至可能超出您的实际需要。您可以为只读方法(获取)设置“读取”锁定类型,以允许并发读取访问。
答案 1 :(得分:0)
采用容器管理锁的解决方案有一个缺点, 考虑一下您是否具有带有WRITE锁定的put操作,这意味着整个“缓存”都被阻止,因此无论密钥是否不同,都无法并行进行get或put操作。 如果您的缓存实现是并发Map,则可以使用它而无需锁定。
如果您对缓存有更多要求,我会使用Infinispan来提供更好的性能。这里的缓存可以是本地缓存,也可以分布在群集中。
答案 2 :(得分:0)
阅读此blog from Adam Bien之后,我现在通过以下方式改进了代码:
示例:
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN) // added concurrency management
public class MyCache {
int DEFAULT_CACHE_SIZE = 30;
int DEFAULT_EXPIRES_TIME = 60000;
long expiresTime = 0;
long lastReset = 0;
Cache cache = null;
....
@PostConstruct
void init() {
resetCache();
}
public void resetCache() {
cache = new Cache(DEFAULT_CACHE_SIZE);
lastReset = System.currentTimeMillis();
}
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
// test if cache is expired
if (expiresTime > 0) {
Long now = System.currentTimeMillis();
if ((now - lastReset) > expiresTime) {
logger.finest("...... Cache expired!");
resetCache();
}
}
return cache.get(key);
}
// changed from LinkedHashMap to ConcurrentHashMap
class Cache extends ConcurrentHashMap<String, Object> implements Serializable {
private static final long serialVersionUID = 1L;
private final int capacity;
public Cache(int capacity) {
super(capacity + 1, 1.1f);
this.capacity = capacity;
}
protected boolean removeEldestEntry(Entry<String, Object> eldest) {
return size() > capacity;
}
}
}
我认为这是更正确的实现方式。