HashMap成员的并发修改

时间:2018-07-18 17:04:23

标签: java multithreading concurrency hashmap

我已经定义了地图

private static HashMap<Object, Object> myMap;

它填充在一个单线程中,然后那个单线程产生了更多的线程,这些线程改变了map元素内的数据,但不改变map结构(没有remove / put / etc)。此外,每个线程仅更改地图的一个唯一成员(没有两个线程更改同一成员)。

我的问题是:一旦所有更改完成,主线程是否会看到对hashmap成员的更改?如果不是,在声明中添加volatile是可行的,还是仅保证其他线程看到结构更改?谢谢

编辑:希望以更清晰的方式突出显示我在做什么的代码

import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TestingRandomStuff {
    public static void main(String[] args) throws Exception {
        HashMap<Object, Object> myMap = new HashMap();
        ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        //myMap is populated by this thread, and the objects inside are initialized, but are left largely empty
        populate();

        for (Object o : myMap.values()) {
            Runnable r = new Task(o);
            pool.execute(r);
        }
        pool.shutdown();
        try {
            pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
        } catch (InterruptedException e) {;}

        //How do I gurantee that the objects inside myMap are displayed correctly with all the data that was just loaded by seperate threads,
        //or is it already guranteed?
        displayObjectData();
    }

    public static class Task implements Runnable {
        private Object o;

        public Task(Object o) {this.o = o;}

        public void run() {
            try {
                o.load(); //o contains many complicated instance variables that will be created and written to
            } catch (Exception e) {;}
        }
    }
}

3 个答案:

答案 0 :(得分:4)

编辑:在您的示例中,仅在该映射引用的对象中,其他线程无法访问该映射。

由于对象的使用方式,对象本身应该是线程安全的。

注意:如果使用parallelStream(),则代码会更简单。


  

其他线程会看到对哈希映射成员的更改吗?

可能,但是没有担保人

  

如果没有,将在声明工作中添加volatile

volatile仅在对地图的引用上添加读取障碍。 (除非您将获得写障碍的位置更改为指向另一个Map,否则将其指向该字段。)

  

还是只能保证其他线程看到结构更改?

否,只能保证看到对myMap引用的更改,而不是对Map或Map中任何内容的更改。即保证金很浅。

有多种方法可以提供线程安全性,但是最简单的方法是在读写时synchronized进行对象操作。您可以使用volatile字段进行一些技巧,但是在很大程度上取决于您在做什么,thi是否可以工作。

答案 1 :(得分:1)

您可以使用ConcurrentHashMap

  

检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠。检索反映了发生后最新完成的更新操作的结果。

这意味着一个线程所做的更改对于另一个读取同一键的值的线程可见。但是两者可能会干扰。

答案 2 :(得分:1)

在Java中,没有对象嵌入其他对象中,由多个对象组成的所有数据结构都是参考图,换句话说,对象的集合是引用的集合,而具有键和值的对象的映射是包含对这些对象的引用的地图。

因此,您的对象从未成为“哈希图成员”,而仅被哈希图引用。因此,为了讨论代码的线程安全性,HashMap的存在只是一个红色鲱鱼,因为您的多线程代码永远不会看到HashMap的任何伪像。

您有代码创建多个不同的对象,然后将Task实例提交给ExecutorService进行处理,每个实例包含对这些对象之一的引用。假定这些对象不共享可变状态,这是一种简单的线程安全方法。

在等待所有作业完成之后,主线程可以确保看到作业内所有操作的结果,即对这些对象所做的修改。再次使用您的HashMap或其他任何内容来引用这些对象之一来查看这些修改都是完全不相关的。

如果您以影响键的相等性或哈希码的方式修改映射键,但这与线程安全问题无关。您绝对不能以这种方式修改映射键,即使在单线程代码中也是如此,违反该约定甚至会破坏线程安全映射。但是,由于您的对象被引用为值,所以没有这种问题。

只有一个极端的情况要注意。您的等待完成包含catch (InterruptedException e) {;}行,因此没有完全保证在执行该语句之后确实完成了所有作业,但这是可见性保证的要求。由于您似乎假设永远都不会发生中断,因此应该使用类似catch(InterruptedException e) { throw new AssertionError(e); }的名称,以确保不会默默地发生违反该假设的情况,同时获得完全的可见性保证,因为现在displayObjectData();条语句只有在所有工作都完成后才能到达(或者Long.MAX_VALUE秒过去了,我们当中没有人会目睹)。