Инкапсулировать ConcurrentHashMap, но при этом оставаться параллельным

#java #multithreading #akka #concurrenthashmap

#java #многопоточность #akka #concurrenthashmap

Вопрос:

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

  • Я хочу инкапсулировать ConcurrentHashMap, но я не знаю, остановит ли это его параллелизм?
  • Я также хочу, чтобы была только одна версия hashmap, поэтому я сделал класс перечисляемым, ужасным и просто получил main для его создания?
  • Я не хочу, чтобы другие потоки изменяли HashMap, поэтому я не передаю коллекцию / итератор. Есть ли способ передать коллекцию без удаления?
  • (новое)Я вызвал два метода в CoolFeature, один блокирует, а другой нет. У обоих могут быть свои места. Есть ли какие-либо проблемы с тезисами?

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

РЕДАКТИРОВАТЬ: я изменил код, и было бы неплохо иметь решение, которое не зависит от Akka, чтобы другие могли найти ответ полезным.

 package server.management;
public class Sessions {
  static final ConcurrentHashMap<Integer, Id<User>> sessions = new concurrentHashMap<>();
  private Sessions() { }
}

public class SessionManager {
  public static void create(int connection, Id<User> userId) {
    // checks
    Sessions.sessions.put(connection, userId);
  }

  public static Id<User> getUser(int connection) {
    Id<User> userId = Sessions.sessions.get(connection);
    return userId;
  }

  public static ArrayList<Id<User>> getActiveUsers() {
    ArrayList<Id<User>> list = new ArrayList<>();

    // Don't pass the sessions.values() directly because the Map can be modified.
    for (Id<User> userId : Sessions.sessions.values()) {
      list.add(userId);
    }
    return list;
  }

  // other methods
}

/* Another thread/akka actor class */
package server;
import server.management.SessionManager;

public class CoolFeature extends AbstractActor {
  ...
  Id<User> client = SessionManager.getUser(1023);
  // do stuff
  CompletableFuture<ArrayList<Id<User>>> futureList =
      CompletableFuture.supplyAsync(SessionManager::getActiveUsers);
  // do stuff
  ArrayList<Id<User>> list = futureList.get();
  // do stuff
}
  

Ответ №1:

С akka actor вы можете поместить карту нормалей внутри субъекта в качестве состояния, и вам не нужно беспокоиться о параллельном доступе, потому что с actor model субъект обрабатывает только одно сообщение за раз. Вы можете сделать что-то вроде приведенного ниже. Код написан на Scala, но вы поняли мою идею.

 object Manager {
  case class Get(id: Int)
  case object GetActive
  case class Create(connId: Int, userId: Int)
}

class Manager extends Actor {
  import Manager._
  val sessions = mutable.Map[Int, Int].empty
  override def receive = {
    case Get(id) => sessions.get(id)
    case GetActive => sessions.values
    case Create(connId, userId) => sessions  = (connId -> userId)
  }
}
  

Дайте мне знать, если у вас возникнут какие-либо вопросы.

Комментарии:

1. Также обычно это плохая идея для кода внутри субъекта (что, я думаю, имеет в виду OP, когда они говорят «использование актеров Akka для создания потоков»), чтобы манипулировать тем же состоянием, которым манипулируют другие актеры.

2. Спасибо, создать субъекта было бы проще. Я просто подумал, что этот участник может получать много запросов и может быть бутылочным горлышком. @LeviRamsey да, ConcurrentHashMap блокирует разделы карты, когда к ней обращаются несколько потоков. Я надеялся, что это распределит нагрузку, чтобы не было большого горлышка бутылки.

3. Блокировка CHM будет надежной (она была протестирована приложениями JVM в течение многих лет, даже если я не знаю официальных доказательств корректности). Однако ничто не мешает действующему лицу вывести из-под контроля другого действующего лица. Если вас беспокоит узкое место, я бы рассмотрел возможность реализации параллельной карты с разделенными участниками (возможно, кластеризованными постоянными участниками, особенно если вы, возможно, захотите запустить несколько экземпляров для HA и т.д.)

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