首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >AtomicBoolean的使用

AtomicBoolean的使用
EN

Code Review用户
提问于 2014-05-12 11:53:58
回答 3查看 9.2K关注 0票数 6

我想知道,下面使用AtomicBoolean作为标志变量是否正确,对多线程应用程序是否有利。预计数据将只加载一次,并且当应用程序处于非常低的负载时。预计SomeObjects列表将在高峰负载时由多个线程非常频繁地读取,可能在一小时内读取数千次,因此我希望避免访问数据库。这个列表不会很大,所以将它保存在内存中不会是一个问题。

代码语言:javascript
复制
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);
    }

}
EN

回答 3

Code Review用户

回答已采纳

发布于 2014-05-12 22:43:55

您正在尝试使用AtomicBoolean来满足两个要求:

  • 访问数据的第一个线程加载数据,而其他线程则等待第一次加载数据。
  • 另一个线程可以为未来的调用者更新数据。

这两种方法都没有完美的效果。

  • 虽然只有一个线程将加载数据,但所有其他线程都将接收null,而无需等待第一个线程完成加载过程。
  • 因为对象列表是在内存屏障之后更新的,所以其他线程不能保证立即看到新的引用。

解决第一个问题很简单:使用CountDownLatch使所有传入线程阻塞,直到设置了第一个对象列表。理想情况下,系统将启动另一个线程来具体加载数据,而不是让第一个随机线程出现并执行。这是不必要的(见下文),但它要干净得多。

对于第二个问题,将对象列表包装在AtomicReference中以提供正确的内存屏障。你可能会想,“哦,不!不是每个访问的两个同步器!”但是这些年来Java的同步原语有了很大的改进,原子值持有者甚至比完全同步更便宜。

代码语言:javascript
复制
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更清楚,因为它表明您正在执行测试集,而不是允许任何线程设置新值。

代码语言:javascript
复制
    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();
    }
票数 6
EN

Code Review用户

发布于 2014-05-12 17:12:34

我过去常常这样做:检查集合是否为null,只有当集合为null时才进行初始化,否则返回集合。当然,这种集团应当同步进行:

代码语言:javascript
复制
synchronized (this) {
  if (someObjects == null) {
    someObjects = ...//initialize
  }
  return someObjects;
}

此外,由于您的块不是同步的,所以如果两个线程同时启动它,一个线程将开始从数据库中获取对象,另一个线程将跳过它(因为已经设置了dataLoaded )并获得someObjects,它为null。

票数 1
EN

Code Review用户

发布于 2014-05-12 20:33:52

缓存失效是计算机科学中两大难题之一。因此,我强烈建议重写代码,以便缓存的实现不同于任何具有其他职责的类。

更好的是,与其滚动自己的缓存实现,不如抓住一个已经有几圈的缓存实现。番石榴可能是一个合理的选择。

正如Igor所指出的,AtomicBoolean没有做您想做的事情--它提供了一个内存屏障(它提供了对其他线程可见的内存更改的保证),但没有提供同步。因此,例如,您可以通过初始化列表来修复空指针问题,但是在加载最新数据时,仍然会遇到线程看到陈旧数据的问题。

另见:忘记回忆录的供应商价值的能力

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/49514

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档