Может ли размер Java (HashMap) не синхронизироваться с фактическим размером записей? - PullRequest
8 голосов
/ 12 марта 2011

У меня есть Java HashMap с именем statusCountMap.
Размер вызова () приводит к 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), которые изменяют statusCountMap одновременно , многократно вызывая 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 - вот где у меня проблема. У methodD есть TestNG @dependsOnMethods = {"methodA", "methodB"}, поэтому, когда выполняется methodD, statusCountMap уже довольно статичен. Я упоминаю об этом, потому что это может быть ошибка в TestNG.
Я использую Sun JDK 1.6.0_24. TestNG is testng-5.9-jdk15.jar

Хммм ... после перечитывания моего поста, может ли это быть из-за одновременного выполнения вне синхронизированного блока map.get (key) == null & map.put (key, 0), которое вызывает эту проблему?

Ответы [ 4 ]

12 голосов
/ 12 марта 2011

Я полагаю, что этого можно достичь, если вы измените ключ после его добавления в HashMap.

Однако в вашем случае это, похоже, всего лишь случай изменения одной и той же карты в двух потоках без надлежащей синхронизации. например В потоке A map.put (key, 0), в потоке B map.put (key2, 0) может привести к размеру 1 или 2. Если вы сделаете то же самое с удалением, вы можете получить размер больше, чем вы. должен.

4 голосов
/ 12 марта 2011

Если вы используете начальную емкость по умолчанию, равную 16, и обращаетесь к ним, сопоставьте их без использования потоков, что может привести к противоречивому состоянию. Размер - это элемент состояния на Карте, который обновляется по мере ввода каждого элемента (размер ++). Это связано с тем, что карта сама по себе является массивом связанных списков и не может действительно вернуть ее фактический размер, поскольку она не указывает на количество элементов, которые она содержит. Как только Карта достигает процента (load_factor) начальной емкости, она должна изменить свой размер, чтобы вместить больше элементов. Если мошенническая ветка пытается добавить элементы по мере изменения размера карты, кто знает, в каком состоянии будет карта.

4 голосов
/ 12 марта 2011

Хммм ... после перечитывания моего поста, может ли это быть из-за одновременного выполнения вне синхронизированного блока 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);
    }
}
2 голосов
/ 12 марта 2011

Проблема в том, что первый 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}
...