2011-03-12 61 views
7

我有一个名为statusCountMap的Java HashMap。
呼叫大小()导致30
但是如果我手动计数的条目,这是31
这是我的TestNG的单元测试之一。下面的这些结果来自Eclipse的Display窗口(键入code - > highlight - >点击Display Results of Evaluating Selected Text)。Java HashMap的大小()能否与其实际条目的大小不同步?

 
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未在这一点上进行修改。
有两种方法(让我们称它们为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 (键,0)这是造成这个问题?

+1

你能显示所有的代码吗?也许一个是默认条目,尝试20个条目,看看你是否得到21 – Vladp 2011-03-12 15:14:16

+0

你如何计数? – Erik 2011-03-12 15:14:25

+0

我只计算了{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,4515-ACTIVE = 1,40513-DRAFT = 4,40534-DRAFT = 1,40514-TOTAL = 3 ,40529-DRAFT = 4,4515-TOTAL = 3,40492-ACTIVE = 1,40528-TOTAL = 4,40514-DRAFT = 2,40526-TOTAL = 3,40524-INACTIVE = 2,4515-DRAFT = 2,44014 -ACTIVE = 1,40534-TOTAL = 3,40513-ACTIVE = 2,40528-DRAFT = 2,4532-TOTAL = 4,40524-ACTIVE = 3,40529-ACTIVE = 1,40529-TOTAL = 5} – trix 2011-03-12 15:22:09

回答

11

我相信,如果您在将密钥添加到HashMap后修改密钥,则可以实现此目的。

但在你的情况下,它似乎只是在两个线程中修改相同的映射而没有正确同步的情况。例如在线程A,map.put(键,0),线程B map.put(KEY2,0)可导致尺寸为1或2.如果相同的与删除可以用一个大小比你更大最终应该。

+0

你的意思是key = new AnEntity(); map.put(key,1);此时map.size()为1; key.setMember( “XYZ”); map.size()不再是1?你能告诉我一个例子吗?我不认为这是这种情况,因为所有的修改都是通过incrementCountInMap()完成的,我不修改这些键。 – trix 2011-03-12 15:27:20

2

第一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} 
4

嗯......重读我的帖子后,会不会是因为同步外的-块地图的并发执行的。 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); 
    } 
} 
4

如果使用16的默认的初始容量和访问他们在一个非线程安全的方式为你成熟不一致的状态映射。大小是在输入每个项目(大小为++)时更新地图中的状态成员。这是因为地图本身是一个链接列表数组,并且不能真正返回其实际大小,因为它不表示它包含的项目数。一旦地图达到初始容量的百分比(load_factor),它必须调整其自身以容纳更多项目。如果一个流氓线程试图添加项目,因为地图正在调整大小,谁知道地图将处于什么状态。