Java HashMap 的 size() 是否会与其实际条目不同步?尺寸?

发布于 2024-10-21 18:27:33 字数 1624 浏览 1 评论 0原文

我有一个名为 statusCountMap 的 Java HashMap。
调用 size() 结果为 30。
但如果我手动计算条目,则为 31
这是我的 TestNG 单元测试之一。下面的这些结果来自 Eclipse 的显示窗口(键入代码 -> 突出显示 -> 点击显示评估所选文本的结果)。

statusCountMap.size()
     (int) 30
statusCountMap.keySet().size()
     (int) 30
statusCountMap.values().size()
     (int) 30
statusCountMap
     (java.util.HashMap) {40534-INACTIVE=2, 40526-INACTIVE=1, 40528-INACTIVE=1, 40492-INACTIVE=3, 40492-TOTAL=4, 40513-TOTAL=6, 40532-DRAFT=4, 40524-TOTAL=7, 40526-DRAFT=2, 40528-ACTIVE=1, 40524-DRAFT=2, 40515-ACTIVE=1, 40513-DRAFT=4, 40534-DRAFT=1, 40514-TOTAL=3, 40529-DRAFT=4, 40515-TOTAL=3, 40492-ACTIVE=1, 40528-TOTAL=4, 40514-DRAFT=2, 40526-TOTAL=3, 40524-INACTIVE=2, 40515-DRAFT=2, 40514-ACTIVE=1, 40534-TOTAL=3, 40513-ACTIVE=2, 40528-DRAFT=2, 40532-TOTAL=4, 40524-ACTIVE=3, 40529-ACTIVE=1, 40529-TOTAL=5}
statusCountMap.entrySet().size()
     (int) 30

什么给出?有人有过这样的经历吗?
我很确定 statusCountMap 此时没有被修改。
有 2 个方法(我们称之为 methodA 和 methodB)可以通过重复调用incrementCountInMap来并发修改statusCountMap。

private void incrementCountInMap(Map map, Long id, String qualifier) {
    String key = id + "-" + qualifier;
    if (map.get(key) == null) {
        map.put(key, 0);
    }
    synchronized (map) {
        map.put(key, map.get(key).intValue() + 1);
    }
}

methodD 是我遇到问题的地方。 methodD 有一个 TestNG @dependsOnMethods = { "methodA", "methodB" } 因此当 methodD 执行时,statusCountMap 几乎已经是静态的了。 我提到这一点是因为它可能是 TestNG 中的一个错误。
我使用的是 Sun JDK 1.6.0_24。 TestNG 是 testng-5.9-jdk15.jar

嗯...重读我的帖子后,是否是因为并发执行了同步块外部 map.get(key) == null & map.put(key,0) 导致了这个问题?

I have a Java HashMap called statusCountMap.
Calling size() results in 30.
But if I count the entries manually, it's 31
This is in one of my TestNG unit tests. These results below are from Eclipse's Display window (type code -> highlight -> hit Display Result of Evaluating Selected Text).

statusCountMap.size()
     (int) 30
statusCountMap.keySet().size()
     (int) 30
statusCountMap.values().size()
     (int) 30
statusCountMap
     (java.util.HashMap) {40534-INACTIVE=2, 40526-INACTIVE=1, 40528-INACTIVE=1, 40492-INACTIVE=3, 40492-TOTAL=4, 40513-TOTAL=6, 40532-DRAFT=4, 40524-TOTAL=7, 40526-DRAFT=2, 40528-ACTIVE=1, 40524-DRAFT=2, 40515-ACTIVE=1, 40513-DRAFT=4, 40534-DRAFT=1, 40514-TOTAL=3, 40529-DRAFT=4, 40515-TOTAL=3, 40492-ACTIVE=1, 40528-TOTAL=4, 40514-DRAFT=2, 40526-TOTAL=3, 40524-INACTIVE=2, 40515-DRAFT=2, 40514-ACTIVE=1, 40534-TOTAL=3, 40513-ACTIVE=2, 40528-DRAFT=2, 40532-TOTAL=4, 40524-ACTIVE=3, 40529-ACTIVE=1, 40529-TOTAL=5}
statusCountMap.entrySet().size()
     (int) 30

What gives ? Anyone has experienced this ?
I'm pretty sure statusCountMap is not being modified at this point.
There are 2 methods (lets call them methodA and methodB) that modify statusCountMap concurrently, by repeatedly calling incrementCountInMap.

private void incrementCountInMap(Map map, Long id, String qualifier) {
    String key = id + "-" + qualifier;
    if (map.get(key) == null) {
        map.put(key, 0);
    }
    synchronized (map) {
        map.put(key, map.get(key).intValue() + 1);
    }
}

methodD is where I'm getting the issue. methodD has a TestNG @dependsOnMethods = { "methodA", "methodB" } so when methodD is executing, statusCountMap is pretty much static already.
I'm mentioning this because it might be a bug in TestNG.
I'm using Sun JDK 1.6.0_24. TestNG is testng-5.9-jdk15.jar

Hmmm ... after rereading my post, could it be because of concurrent execution of outside-of-synchronized-block map.get(key) == null & map.put(key,0) that's causing this issue ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

趴在窗边数星星i 2024-10-28 18:27:33

我相信如果你在将键添加到 HashMap 后修改它,你就可以实现这一点。

然而,在您的情况下,这似乎只是在两个线程中修改同一映射而没有正确同步的情况。例如,在线程 A 中,map.put(key, 0)、线程 B 中的 map.put(key2, 0) 可能会导致大小为 1 或 2。如果对删除执行相同操作,最终可能会得到大于你应该。

I believe you can achieve this if you modify a key after it is added to a HashMap.

However in your case it appears to be just a case of modifying the same map in two threads without proper synchronization. e.g. in thread A, map.put(key, 0), thread B map.put(key2, 0) can results in a size of 1 or 2. If you do the same with remove you can end up with a size larger than you should.

木格 2024-10-28 18:27:33

嗯……重读我的帖子后,是否是因为同步块外的map.get(key) == null & 并发执行? map.put(key,0) 导致了这个问题?

总之……是的。

HashMap 不是线程安全的。因此,如果两个线程在没有适当同步的情况下更新 HashMap,则映射可能会进入不一致状态。即使其中一个线程只是读取,该线程也可能看到地图的不一致状态。

编写该方法的正确方法是:

private void incrementCountInMap(Map map, Long id, String qualifier) {
    String key = id + "-" + qualifier;
    synchronized (map) {
        Integer count = map.get(key);
        map.put(key, count == null ? 1 : count + 1);
    }
}

Hmmm ... after rereading my post, could it be because of concurrent execution of outside-of-synchronized-block map.get(key) == null & map.put(key,0) that's causing this issue ?

In a word ... yes.

HashMap is not thread-safe. Therefore, if there is any point where two threads could update a HashMap without proper synchronization, the map could get into an inconsistent state. And even if one of the threads is only reading, that thread could see an inconsistent state for the map.

The correct way to write that method is:

private void incrementCountInMap(Map map, Long id, String qualifier) {
    String key = id + "-" + qualifier;
    synchronized (map) {
        Integer count = map.get(key);
        map.put(key, count == null ? 1 : count + 1);
    }
}
濫情▎り 2024-10-28 18:27:33

如果您使用默认的初始容量 16 并以非线程安全的方式访问它们映射,那么您就会陷入不一致的状态。 Size 是 Map 中的一个状态成员,随着每个项目的输入而更新(size++)。这是因为地图本身是一个链表数组,无法真正返回其实际大小,因为它不指示其包含的项目数。一旦地图达到初始容量的百分比(load_factor),它就必须调整自身大小以容纳更多项目。如果恶意线程在调整地图大小时尝试添加项目,谁知道地图将处于什么状态。

If you using the default initial capacity of 16 and accessing them map in a non thread safe manner your ripe for an inconsistent state. Size is a state member in the Map getting updated as each item is entered(size++). This is because the map itself is an array of linked lists and cannot really return its actual size because its not indicative of the number of items it contains. Once the Map reaches a percentage(load_factor) of the intial capacity it has to resize itself to accomodate more items. If a rogue thread is attempting to add items as the map is resizing who knows what state the map will be in.

月棠 2024-10-28 18:27:33

第一个map.put(..)不同步的问题。要么对其进行同步,要么使用Collections.synchronizedMap(..)。测试用例:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class Test {
    public static void main(String... args) throws InterruptedException {
        final Random random = new Random();
        final int max = 10;
        for (int j = 0; j < 100000; j++) {
            // final Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
            final HashMap<String, Integer> map = new HashMap<String, Integer>();
            Thread t = new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        incrementCountInMap(map, random.nextInt(max));
                    }
                }
            };
            t.start();
            for (int i = 0; i < 100; i++) {
                incrementCountInMap(map, random.nextInt(max));
            }
            t.join();
            if (map.size() != max) {
                System.out.println("size: " + map.size() + " entries: " + map);
            }
        }
    }
    static void incrementCountInMap(Map<String, Integer> map, int id) {
        String key = "k" + id;
        if (map.get(key) == null) {
            map.put(key, 0);
        }
        synchronized (map) {
            map.put(key, map.get(key).intValue() + 1);
        }
    }

}

我得到的一些结果:

size: 11 entries: {k3=24, k4=20, k5=16, k6=30, k7=16, k8=18, k9=11, k0=18, k1=16, k1=13, k2=18}
size: 11 entries: {k3=18, k4=19, k5=21, k6=20, k7=18, k8=26, k9=20, k0=16, k1=25, k2=15}
size: 11 entries: {k3=25, k4=20, k5=27, k6=15, k7=17, k8=17, k9=24, k0=21, k1=16, k1=1, k2=17}
size: 11 entries: {k3=13, k4=21, k5=18, k6=21, k7=13, k8=17, k9=25, k0=20, k1=23, k2=28}
size: 11 entries: {k3=21, k4=25, k5=19, k6=12, k7=17, k8=14, k9=23, k0=24, k1=26, k2=18}
size: 9 entries: {k3=13, k4=17, k5=23, k6=24, k7=18, k8=19, k9=28, k0=21, k1=17, k2=20}
size: 9 entries: {k3=15, k4=24, k5=21, k6=18, k7=21, k8=30, k9=20, k0=17, k1=15, k2=19}
size: 11 entries: {k3=15, k4=13, k5=21, k6=21, k7=15, k8=19, k9=23, k0=30, k1=15, k2=27}
size: 11 entries: {k3=29, k4=15, k5=19, k6=19, k7=15, k8=23, k9=14, k0=31, k1=18, k2=12}
size: 11 entries: {k3=17, k4=18, k5=20, k6=11, k6=13, k7=20, k8=22, k9=30, k0=12, k1=21, k2=16}

The problem that the first map.put(..) isn't synchronized. Either synchronize it, or use Collections.synchronizedMap(..). Test case:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class Test {
    public static void main(String... args) throws InterruptedException {
        final Random random = new Random();
        final int max = 10;
        for (int j = 0; j < 100000; j++) {
            // final Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
            final HashMap<String, Integer> map = new HashMap<String, Integer>();
            Thread t = new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        incrementCountInMap(map, random.nextInt(max));
                    }
                }
            };
            t.start();
            for (int i = 0; i < 100; i++) {
                incrementCountInMap(map, random.nextInt(max));
            }
            t.join();
            if (map.size() != max) {
                System.out.println("size: " + map.size() + " entries: " + map);
            }
        }
    }
    static void incrementCountInMap(Map<String, Integer> map, int id) {
        String key = "k" + id;
        if (map.get(key) == null) {
            map.put(key, 0);
        }
        synchronized (map) {
            map.put(key, map.get(key).intValue() + 1);
        }
    }

}

Some results I get:

size: 11 entries: {k3=24, k4=20, k5=16, k6=30, k7=16, k8=18, k9=11, k0=18, k1=16, k1=13, k2=18}
size: 11 entries: {k3=18, k4=19, k5=21, k6=20, k7=18, k8=26, k9=20, k0=16, k1=25, k2=15}
size: 11 entries: {k3=25, k4=20, k5=27, k6=15, k7=17, k8=17, k9=24, k0=21, k1=16, k1=1, k2=17}
size: 11 entries: {k3=13, k4=21, k5=18, k6=21, k7=13, k8=17, k9=25, k0=20, k1=23, k2=28}
size: 11 entries: {k3=21, k4=25, k5=19, k6=12, k7=17, k8=14, k9=23, k0=24, k1=26, k2=18}
size: 9 entries: {k3=13, k4=17, k5=23, k6=24, k7=18, k8=19, k9=28, k0=21, k1=17, k2=20}
size: 9 entries: {k3=15, k4=24, k5=21, k6=18, k7=21, k8=30, k9=20, k0=17, k1=15, k2=19}
size: 11 entries: {k3=15, k4=13, k5=21, k6=21, k7=15, k8=19, k9=23, k0=30, k1=15, k2=27}
size: 11 entries: {k3=29, k4=15, k5=19, k6=19, k7=15, k8=23, k9=14, k0=31, k1=18, k2=12}
size: 11 entries: {k3=17, k4=18, k5=20, k6=11, k6=13, k7=20, k8=22, k9=30, k0=12, k1=21, k2=16}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文