为什么使用HashMap线程不安全?

时间:2019-08-13 03:46:22

标签: java concurrency hashmap

我试图通过分配线程仅修改它们各自的节点来规避HashMap中的并发Node冲突。但是,预期的尺寸结果仍然不准确。

我知道HashMap不能并发,因为不同的线程会修改同一链接列表。 #hashSlot用于计算HashMap插入的节点。

这是我的例子:

public class Maptest {

    private static HashMap map = new HashMap(1024,1);
    public static void main(String[] args) throws InterruptedException {
        Thread writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    int a = hashSlot(String.valueOf(i));
                    if (a >= 500) {
                        map.put(String.valueOf(i), i);
                    }
                }
            }
        });
        writeThread.start();
        for (int i = 0; i < 1000; i++) {
            int a = hashSlot(String.valueOf(i));
            if (a < 500) {
                map.put(String.valueOf(i), i);
            }
        }
        writeThread.join();
        System.out.println(map.size());
    }

    public static int hashSlot(String i) {
        int h;
        int b = (h = i.hashCode()) ^ (h >>> 16);
        return 1023 & b;
    }
}

结果是带有size() != 1000的映射,但是没有一个哈希码在同一线程内发生冲突。为什么最终尺寸不是1000?

1 个答案:

答案 0 :(得分:0)

关于您当前测试用例的一些要点:

  • 在初始地图插入时,调用#resize构造表。这不是在施工中完成的,因此那里存在潜在的比赛条件。
  • 从上方继续,我怀疑类似于map.entrySet().stream().count()的值是否会返回999或1000,具体取决于是否满足比赛条件。
  • 通过使用String键而不是Integer键,您将留在Strings的#hashcode结果中,这很难预测。您可能在同一个存储桶中有几个项目,但这并不是一个太大的问题,因为您要按哈希结果进行分区。整数地图可以使制作拼图更加容易

现在,在回答有关size的特定问题时:

//HashMap#putVal (near method end)
if (++size > threshold) //both are fields
    resize();

threshold是大小限制,直到调用下一个#resize操作为止,在您的情况下,该操作被设置为initialCapacity参数:1024。在测试用例中,您永远都不会遇到这个问题。 / p>

然而size只是#put方法末尾的一个预增量,如果在地图上添加新值,它将始终被调用。这绝对是线程不安全的,但总体上不会对地图存储和检索结果的功能产生重大影响。就是说,不要指望size在这里确实能提供很好的价值,同时修改的地方越多,它就会变得越糟。真正的缺点是它会放弃地图调整大小的计算,从而降低性能。