Статьи

Как создать потокобезопасный ConcurrentHashSet в Java 8?

До JDK 8 не было никакого способа создать большой потокобезопасный ConcurrentHashSet в Java. В пакете java.util.concurrent даже нет класса ConcurrentHashSet, но начиная с JDK 8 и выше, вы можете использовать вновь добавленные keySet (значение по умолчанию) и newKeySet () для создания ConcurrentHashSet, поддерживаемого ConcurrentHashMap. В отличие от тактических решений, таких как использование одновременной хэш-карты с фиктивным значением или использование установленного вида карты, где вы не можете добавлять новые элементы. Набор, возвращаемый методами keySet (defaultValue) и newKeySet () из JDK 8, является правильным набором, где вы также можете добавлять новые элементы наряду с выполнением других операций над наборами, например, Содержит (), Удалить () и т. Д. Хотя вы должны быть осторожны что эти методы доступны только в классе ConcurrentHashMap, а не в интерфейсе ConcurrentMap, поэтому вам необходимо использовать переменную ConcurrentHashMap для хранения ссылки или использовать приведение типов для приведения объекта ConcurrentHashMap, хранящегося в переменной ConcurrentMAp.

Java Concurrency API имеет параллельные версии популярных классов Collection, например, CopyOnArrayList для ArrayList, ConcurrentHahsMap для HashMap и CopyOnWriteArraySet для HashSet, но в Java не было ничего подобного ConcurrentHashSet. Несмотря на то, что CopyOnWriteArraySet является поточно-ориентированным, он не подходит для приложений, где требуется большой потокобезопасный набор. Он используется только для приложений, где размер набора остается небольшим, а операции только для чтения значительно превосходят количество операций записи.

Поэтому, когда вы спрашиваете Java-программистов о том, как создать ConcurrentHashSet без написания своего собственного класса, многие скажут, что они могут использовать ConcurrentHashMap с поддельными значениями. На самом деле это то, что делает Java, потому что, если вы знаете, HashSet внутренне использует HashMap с теми же значениями.

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

Другой вариант, многие программисты на Java будут упоминать, что вы можете получить представление Set из ConcurrentHashMap, вызвав метод keySet (), который фактически возвращает Set, где вы можете выполнять операции Set и передавать его методу, который ожидает Set но этот подход также имеет свои ограничения, например, Set поддерживается ConcurrentHashMAp, и любое изменение в Map будет отражаться и в Set. Еще одним ограничением было то, что вы не можете добавлять новые элементы в этот набор ключей, так как это вызовет исключение UnsupportedOperationException. Увидеть
Java 8 в действии, чтобы узнать больше об этом.

Оба эти ограничения теперь ушли в прошлое, потому что JDK 8 добавил метод newKeySet (), который возвращает Set, поддерживаемый ConcurrentHashMap из данного типа, где значения являются Boolean.TRUE. В отличие от представления Set, возвращаемого методом keySet (), вы также можете добавлять новые объекты в этот Set. Метод также перегружен и принимает начальную емкость, чтобы предотвратить изменение размера Set.

Вот пример кода для создания ConcurrentHashSet в Java 8:

1
2
3
4
5
ConcurrentHashMap certificationCosts = new ConcurrentHashMap<>();
Set concurrentHashSet = certificationCosts.newKeySet();
concurrentHashSet.add("OCEJWCD"); //OK
concurrentHashSet.contains("OCEJWCD"); //OK
concurrentHashSet.remove("OCEJWCD"); //OK

Кстати, это не единственный способ создать параллельный, большой, потокобезопасный набор в Java. Вы также можете использовать недавно добавленный перегруженный метод keySet (значение по умолчанию) для создания ConcurrentHashSet. Этот метод возвращает представление Set ключей в ConcurrentHashMap, используя данное общее значение по умолчанию для любых дополнений (т. Е. Collection.add и Collection.addAll (Collection)).

Это, конечно, только использование, вы можете использовать одно и то же значение для всех элементов в наборе, что нормально в большинстве ситуаций, потому что вас не волнуют значения в наборе. Помните, что HashSet — это также HashMap с одинаковыми значениями для всех элементов. Дополнительные сведения см. В разделе Как HashSet работает внутри Java .

Вот пример получения ConcurrentHashSet с использованием метода keySet (сопоставленное значение) в Java 8:

1
2
3
ConcurrentHashMap certificationCosts = new ConcurrentHashMap<>();
Set concurrentHashSet = certificationCosts.keySet(246);
concurrentSet.add("Spring enterprise"); // value will be 246 but no error

Вы также можете выполнять другие операции Set, например addAll (), remove (), removeAll (), retainAll (), contains () с этим Set. Он также поточно-ориентированный, поэтому может использоваться в многопоточных приложениях Java. Вы можете узнать больше об операциях на основе множеств в Java SE 8 для Really Impatient .

Java-программа для создания ConcurrentHashSet из ConcurrentHashMAp.

Вот наша полная Java-программа для создания большого, поточно-ориентированного, одновременного хеш-набора в Java 8 с использованием новых методов, добавленных в класс java.util.concurrent.ConcurrentHashMap

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
 
/*
* Java Program to remove key value pair from Map while
* iteration.
*/
public class Demo {
 
public static void main(String[] args) throws Exception {
 
ConcurrentHashMap certificationCosts = new ConcurrentHashMap<>();
certificationCosts.put("OCAJP", 246);
certificationCosts.put("OCPJP", 246);
certificationCosts.put("Spring Core", 200);
certificationCosts.put("Spring Web", 200);
certificationCosts.put("OCMJEA", 300);
 
 
Set concurrentSet = certificationCosts.keySet();
 
System.out.println("before adding element into concurrent set: " + concurrentSet);
// concurrentSet.add("OCEJWCD"); // will throw UnsupportedOperationExcetpion
System.out.println("after adding element into concurrent set: " + concurrentSet);
 
// creating concurrent hash set in Java 8 using newKeySet() method
Set concurrentHashSet = certificationCosts.newKeySet();
 
 
concurrentHashSet.add("OCEJWCD");
concurrentHashSet.contains("OCEJWCD");
concurrentHashSet.remove("OCEJWCD");
System.out.println("after adding element into concurrent HashSet: " + concurrentSet);
 
// you can also use keySet(defaultValue) method to add element into Set
concurrentSet = certificationCosts.keySet(246);
concurrentSet.add("Spring enterprise"); // value will be 246 but no error
 
 
}
 
}
 
Output
before adding an element into the concurrent set:
[Spring Web, OCPJP, OCAJP, Spring Core, OCMJEA]
after adding an element into the concurrent set:
[Spring Web, OCPJP, OCAJP, Spring Core, OCMJEA]
after adding an element into concurrent HashSet:
[Spring Web, OCPJP, OCAJP, Spring Core, OCMJEA]

Вы можете видеть, что если вы попытаетесь добавить новые объекты в Set, возвращенный методом keySet () метода ConcurrentHashMAp, он выдаст UnsupportedOperationExcepiton, как показано ниже:

Исключение в потоке «main» java.lang.UnsupportedOperationException

в java.util.concurrent.ConcurrentHashMap $ KeySetView.add (ConcurrentHashMap.java:4594) в Demo.main (Demo.java:23)

Вот почему я прокомментировал этот код, но Set, возвращаемый методами newKeySet () и keySet (сопоставленное значение), позволяет добавлять новые элементы в Set, там нет ошибок.

Кстати, это не единственный способ создать потокобезопасный набор в Java. Еще до Java 8 существует класс с именем CopyOnWriteArraySet, который позволяет создавать потокобезопасный набор в Java. Он похож на CopyOnWriteArrayList и подходит только для приложений, в которых размер набора мал и вы только читаете единственную операцию, поскольку она копирует все элементы из Set в новый Set каждый раз, когда вы записываете в него. См. Java SE 8 для действительно нетерпеливых, чтобы узнать больше о параллельных коллекциях в Java 8.

Вот некоторые из важных свойств CopyOnWriteArraySet:

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

2. Это потокобезопасный.

3. Мутативные операции (добавление, установка, удаление и т. Д.) Являются дорогостоящими, поскольку они обычно влекут за собой копирование всего базового массива.

4. Итераторы не поддерживают мутативную операцию удаления.

5. Обход через итераторы быстрый и не может столкнуться с помехами из других потоков.

6. Итераторы полагаются на неизменные снимки массива во время создания итераторов.

Это все о том, как создать ConcurrentHashSet в Java 8 . JDK 8 API имеет не только основные функции, такие как лямбда-выражение и поток, но и небольшие изменения, которые упрощают повседневное кодирование. Не так просто создать ConcurrentHashSet в Java с помощью метода newKeySet (). Вам не нужно использовать карту, подобную набору с поддельным значением, или жить с ограничением вида набора, возвращаемого keySet (), которое не позволяет добавлять новые элементы в набор.

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

Статьи по Теме:

Как написать компаратор в Java 8?
как читать файл в Java 8?
Как присоединиться к String в Java 8?
Как сравнить даты в Java 8?
Как отформатировать дату в Java 8?
Как отсортировать список в Java 8?

Большое спасибо за чтение этой статьи. Если вам нравится этот урок, пожалуйста, поделитесь с друзьями и коллегами.