当前位置: 首页 > 知识库问答 >
问题:

双重检查锁定陷阱?

吉鸿宝
2023-03-14

我使用Java大约一个月了,在编程方面仍然是一个一般的业余爱好者,所以如果我有什么不对的地方,请随时纠正我。也许我会提供一些多余的细节,但我现在是如此困惑,我无法决定什么是重要的。

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

经过一番研究后想到的第一个想法是创建一个同步方法,当请求类中的某些值时,该方法将被调用,如果配置文件自上次访问以来发生更改,则该方法将重新读取这些值,否则将立即返回,如下所示:

<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真的足够吗?如果可能的话,我更希望得到一个恰当的解释,说明为什么它会/不会奏效。提前谢谢你。

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

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

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

如果在if-check和返回值之间的时间跨度内更新了case file,则线程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”类型的读/写是非原子的。

共有1个答案

宋典
2023-03-14

您希望使用读写锁。只要没有写作者,这就不会阻塞读者。

 类似资料:
  • 问题内容: 我偶然遇到了一篇文章,该文章最近讨论了Java中的双重检查锁定模式及其陷阱,现在我想知道我多年来使用的那种模式的变体是否会遇到任何问题。 我看过许多关于该主题的文章和文章,并了解了对部分构造的对象的引用所带来的潜在问题,据我所知,我认为我的实现不受这些问题的影响。以下模式是否有问题? 而且,如果没有,人们为什么不使用它?在围绕此问题进行的任何讨论中,我从未见过推荐它的方法。 问题答案:

  • 这是我为单例模式的自定义类。在这段代码中,我使用双重检查锁定,如下所示。当我在一些源代码上读到许多帖子时,他们说双重检查很有用,因为它可以防止两个并发线程同时运行,从而产生两个不同的对象。 我还是不太懂上面的代码。当实例为null时,如果两个线程一起运行同一行代码,问题是什么? 当它出现时。两个线程都将看到对象为null。然后两者同步。然后,他们再次检查,仍然看到它为空。并创建两个不同的对象。哎呀

  • 问题内容: 此问题与旧Java版本的行为以及双重检查锁定算法的旧实现有关 较新的实现使用volatile并依赖于略有更改的语义,因此它们 不会 损坏。 声明字段分配始终是原子的,除了long或double字段。 但是,当我阅读解释为什么双重检查锁定被破坏时,据说问题出在赋值操作中: 线程A注意到该值未初始化,因此它获得了锁并开始初始化该值。 由于某些编程语言的语义,允许编译器生成的代码在A完成执行

  • 据我所知,这是Java中双重检查锁定模式的正确实现(自Java5): 我想知道缺少是一个严重的错误,还是一个可能存在性能缺陷的轻微缺陷,假设只能通过访问。 我的想法是这样的:引入了事前发生关系。初始化的线程将其值写入离开同步块的主存储器。因此,即使不是易失性的,也不会有的双重初始化:其他线程的本地副本中有(在第一次检查中得到),但是必须在进入同步块后的第二次检查中从主存储器中读取新值(get并且不

  • 问题内容: 在设计模式手册中,具有双重检查锁定的单例模式已实现如下: 我不明白为什么要使用它。使用不会 违反使用双重检查锁定(即性能)的目的吗? 问题答案: 真正的问题是可能在完成构造之前为其分配存储空间。将看到该作业并尝试使用它。由于使用的是的部分构造版本,因此导致失败。

  • 本文向大家介绍Java线程安全的Singleton具有双重检查的锁定,包括了Java线程安全的Singleton具有双重检查的锁定的使用技巧和注意事项,需要的朋友参考一下 示例 这种类型的Singleton是线程安全的,并且可以在创建Singleton实例之后防止不必要的锁定。 Java SE 5 必须强调的是-在Java SE 5之前的版本中,上述实现是错误的,应避免使用。在Java 5之前的J