【问题标题】:Why would I place a synchronized block within a single-threaded method?为什么要在单线程方法中放置同步块?
【发布时间】:2018-03-16 21:22:37
【问题描述】:

我在 IBM - developerworks 上偶然发现了 this article,他们发布的代码让我提出了一些问题:

  1. 为什么局部变量Map 的构建被包裹在synchronized 块中?请注意,他们隐含地说只有一个producer 线程。

  2. 实际上,为什么这个 sn-p 需要 synchronized 块呢? volatile 变量应该足以完成这项工作,因为新创建的地图只有在填满后才会发布。

  3. 锁对象上只有一个线程synchronizing有什么意义?

文章提到:

清单 1 中的同步块和 volatile 关键字是必需的,因为在对 currentMap 的写入和从 currentMap 的读取之间不存在发生前的关系。因此,如果未使用同步块和 volatile 关键字,读取线程可能会看到垃圾。

并且代码中的注释说:

由于 Java 内存模型,这必须同步

我觉得我正在处理超出我理解范围的多线程概念;我希望有更专业的人为我指明正确的方向。


这是从文章中摘录的sn-p:

static volatile Map currentMap = null;   // this must be volatile
static Object lockbox = new Object();  

public static void buildNewMap() {       // this is called by the producer     
    Map newMap = new HashMap();          // when the data needs to be updated

    synchronized (lockbox) {                 // this must be synchronized because
                                     // of the Java memory model
        // .. do stuff to put things in newMap
        newMap.put(....);
        newMap.put(....);
    }                 
    /* After the above synchronization block, everything that is in the HashMap is 
    visible outside this thread */

    /* Now make the updated set of values available to the consumer threads.  
    As long as this write operation can complete without being interrupted, 
    and is guaranteed to be written to shared memory, and the consumer can 
    live with the out of date information temporarily, this should work fine */

     currentMap = newMap;

}

public static Object getFromCurrentMap(Object key) {
    Map m = null;
    Object result = null;

    m = currentMap;               // no locking around this is required
    if (m != null) {              // should only be null during initialization
         Object result = m.get(key); // get on a HashMap is not synchronized

    // Do any additional processing needed using the result
    }
    return(result);

}

【问题讨论】:

  • 在这种情况下,单线程是生产者这一事实并不是很重要。当消费者线程尝试读取而生产者写入时,就会出现问题。
  • @NiVeR 感谢您的评论。这并不能解释synchronized 块吗?不管那个区块有没有,都会出现新创建的地图发布而其他人阅读的情况……或者不是吗?一次只有一个线程访问地图一次将要求消费者也有一个sync 块,或任何其他锁定机制......
  • @Marko volatile 应该解决这个问题。

标签: java multithreading synchronized volatile producer-consumer


【解决方案1】:

根据Java memory model,在 volatile 写入和后续 volatile 读取之间存在 happens-before 关系,这意味着m = currentMap; 可以保证看到currentMap = newMap; 之前发生的所有事情,则不需要synchronized 块。

不仅如此,它还完全没有任何作用:

由于 Java 内存模型,这必须同步

在上面的同步块之后,HashMap中的所有内容都是 在此线程之外可见

cmets 是错误的。根据 Java 内存模型,只有当两个线程都是 synchronized 时,才存在 happens-before 关系;根据JMM,在文章中使用它绝对没有任何作用(一些JVM 实现可以做一些事情,也许2007 年的一些IBM JVM 实现在这种情况下执行了某种同步,但JMM 不需要它)。

总之,IBM 的文章是完全错误的。

【讨论】:

  • 我就是这么想的。基本上,根据文章,在每种多线程情况下,您都必须同步每个更改类字段的单线程方法
  • 也许,这篇文章是基于 Java 5 之前的知识,volatile 变量的保证太弱了。但即便如此,没有其他线程在同一对象上使用synchronizedsynchronized 块的保证也太弱了。
  • @Holger 他们在文章末尾说这是关于 Java 5 的“请务必注意,由于 Java 内存模型的复杂性,此处描述的技术仅适用于 Java 1.5 及更高版本”
  • @Oleg 是的,我在写完评论后注意到了。我进一步注意到,这篇关于 IBM 的文章似乎是对 Brian Goetz 文章的严重扭曲版本。显然,IBM 文章的作者未能认识到他们的代码与其他文章的不同之处。
猜你喜欢
  • 2012-11-23
  • 2014-07-04
  • 2019-02-26
  • 2014-01-21
  • 1970-01-01
  • 2013-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多