Поиск по сайту:

ConcurrentHashMap в Java


Класс Java ConcurrentHashMap является частью классов Concurrency Collection. Это реализация хеш-таблицы, которая поддерживает одновременный поиск и обновление. Он используется в многопоточной среде, чтобы избежать ConcurrentModificationException.

ConcurrentHashMap

Если мы попытаемся изменить коллекцию во время ее повторения, мы получим ConcurrentModificationException. Java 1.5 представила классы Concurrent в пакете java.util.concurrent, чтобы обойти этот сценарий. ConcurrentHashMap — это реализация карты, которая позволяет нам изменять карту во время итерации. Операции ConcurrentHashMap являются потокобезопасными. ConcurrentHashMap не допускает null для ключей и значений.

Пример Java ConcurrentHashMap

Класс ConcurrentHashMap подобен HashMap, за исключением того, что он потокобезопасен и допускает модификацию во время итерации.

package com.journaldev.util;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {

	public static void main(String[] args) {

		//ConcurrentHashMap
		Map<String,String> myMap = new ConcurrentHashMap<String,String>();
		myMap.put("1", "1");
		myMap.put("2", "1");
		myMap.put("3", "1");
		myMap.put("4", "1");
		myMap.put("5", "1");
		myMap.put("6", "1");
		System.out.println("ConcurrentHashMap before iterator: "+myMap);
		Iterator<String> it = myMap.keySet().iterator();

		while(it.hasNext()){
			String key = it.next();
			if(key.equals("3")) myMap.put(key+"new", "new3");
		}
		System.out.println("ConcurrentHashMap after iterator: "+myMap);

		//HashMap
		myMap = new HashMap<String,String>();
		myMap.put("1", "1");
		myMap.put("2", "1");
		myMap.put("3", "1");
		myMap.put("4", "1");
		myMap.put("5", "1");
		myMap.put("6", "1");
		System.out.println("HashMap before iterator: "+myMap);
		Iterator<String> it1 = myMap.keySet().iterator();

		while(it1.hasNext()){
			String key = it1.next();
			if(key.equals("3")) myMap.put(key+"new", "new3");
		}
		System.out.println("HashMap after iterator: "+myMap);
	}

}

Выход:

ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
	at java.util.HashMap$KeyIterator.next(HashMap.java:828)
	at com.test.ConcurrentHashMapExample.main(ConcurrentHashMapExample.java:44)

Из вывода ясно, что ConcurrentHashMap заботится о новой записи на карте во время итерации, тогда как HashMap генерирует ConcurrentModificationException. Давайте внимательно посмотрим на трассировку стека исключений. Следующий оператор вызвал исключение.

String key = it1.next();

Это означает, что новая запись была вставлена в HashMap, но итератор не работает. На самом деле итератор объектов коллекции является отказоустойчивым, т. е. любое изменение структуры или количества записей в объекте коллекции вызовет исключение.

Как итератор узнает об изменении в Коллекции?

Мы взяли набор ключей из HashMap, а затем перебрали его. HashMap содержит переменную для подсчета количества модификаций, и итератор использует ее, когда вы вызываете функцию next() для получения следующей записи. HashMap.java:

/**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient volatile int modCount;

Давайте немного изменим код, чтобы выйти из цикла итератора при вставке новой записи. Все, что нам нужно сделать, это добавить оператор break после вызова put.

if(key.equals("3")){
	myMap.put(key+"new", "new3");
	break;
}

Вывод с приведенным выше кодом:

ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
HashMap after iterator: {3=1, 2=1, 1=1, 3new=new3, 6=1, 5=1, 4=1}

Что произойдет, если значение ключа будет изменено?

Что если мы не будем добавлять новую запись, а обновим существующую пару ключ-значение? Будет ли это вызывать исключение? Давайте изменим код в исходной программе и проверим его.

//myMap.put(key+"new", "new3");
myMap.put(key, "new3");

Никаких исключений не будет, потому что коллекция изменена, но ее структура остается прежней.

Дальнейшее чтение

Вы заметили эти угловые скобки при создании нашего объекта коллекции и Итератора? Он называется дженериками, и он очень эффективен, когда дело доходит до проверки типов во время компиляции для удаления ClassCastException во время выполнения. Узнайте больше об универсальных шаблонах в шаблоне проектирования итераторов в Java.

Вы можете ознакомиться с другими примерами коллекций Java в нашем репозитории GitHub.

Ссылка: документ API