为什么在单线程方法中会放置同步块?

12

我偶然发现了IBM - developerworks上的这篇文章,他们发布的代码引发了一些疑问:

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

  2. 实际上,为什么这段代码需要synchronized块呢?应该只需要一个volatile变量就足够了,因为新创建的地图只有在填充完毕后才会发布。

  3. 只有一个线程在锁对象上同步的目的是什么?

文章中提到:

在列表1中,同步块和volatile关键字是必需的,因为当前映射的写入和从currentMap中读取之间不存在happens-before关系。因此,如果不使用同步块和volatile关键字,则读取线程可能会看到垃圾。

代码中的注释说:

这必须同步,因为Java内存模型

我感觉自己在处理超出自己理解范围的多线程概念;我希望有更多专业知识的人能指点我正确的方向。


以下是从文章中摘录的代码:

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
@NiVeR 感谢您的评论。但这并没有解释synchronized块的作用?无论是否有该块,新创建的映射表在其他线程读取时都会被发布...或者不会吗?只有一个线程一次访问映射表,才需要消费者也有sync块或任何其他锁定机制... - payloc91
@Marko volatile 应该解决这个问题。 - NiVeR
1个回答

8
根据Java内存模型,volatile写和随后的volatile读之间存在“happens-before”关系,这意味着m = currentMap;保证能看到在currentMap = newMap;之前发生的一切,synchronized块是不需要的。
不仅如此,它完全没有作用,以下内容:

this must be synchronized because of the Java memory model

After the above synchronization block, everything that is in the HashMap is visible outside this thread

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

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


这正是我所想的。基本上,根据这篇文章,在每个多线程的情况下,您都必须同步每个修改类字段的线程方法。 - payloc91
也许,这篇文章基于Java 5之前的知识,其中volatile变量的保证过于薄弱。但即使在那时,没有其他线程在相同对象上使用synchronized的情况下,synchronized块的保证也是太弱了。 - Holger
@Holger 他们在文章末尾说到它是关于Java 5的:“需要注意的是,由于Java内存模型的复杂性,这里描述的技术仅适用于Java 1.5及更高版本。” - Oleg
@Oleg 是的,我在写评论之后注意到了这一点。我还注意到,IBM上的这篇文章似乎是Brian Goetz文章的一个严重扭曲版本。显然,IBM文章的作者未能意识到他们的代码与其他文章有哪些不同之处。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接