繁体   English   中英

双重检查锁定 - 陷阱?

[英]Double-checked locking - pitfalls?

我正在使用java大约一个月,而且我一般都是编程的业余爱好者,所以如果我出错了,请随时纠正我。 也许我会提供一些过多的细节,但我现在很困惑,我无法决定什么是重要的。

所以,我一直在开发多线程客户端 - 服务器应用程序。 所有线程都使用相同的对象,其中存储了某些配置值和共享记录器; 此对象在服务器线程中初始化,然后作为参数传递给客户端线程类构造函数。 首先,假设当服务器启动时,该对象的字段只更改一次,因此并发访问无需担心,但现在需要在修改时从配置文件中重新读取某些配置值,而不必重启服务器。

在一些研究之后想到的第一个想法是创建一个同步方法,当请求类中的某些值时将调用该方法,并且如果我们的配置文件自上次访问后发生更改则会重新读取这些值,否则会立即返回,如这个:

<This code is inside "config" class, instance of which is shared between threads>
private static long lastModified;
private static File configFile;

public class ChangingVariableSet
    {
    <changing variables go here>
    }

private synchronized void ReReadConfig
    {
    long tempLastMod = configFile.lastModified();
    if(lastModified == tempLastMod)
        return;
    <reread values here>
    lastModified = tempLastMod;
    }

public ChangingVariableSet GetValues()
    {
    ReReadConfig();
    <return necessary values>
    }

(上面的代码没有经过测试,我只是希望得到一般的想法)。

但是我只是不喜欢每次都要阻止请求值的想法,因为这看起来很昂贵,而且我的应用程序有可能在将来变得非常高负载大量的线程。 所以我有一个“好”的想法 - 在锁定之前检查文件是否被修改,然后再次在锁定方法内部,以避免在任何可能的情况下锁定:

 public ChangingVariableSet GetValues()
    {
    if(lastModified == configFile.lastModified())
        ReReadConfig();
    <return necessary values>
    }

十分钟后,我学会了它被称为双重检查锁定,另外十分钟后我的世界在读完这篇文章后崩溃了两次:第一次当我学会它时,据说由于内部CPU缓存而无法工作,第二次当我读到长时间操作时/ float类型不是原子的。 或者它是否会起作用,因为不涉及对象创建? 并且,由于long上的操作是非原子的,将“lastModified”声明为volatile是否足够? 如果可能的话,我更愿意正确解释它为什么会/不会起作用。 先感谢您。

PS:我知道类似的问题已经回答了几次,也许最好停止挑剔和同步整个“getValue”方法而不是“ReReadConfig”,但我正在努力学习更多关于线程安全编程和在我自己的代码中查看陷阱以避免将来出现类似情况。 我也为任何可能的语法和拼写错误道歉,我不太懂英语。


编辑:首先,我修改了最后一个“if”条款中的拼写错误。 第二 - 警告,上面的代码不是线程安全的,不要使用它! 在方法中

 public ChangingVariableSet GetValues()
    {
    if(lastModified == configFile.lastModified())
        ReReadConfig();
    <return necessary values>
    }

如果文件在if-check和值返回之间的时间跨度内更新,则线程B可以在线程A开始返回值之前启动ReReadConfig,从而导致对必要数据的危险部分更改。 在没有过多阻塞的情况下执行我需要的正确方法似乎是使用ReentrantReadWriteLock,但是,我仍然希望使用双重检查以避免过多(并且昂贵的,文件被假定为大型XML)配置重新读取:

<...>
private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static final Lock read  = readWriteLock.readLock();
private static final Lock write = readWriteLock.writeLock();

private void ReReadConfig
    {
    write.lock();
    long tempLastMod = configFile.lastModified();
    if(lastModified == tempLastMod)
        return;
    <reread values here>
    lastModified = tempLastMod;
    write.release();
    }

 public ChangingVariableSet GetValues()
    {
    if(lastModified == configFile.lastModified())
        ReReadConfig();
    read.lock();
    <get necessary values>
    read.release();
    <return necessary values>
    }

现在它至少看起来是线程安全的,但问题仍然是开放的,取决于检查时的volatile“lastModified”变量:我读过某些地方,volatile变量无法保证非原子操作的任何内容,而“long”类型读取/写是非原子的。

您想使用ReadWriteLock 只要没有作者,这不会阻止读者。

如果您可以组织代码,使所有配置数据都在一个不可变对象中,并且存在对该对象的共享volatile引用,那么这将是线程安全的。 这种用例实际上是volatile语义的修订目标。

public static volatile Config config;

void rereadConfig() {
  if (modified)
    config = new Config(...);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM