Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/318.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么不是';HashMap的这种使用不是线程安全的吗?_Java_Concurrency_Hashmap - Fatal编程技术网

Java 为什么不是';HashMap的这种使用不是线程安全的吗?

Java 为什么不是';HashMap的这种使用不是线程安全的吗?,java,concurrency,hashmap,Java,Concurrency,Hashmap,我试图在HashMap中通过分配线程只修改各自的节点来避免并发的节点冲突。然而,预期的尺寸结果仍然不准确 我知道HashMap不能是并发的,因为不同的线程修改相同的链表#hashSlot用于计算HashMap插入的节点 以下是我的例子: public class Maptest { private static HashMap map = new HashMap(1024,1); public static void main(String[] args) throws Int

我试图在HashMap中通过分配线程只修改各自的节点来避免并发的
节点
冲突。然而,预期的尺寸结果仍然不准确

我知道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;
    }
}
公共类映射测试{
私有静态HashMap=newhashmap(1024,1);
公共静态void main(字符串[]args)引发InterruptedException{
Thread writeThread=新线程(new Runnable(){
@凌驾
公开募捐{
对于(int i=0;i<1000;i++){
int a=hashSlot(String.valueOf(i));
如果(a>=500){
map.put(String.valueOf(i),i);
}
}
}
});
writeThread.start();
对于(int i=0;i<1000;i++){
int a=hashSlot(String.valueOf(i));
如果(a<500){
map.put(String.valueOf(i),i);
}
}
writeThread.join();
System.out.println(map.size());
}
公共静态int hashSlot(字符串i){
int-h;
intb=(h=i.hashCode())^(h>>>16);
返回1023&b;
}
}

结果是一个具有
size()!=1000
,但在同一线程中没有任何哈希代码发生冲突。为什么结束大小不是1000?

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

  • 在初始地图插入时,调用#resize来构造表。这不是在建筑中完成的,所以这里有一个潜在的竞争条件
  • 从上面继续,我怀疑类似于
    map.entrySet().stream().count()
    的东西会返回999或1000,这取决于是否达到了竞争条件
  • 通过使用字符串键而不是整数键,您可以使用字符串的#hashcode结果,这是不可预测的。您可能会在同一个bucket中有一些项,但这应该不会有太大的问题,因为您正在根据散列结果进行分区。整型地图可能会使制作拼图变得更容易
现在,在回答有关
尺寸的具体问题时:

//HashMap#putVal (near method end)
if (++size > threshold) //both are fields
    resize();
threshold
是在调用下一个#resize操作之前的大小限制,在您的情况下,该操作设置为
initialCapacity
参数:1024。在您的测试用例中,您永远不会遇到这个问题


但是,
size
只是#put方法末尾的一个预增量,如果向映射添加新值,它将始终被调用。这是非常不安全的线程,但总体上不会对映射的存储和检索结果的功能产生重大影响。这就是说,不要指望
size
在这里真的给出一个好的值,同时修改它的次数越多,它只会变得更糟。真正的缺点是,它会偏离地图大小调整计算,降低性能。

线程安全不会在默认情况下或意外情况下发生。HashMap不是为线程安全而设计的,所以它不是。因为it.OP不寻求并发HashMap实现,他想知道为什么他为HashMap定制的、应该是线程安全的案例不是。这实际上是一个有趣的问题,一旦你深入到源代码,也许与视图/父类实现有冲突,粗略地看一眼hashmap似乎是安全的。进一步澄清一下:他使用了与hashmap内部相同的哈希算法,并试图只插入两组不同的bucket(0-499和500-999)。我自己还没有找到具体的失败点,但我喜欢并发受虐。我可能理解你的答案。我通过调试检查,发现我计算的插入点与hashmap的最终插入点相同。Integer与key有相同的问题。我认为问题应该是在不同的C中写入存储PU缓存。这导致不同线程写入
/***存储单元数组时覆盖。在第一次插入时延迟初始化。*大小始终是二的幂。由迭代器直接访问。*/transient volatile Node[]table;
并发HashMap允许内存同步。但我将代码中的#map更改为volatile,问题仍然存在。
volatile
不会以这种方式工作;它会使获取或设置
map
变量本身原子化,但不会影响类内部的任何内容。很像
final
对象仍然可以修改其内容。我在
Integer
键上的点更重要,因此您可以执行
map.put(1023,42);
初始化基础表,同时保持在两个线程的边界之外。Integer的哈希代码就是整数本身。最终是的,
++size
不是一个原子操作;您有一个内存读取、一个增量和一个内存写入。两个线程可以在不同的点执行这些操作,因此在另一个还在增加。顺便说一句,如果最终的结果不仅仅是修修补补,而是尝试学习如何使事情线程安全,我建议您尝试使用
synchronized
关键字,学习原子操作,比如
ReentrantLock
StampedLock