Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/379.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ruby-on-rails-3/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 双重检查锁定陷阱?_Java_Synchronization_Double Checked Locking - Fatal编程技术网

Java 双重检查锁定陷阱?

Java 双重检查锁定陷阱?,java,synchronization,double-checked-locking,Java,Synchronization,Double Checked Locking,我使用java已经有一个月了,现在仍然是编程的业余爱好者,所以如果我有什么问题,请随时纠正我。也许我会提供一些额外的细节,但我现在太困惑了,我无法决定什么是重要的 所以,我一直在开发多线程客户机-服务器应用程序。所有线程都使用同一个对象,其中存储了某些配置值和共享记录器;此对象在服务器线程中初始化,然后作为参数传递给客户端线程类构造函数。首先,假设该对象的字段在服务器启动时只更改一次,所以并发访问不需要担心,但现在需要在修改配置文件时从配置文件中重新读取一些配置值,而无需重新启动服务器 经过一些

我使用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>
    }

私有静态长时间修改;
私有静态文件;
公共类ChangingVariableSet
{
}
私有同步的void重新读取配置
{
long tempLastMod=configFile.lastModified();
if(lastModified==tempLastMod)
返回;
lastModified=tempLastMod;
}
公共更改变量集GetValues()
{
重新读取配置();
}
(上面的代码没有经过测试,我只是想让大家了解一下基本情况)

但我只是不喜欢每次请求值时都阻塞的想法,因为这似乎很昂贵,而且我的应用程序将来可能会装载大量线程。所以我有一个“好”主意——在锁定之前检查文件是否被修改,然后再次进入锁定的方法,以尽可能避免锁定:

 public ChangingVariableSet GetValues()
    {
    if(lastModified == configFile.lastModified())
        ReReadConfig();
    <return necessary values>
    }
public ChangingVariableSet GetValues()
{
如果(lastModified==configFile.lastModified())
重新读取配置();
}
十分钟后,我了解到它被称为双重检查锁定,十分钟后,我的世界在阅读后崩溃了两次:第一次,我了解到由于内部CPU缓存,它可能无法工作;第二次,我了解到长/浮点类型上的操作不是原子的。或者,既然不涉及对象创建,它最终会起作用吗?而且,既然long上的操作是非原子的,那么将“lastModified”声明为volatile就足够了吗?如果可能的话,我更愿意对它为什么会/不会起作用进行适当的解释。先谢谢你

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


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

 public ChangingVariableSet GetValues()
    {
    if(lastModified == configFile.lastModified())
        ReReadConfig();
    <return necessary values>
    }
public ChangingVariableSet GetValues()
{
如果(lastModified==configFile.lastModified())
重新读取配置();
}
若文件在if检查和值返回之间的时间跨度内更新,线程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>
    }

private static final ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
私有静态最终锁read=readWriteLock.readLock();
private static final Lock write=readWriteLock.writeLock();
私有void重新读取配置
{
write.lock();
long tempLastMod=configFile.lastModified();
if(lastModified==tempLastMod)
返回;
lastModified=tempLastMod;
write.release();
}
公共更改变量集GetValues()
{
如果(lastModified==configFile.lastModified())
重新读取配置();
read.lock();
read.release();
}

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

您想使用。只要没有写入程序,这就不会阻止读卡器。

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

public static volatile Config config;

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

顺便说一句,你在这里的英语非常好。谢谢;不过,我不得不再读三遍:3毫米,谢谢你的建议。我确实想过,但不确定“volatile”是如何工作的,特别是因为“X=newy()”根本不是原子的。所以,澄清一下:如果我声明对“X”的共享引用为volatile,然后运行“X=newy()”,那么它将首先完全创建新的Y实例,然后才将其分配给X,对吗?如果我是对的,典型的惰性初始化陷阱是在构造函数完成其工作之前可以分配引用。是的,这就是volatile的语义。一个线程中写入volatile之前的所有操作
都发生在任何其他线程中读取该值之后的所有操作之前。你可能想退房,这是一个很好的选择