7

Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?

1

Я столкнулся с проблемой, связанной с использованием HashSet и его зависимостью от HashMap.

Как мы знаем, реализация HashSet<E> фактически управляется через HashMap<E, Object>, где <E> используется в качестве ключа. Однако HashMap не является потокобезопасным, именно поэтому в Java существует ConcurrentHashMap.

У меня возникает вопрос: почему в Java не существует ConcurrentHashSet, который основывался бы на ConcurrentHashMap?

Возможно, я упускаю что-то важное. У меня есть необходимость использовать Set в многопоточной среде.

Кроме того, если я захочу создать свой собственный ConcurrentHashSet, смогу ли я сделать это, просто заменив HashMap на ConcurrentHashMap, оставив остальную часть неизменной?

5 ответ(ов)

1

До Java 8 вы могли создать Set с помощью Collections.newSetFromMap, как показано в следующем примере:

Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());

С Java 8 и выше существует более удобный способ создания Set, который поддерживает параллельные операции. Вы можете использовать статический метод newKeySet() класса ConcurrentHashMap:

ConcurrentHashMap.KeySetView<String, Boolean> mySet = ConcurrentHashMap.newKeySet();

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

0

Вы можете создать потокобезопасное множество (Set) в Java, используя ConcurrentHashMap. Ниже приведен пример реализации класса ConcurrentHashSet, который расширяет AbstractSet и реализует интерфейс Set.

import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E> {
   private final ConcurrentMap<E, Object> theMap;
   private static final Object dummy = new Object();

   public ConcurrentHashSet() {
      theMap = new ConcurrentHashMap<E, Object>();
   }

   @Override
   public int size() {
      return theMap.size();
   }

   @Override
   public Iterator<E> iterator() {
      return theMap.keySet().iterator();
   }

   @Override
   public boolean isEmpty() {
      return theMap.isEmpty();
   }

   @Override
   public boolean add(final E o) {
      return theMap.put(o, ConcurrentHashSet.dummy) == null;
   }

   @Override
   public boolean contains(final Object o) {
      return theMap.containsKey(o);
   }

   @Override
   public void clear() {
      theMap.clear();
   }

   @Override
   public boolean remove(final Object o) {
      return theMap.remove(o) == ConcurrentHashSet.dummy;
   }

   public boolean addIfAbsent(final E o) {
      Object obj = theMap.putIfAbsent(o, ConcurrentHashSet.dummy);
      return obj == null;
   }
}

Объяснение кода:

  1. Основные компоненты:

    • ConcurrentMap<E, Object> theMap;: используется для хранения уникальных значений множества. Вместо значения в качестве значения карты используется "пустой" объект dummy.
    • ConcurrentHashMap: потокобезопасная реализация карты, которая поддерживает выполнение операций, не требуя внешней синхронизации.
  2. Методы:

    • size(): возвращает количество элементов в множестве.
    • iterator(): возвращает итератор по множеству.
    • isEmpty(): проверяет, пусто ли множество.
    • add(E o): добавляет элемент, возвращая true, если элемент был добавлен, и false, если он уже существовал.
    • contains(Object o): проверяет наличие элемента в множестве.
    • clear(): очищает множество.
    • remove(Object o): удаляет элемент, возвращая true, если элемент был удален.
    • addIfAbsent(E o): добавляет элемент в множество, если он отсутствует, и возвращает true, если элемент был добавлен.

Этот класс позволяет эффективно управлять множеством из нескольких потоков, минимизируя возможности гонки данных.

0

Использование CopyOnWriteArraySet из пакета java.util.concurrent может показаться привлекательным решением для работы с многопоточными приложениями, однако следует учитывать несколько факторов, которые могут сделать его неподходящим в определенных сценариях.

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

  2. Память: Создание новых копий массива для каждой операции записи увеличивает потребление памяти. В ситуациях с частыми изменениями это может стать серьезной проблемой.

  3. Необходимость в итерациях: Если вы часто выполняете итерации по элементам множества, CopyOnWriteArraySet может быть удобным вариантом, поскольку итерации могут выполняться быстро и безопасно без блокировок. Однако, когда операции записи преобладают, это может привести к снижению производительности.

  4. Контекст использования: Важно определить, для чего именно вам нужно это множество. Если вам нужна структура данных, которая безопасно обрабатывает конкурентный доступ, и вы не проводите слишком много обновлений, то CopyOnWriteArraySet может быть хорошим выбором. Однако, если у вас сценарий с более частыми изменениями, стоит рассмотреть альтернативы.

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

0

В io.vertx существует класс ConcurrentHashSet, который можно использовать в качестве альтернативы для java.util.concurrent.ConcurrentHashMap. Вы можете найти документацию и примеры использования этого класса по следующей ссылке: ConcurrentHashSet Documentation.

ConcurrentHashSet реализует схожие принципы работы с потоками и обеспечивает безопасность при параллельном доступе к коллекции. Это может быть полезно, если вам нужна реализация множества, основанная на параллельной работе, без необходимости управлять синхронизацией вручную. Просто помните, что этот класс использует внутреннюю структуру ConcurrentHashMap для хранения значений, так что его производительность и поведение будут соответствовать концепциям потокобезопасных коллекций в Java.

0

На мой взгляд, нам действительно нужен общий интерфейс ConcurrentSet, который должен быть наравне с интерфейсом ConcurrentMap.

Было бы полезно, чтобы весь существующий код соответствовал этому новому интерфейсу, чтобы немного прояснить, когда у нас есть Set, который является конкурентным (потокобезопасным), а когда нет.

Например, это могло бы выглядеть так:

ConcurrentSet<String> set = ConcurrentHashMap.newKeySet();
ConcurrentSet<String> set = Collections.synchronizedSet(new HashSet<>());
ConcurrentSet<String> set = new ConcurrentSkipListSet<>();

Наличие общего интерфейса упростило бы работу с потокобезопасными множествами и сделало код более понятным.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь