Reputation: 1835
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 ?
Upvotes: 8
Views: 8528
Reputation: 15363
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.
Upvotes: 4
Reputation: 718788
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);
}
}
Upvotes: 4
Reputation: 50097
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}
Upvotes: 3
Reputation: 533500
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.
Upvotes: 12