首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >正确使用volatile关键字

正确使用volatile关键字
EN

Stack Overflow用户
提问于 2010-06-11 00:23:48
回答 4查看 754关注 0票数 1

我想我对java中的volatile关键字有一个很好的想法,但我正在考虑重构一些代码,我认为使用它会是一个好主意。

我有一个类,它基本上是作为DB缓存工作的。它保存从数据库中读取的一组对象,为这些对象的请求提供服务,然后偶尔刷新数据库(基于超时)。这是骨架

代码语言:javascript
复制
public class Cache
{
    private HashMap mappings =....;
    private long last_update_time;
    private void loadMappingsFromDB()
    {
        //....
    }
    private void checkLoad()
    {
        if(System.currentTimeMillis() - last_update_time > TIMEOUT)
            loadMappingsFromDB();
    }
    public Data get(ID id)
    {
        checkLoad();
        //.. look it up
    }
}

因此,令人担忧的是loadMappingsFromDB可能是一个高延迟的操作,这是不可接受的,所以最初我认为我可以在缓存启动时启动一个线程,然后让它休眠,然后在后台更新缓存。但是,我需要同步我的类(或映射)。然后,我只会牺牲偶尔的大停顿来降低每次缓存访问的速度。

然后我想为什么不使用volatile

我可以将映射引用定义为易失性

代码语言:javascript
复制
private volatile HashMap mappings =....;

然后在get (或其他任何使用mappings变量的地方)中,我只会创建一个引用的本地副本:

代码语言:javascript
复制
public Data get(ID id)
{
    HashMap local = mappings;
    //.. look it up using local
}

然后,后台线程将加载到临时表中,然后交换类中的引用

代码语言:javascript
复制
HashMap tmp;
//load tmp from DB
mappings = tmp;//swap variables forcing write barrier

这种方法有意义吗?它实际上是线程安全的吗?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2010-06-11 01:06:16

这个问题的现有答案中存在一些错误信息。使用volatile实际上是确保线程安全的一个很好的步骤。参见IBM的Peter Haggar所著的item 3 in 。Haggar给出了一些背景知识和一个例子,但问题是:

那么,原子操作怎么可能不是线程安全的呢?主要的一点是它们可能确实是线程安全的,但不能保证它们是安全的。允许Java线程将变量的私有副本与主内存分开。

通过使用volatile,您将保证线程引用的是主内存,而不是使用您不知道或不期望的变量的私有副本。

回答你的问题:是的,你的策略是安全的。

编辑:

作为对另一篇文章的回应,这里是the JLS section about volatile fields

票数 2
EN

Stack Overflow用户

发布于 2010-06-11 00:58:40

这种方法有意义吗?它实际上是线程安全的吗?

它确实有意义,而且它是线程安全的。在某种程度上,不管怎样。以下是一些需要考虑的事情:

  • 更新时,您可以让应用程序读取旧的、过期的值。这是你想要的吗?
  • loadMappingsFromDB()启动时,最初调用get(ID)的线程将会阻塞,直到更新完成,线程可能会同时调用checkLoad(),这意味着如果重新加载很慢,并且你有几个线程在调用get(ID),你可能最终会得到大量的并发更新。虽然结果是一样的,但这将是对系统资源的浪费。在当前代码中修复它的一个简单方法是在更新之前检查一个AtomicBoolean

private final AtomicBoolean isUpdating = new AtomicBoolean(false);private void checkLoad() { if (System.currentTimeMillis() - last_update_time <= TIMEOUT) return;if (!isUpdating.compareAndSet(false,true)) return;//已在更新try { loadMappingsFromDB();} finally { isUpdating.set(false);} }

票数 1
EN

Stack Overflow用户

发布于 2010-06-11 00:29:01

我认为这种通用的方法是有效的(在后台线程上重新加载缓存,不会在加载过程中通过将数据加载到单独的实例来阻止对缓存的访问),但我不确定将引用声明为volatile真正能给您带来什么。

您可以很容易地让get(id)方法和覆盖引用的loadMappingsFromDB()部分(不是整个load from DB,而仅仅是mappings的重新分配)在同一个锁上同步。

不过老实说,我会考虑重用像EhCache这样的已建立的缓存库,它具有后台加载或启动时加载的功能,因为这些库很可能很久以前就解决了所有的同步问题,你可以回去担心你的应用程序的逻辑,而不是自制缓存的低级安全性。

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

https://stackoverflow.com/questions/3016318

复制
相关文章

相似问题

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