Java ConcurrentHashMap лучше, чем производительность HashMap?

Я просто читал книгу "Чистый код" и наткнулся на это утверждение:

Когда Java была молода, Дуг Ли написал оригинальную книгу [8] Concurrent Программирование на Java. Наряду с книгой он разработал несколько потокобезопасной коллекции, которая позже стала частью JDK в java.util.concurrent пакет. Коллекции в этом пакете являются безопасными для многопоточных ситуаций, и они хорошо работают. Фактически, ConcurrentHashMap реализация выполняется лучше, чем HashMap впочти во всех ситуациях. Это также позволяет одновременное одновременное читает и пишет, и у него есть методы, поддерживающие общий составной которые в противном случае не являются потокобезопасными. Если Java 5 - это среды развертывания, начните с ConcurrentHashMap

Обратите внимание, что в приведенной цитате я использовал "[n]", где n - некоторое число, чтобы указать места, где автор предоставил ссылки, и, как вы видите, он не предоставил никаких ссылок для выделенной полужирной части.

Не то, чтобы я не верил этому утверждению, но я хотел бы знать подтверждающие доказательства этого утверждения. Итак, кто-нибудь знает какие-либо ресурсы, которые показывают статистику производительности для ConcurrentHashMap и HashMap? Или кто-нибудь может объяснить мне, почему ConcurrentHashMap быстрее, чем HashMap?

Я, вероятно, рассмотрю реализацию ConcurrentHashMap на работе, когда я сделаю перерыв, но сейчас я хотел бы услышать ответы от соратников SOers.

Ответ 1

Даг Ли чрезвычайно хорош в этих вещах, поэтому я не удивлюсь, если в свое время его ConcurrentyHashMap выступит лучше, чем Джошуа Блох HashMap. Однако, начиная с Java 7, первым автором HashMap стал и Дуг Ли. Очевидно, что теперь нет причин, по которым HashMap будет медленнее, чем его двоюродный брат.

Из любопытства я все-таки сделал какой-то тест. Я запускаю его под Java 7. Чем больше записей, тем ближе производительность. В конце концов ConcurrentHashMap находится в пределах 3% от HashMap, что весьма примечательно. Узким местом в действительности является доступ к памяти, как говорится, "память - это новый диск (а диск - это новая лента)". Если записи находятся в кеше, оба будут быстрыми; если записи не помещаются в кеш, оба будут медленными. В реальных приложениях карта не обязательно должна быть большой, чтобы конкурировать с другими за размещение в кеше. Если карта используется часто, она кэшируется; если нет, то это не кэшируется, и это является реальным определяющим фактором, а не реализациями (учитывая, что оба они реализованы одним и тем же экспертом)

public static void main(String[] args)
{
    for(int i = 0; i<100; i++)
    {
        System.out.println();

        int entries = i*100*1000;
        long t0=test( entries, new FakeMap() );
        long t1=test( entries, new HashMap() );
        long t2=test( entries, new ConcurrentHashMap() );

        long diff = (t2-t1)*100/(t1-t0);
        System.out.printf("entries=%,d time diff= %d%% %n", entries, diff);
    }
}


static long test(int ENTRIES, Map map)
{
    long SEED = 0;
    Random random = new Random(SEED);

    int RW_RATIO = 10;

    long t0 = System.nanoTime();

    for(int i=0; i<ENTRIES; i++)
        map.put( random.nextInt(), random.nextInt() );

    for(int i=0; i<RW_RATIO; i++)
    {
        random.setSeed(SEED);
        for(int j=0; j<ENTRIES; j++)
        {
            map.get( random.nextInt() );
            random.nextInt();
        }
    }
    long t = System.nanoTime()-t0;
    System.out.printf("%,d ns %s %n", t, map.getClass());
    return t;
}


static class FakeMap implements Map
{
    public Object get(Object key)
    {
        return null;  
    }
    public Object put(Object key, Object value)
    {
        return null;  
    }
    // etc. etc.
}

Ответ 2

Если вы обращаетесь к HashMap только с одним потоком, HashMap работает быстрее (он не выполняет никакой синхронизации), если вы обращаетесь к нему из нескольких потоков ConcurrentHashMap быстрее, чем синхронизация вручную. См. Здесь небольшое сравнение:

http://www.codercorp.com/blog/java/why-concurrenthashmap-is-better-than-hashtable-and-just-as-good-hashmap.html

Ответ 3

Причина, по которой HashMap может быть более медленной, заключается в том, что она должна обнаруживать ConcurrentModification, чтобы знать, когда выкидывать исключение. ConcurrentHashMap не должен проверять modCount, чтобы знать, когда его нужно бросить (но он использует его для size() и isEmpty()). Приобретение блокировки происходит очень быстро, особенно в однопоточных ситуациях, когда вы уже держите блокировку, но проверка modCount - это два чтения и переход, если не равный, который HashMap должен заплатить, чтобы выбросить CoModException.

Я рекомендую прочитать источник ваших классов коллекций, чтобы вы знали, сколько работы они выполняют при вызове метода. В ситуациях, когда у вас есть полностью закрытая карта для словаря get/put, вы часто можете использовать урезанный HashMap без какого-либо modCount или даже отслеживания размера для дополнительного повышения производительности.

Ответ 4

Это своего рода резиновое утверждение, которое трудно доказать так или иначе. Как вы измеряете что-то в "почти всех ситуациях"?

A ConcurrentHashMap, вероятно, будет лучше синхронизированного HashMap. Чем больше утверждений есть, тем значительнее будет разница. С другой стороны, несинхронизированный HashMap, скорее всего, будет быстрее, чем ConcurrentHashMap, из-за накладных расходов на ненужную блокировку в последнем случае.


Я также хотел бы видеть контекст для этого утверждения и какие доказательства автор книги предлагает поддержать. И доказательства неустановленного предположения о том, что "почти все" варианты использования хэш-карт связаны с синхронизацией.