我想知道,下面使用AtomicBoolean作为标志变量是否正确,对多线程应用程序是否有利。预计数据将只加载一次,并且当应用程序处于非常低的负载时。预计SomeObjects列表将在高峰负载时由多个线程非常频繁地读取,可能在一小时内读取数千次,因此我希望避免访问数据库。这个列表不会很大,所以将它保存在内存中不会是一个问题。
public class HibernateDao extends SomeOtherClass implements Dao {
//Need suggestions for correct usage of following variable
private AtomicBoolean dataLoaded = new AtomicBoolean();
private List<SomeObject> someObjects;
@Override
public List<SomeObject> getAllSomeObjects() {
if (dataLoaded.getAndSet(false)) {
//aim is to atomically load the list only once and avoid calling loadAll
//every time list is used
someObjects= (List<SomeObject>) getHibernateTemplate().loadAll(SomeObject.class);
}
return someObjects;
}
@Override
public void updateAllSomeObjects(Collection<SomeObject> someObjects) {
//replaceAll is part of parent class and it removed and reloads
//data for the entity i.e. SomeObject
replaceAll(SomeObject.class, someObjects);
//set this flag atomically so that data is fetched from DB next time
dataLoaded.getAndSet(true);
}
}发布于 2014-05-12 22:43:55
您正在尝试使用AtomicBoolean来满足两个要求:
这两种方法都没有完美的效果。
null,而无需等待第一个线程完成加载过程。解决第一个问题很简单:使用CountDownLatch使所有传入线程阻塞,直到设置了第一个对象列表。理想情况下,系统将启动另一个线程来具体加载数据,而不是让第一个随机线程出现并执行。这是不必要的(见下文),但它要干净得多。
对于第二个问题,将对象列表包装在AtomicReference中以提供正确的内存屏障。你可能会想,“哦,不!不是每个访问的两个同步器!”但是这些年来Java的同步原语有了很大的改进,原子值持有者甚至比完全同步更便宜。
public class HibernateDao extends SomeOtherClass implements Dao {
private CountDownLatch dataLoaded = new CountDownLatch();
private AtomicReference<List<SomeObject>> data = new AtomicReference<>();
@Override
public List<SomeObject> getAllSomeObjects() {
dataLoaded.await(); // wait until first countDown call
return data.get();
}
@Override
public void updateAllSomeObjects(Collection<SomeObject> someObjects) {
replaceAll(SomeObject.class, someObjects);
data.set(someObjects);
dataLoaded.countDown(); // release any waiting threads
}
}如果您确实必须让第一个线程执行初始加载,而不是一个单独的线程单独调用updateAllSomeObjects,则可以添加回您心爱的AtomicBoolean。然而,这将是我最后的选择。
注意,虽然可以使用
if (!ab.getAndSet(true)),但使用compareAndSet更清楚,因为它表明您正在执行测试集,而不是允许任何线程设置新值。
private AtomicBoolean firstCaller = new AtomicBoolean();
@Override
public List<SomeObject> getAllSomeObjects() {
if (firstCaller.compareAndSet(false, true)) {
updateAllSomeObjects((List<SomeObject>) getHibernateTemplate().loadAll(SomeObject.class));
}
else {
dataLoaded.await(); // wait until first countDown call
}
return data.get();
}发布于 2014-05-12 17:12:34
我过去常常这样做:检查集合是否为null,只有当集合为null时才进行初始化,否则返回集合。当然,这种集团应当同步进行:
synchronized (this) {
if (someObjects == null) {
someObjects = ...//initialize
}
return someObjects;
}此外,由于您的块不是同步的,所以如果两个线程同时启动它,一个线程将开始从数据库中获取对象,另一个线程将跳过它(因为已经设置了dataLoaded )并获得someObjects,它为null。
发布于 2014-05-12 20:33:52
缓存失效是计算机科学中两大难题之一。因此,我强烈建议重写代码,以便缓存的实现不同于任何具有其他职责的类。
更好的是,与其滚动自己的缓存实现,不如抓住一个已经有几圈的缓存实现。番石榴可能是一个合理的选择。
正如Igor所指出的,AtomicBoolean没有做您想做的事情--它提供了一个内存屏障(它提供了对其他线程可见的内存更改的保证),但没有提供同步。因此,例如,您可以通过初始化列表来修复空指针问题,但是在加载最新数据时,仍然会遇到线程看到陈旧数据的问题。
https://codereview.stackexchange.com/questions/49514
复制相似问题