Can the Java HashMap () size not sync with the size of the actual records?

I have a Java HashMap called statusCountMap.
The calling size () of the results is 30.
But if I read the entries manually, this is 31. This is one of my TestNG test cases. Below are the results from the Eclipse display window below (type code → highlight → click Show result of selected text evaluation).

  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? Has anyone experienced this?
I am pretty sure that statusCountMap will not be changed at the moment.
There are 2 methods (allows you to call their method A and method B) that change the statusCountMap at the same time , repeating the call to 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 get this problem. methodD has TestNG @dependsOnMethods = {"methodA", "methodB"}, so when methodD is executed, statusCountMap is already pretty static. I mention this because it may be a bug in TestNG.
I am using Sun JDK 1.6.0_24. TestNG - testng-5.9-jdk15.jar

Hmmm ... after re-reading my message, could it be due to the parallel execution of the out-of-sync block map.get (key) == null and map.put (key, 0) that cause this problem?

+7
source share
4 answers

I believe that you can achieve this if you change the key after adding it to the HashMap.

However, in your case, this is just a case of changing the same card in two streams without proper synchronization. for example, in thread A, map.put (key, 0), thread B map.put (key2, 0) can have a size of 1 or 2. If you do the same with deletion, you can get a size larger than you should.

+11
source

Hmmm ... after re-reading my message, could it be due to the parallel execution of the out-of-sync block map.get (key) == null and map.put (key, 0) that cause this problem?

In a word ... yes.

HashMap is not thread safe. Therefore, if there is any point at which two threads can update the HashMap without proper synchronization, the card may fall into an inconsistent state. And even if one of the threads is only reading, this thread may see an inconsistent state for the card.

The correct way to write this 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); } } 
+4
source

If you use the default default initial capacity of 16 and access it, map it using non-threaded security, which is ripe for an inconsistent state. Size is a member of the state in the Map, updated as each element is entered (size ++). This is due to the fact that the map itself is an array of linked lists and cannot really return its actual size, since it does not indicate the number of elements that it contains. When the Card reaches the percentage (load_factor) of the internal capacity, it must resize to accommodate more items. If the rogue thread is trying to add elements as the size of the map changes, who knows what condition the map will be in.

+4
source

The problem is that the first map.put (..) is not 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 that 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} 
+2
source

All Articles