简体   繁体   English

双重检查锁定 - 陷阱?

[英]Double-checked locking - pitfalls?

I'm using java for about a month, and still am generally an amateur in programming, so feel free to correct me if I get something wrong. 我正在使用java大约一个月,而且我一般都是编程的业余爱好者,所以如果我出错了,请随时纠正我。 Maybe I'll provide some excess details, but I'm so confused right now that I can't decide what matters anymore. 也许我会提供一些过多的细节,但我现在很困惑,我无法决定什么是重要的。

So, I've been developing multi-threaded client-server application. 所以,我一直在开发多线程客户端 - 服务器应用程序。 All threads are using the same object, in which certain configuration values and shared loggers are stored; 所有线程都使用相同的对象,其中存储了某些配置值和共享记录器; this object is initialized in server-thread and then passed as an argument to client-thread class constructor. 此对象在服务器线程中初始化,然后作为参数传递给客户端线程类构造函数。 First it was assumed that fields of that object are changed only once, when server starts, so concurrent access was nothing to worry about, but now it's required for some configuration values to be re-read from config file when it's modified, without having to restart the server. 首先,假设当服务器启动时,该对象的字段只更改一次,因此并发访问无需担心,但现在需要在修改时从配置文件中重新读取某些配置值,而不必重启服务器。

The first idea that came to mind after some research was to make a synchronized method that would be called when some values from the class are requested, and would re-read those values if our config file changed since last access and return immediately otherwise, like this: 在一些研究之后想到的第一个想法是创建一个同步方法,当请求类中的某些值时将调用该方法,并且如果我们的配置文件自上次访问后发生更改则会重新读取这些值,否则会立即返回,如这个:

<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>
    }

(The above code isn't tested, i just want to get the general idea across). (上面的代码没有经过测试,我只是希望得到一般的想法)。

But I just didn't like the idea of blocking every single time the value is requested, since that seems expensive, and my application has a possibility of becoming pretty high-loaded with lots of threads in the future. 但是我只是不喜欢每次都要阻止请求值的想法,因为这看起来很昂贵,而且我的应用程序有可能在将来变得非常高负载大量的线程。 So I had a "good" idea - to check if file is modified before locking and then inside the locked method again, to avoid locking at all whenever possible: 所以我有一个“好”的想法 - 在锁定之前检查文件是否被修改,然后再次在锁定方法内部,以避免在任何可能的情况下锁定:

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

Ten minutes later I learned it's called double-checked locking and another ten minutes later my world crumbled twice after reading this article : first time when I learned it supposedly would not work due to internal CPU caching and second time when I read about operations on long/float types not being atomic. 十分钟后,我学会了它被称为双重检查锁定,另外十分钟后我的世界在读完这篇文章后崩溃了两次:第一次当我学会它时,据说由于内部CPU缓存而无法工作,第二次当我读到长时间操作时/ float类型不是原子的。 Or will it work after all, since no object creation is involved? 或者它是否会起作用,因为不涉及对象创建? And, since operations on long are non-atomic, would it really be enough to declare "lastModified" as volatile? 并且,由于long上的操作是非原子的,将“lastModified”声明为volatile是否足够? If possible, I would prefer a proper explanation on why it will/will not work. 如果可能的话,我更愿意正确解释它为什么会/不会起作用。 Thank you in advance. 先感谢您。

PS: I know similar questions were already answered couple of times, and maybe it would be better to stop nitpicking and synchronize the whole "getValue" method rather than "ReReadConfig", but I'm striving to learn more about thread-safe programming and to see pitfalls in my own code to avoid something similar in the future. PS:我知道类似的问题已经回答了几次,也许最好停止挑剔和同步整个“getValue”方法而不是“ReReadConfig”,但我正在努力学习更多关于线程安全编程和在我自己的代码中查看陷阱以避免将来出现类似情况。 I also apologize for any possible grammar and spelling errors, I don't know English that well. 我也为任何可能的语法和拼写错误道歉,我不太懂英语。


EDIT: First, I fixed a typo in last "if" clause. 编辑:首先,我修改了最后一个“if”条款中的拼写错误。 Second - warning, the above code is NOT thread-safe, do not use it! 第二 - 警告,上面的代码不是线程安全的,不要使用它! In method 在方法中

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

In case file was updated in time span between if-check and value returning, thread B could start ReReadConfig BEFORE thread A starts returning values, resulting in a dangerous partial changes to necessary data. 如果文件在if-check和值返回之间的时间跨度内更新,则线程B可以在线程A开始返回值之前启动ReReadConfig,从而导致对必要数据的危险部分更改。 It seems the correct way of doing what I need without excessive blocking is using ReentrantReadWriteLock, however, I still want to use double-checking to avoid excessive (and expensive, file is assumed to be large XML) config re-reads: 在没有过多阻塞的情况下执行我需要的正确方法似乎是使用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>
    }

Now it at least looks thread-safe, but question remains open on depending on volatile "lastModified" variable when checking: I've read somewhere that volatile variables can't guarantee anything on non-atomic operations, and "long" type read/writes ARE non-atomic. 现在它至少看起来是线程安全的,但问题仍然是开放的,取决于检查时的volatile“lastModified”变量:我读过某些地方,volatile变量无法保证非原子操作的任何内容,而“long”类型读取/写是非原子的。

You want to use a ReadWriteLock . 您想使用ReadWriteLock This does not block readers as long as there are no writers. 只要没有作者,这不会阻止读者。

If you can organize the code in such a way that all your config data is in a single immutable object and there is a shared volatile reference to that object, then this will be thread-safe. 如果您可以组织代码,使所有配置数据都在一个不可变对象中,并且存在对该对象的共享volatile引用,那么这将是线程安全的。 This kind of use case is actually what the revision of volatile semantics was aiming for. 这种用例实际上是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