从单个线程填充ConcurrentHashMap,然后从没有任何竞争条件的多个线程中读取?

时间:2017-02-26 18:12:59

标签: java multithreading concurrency thread-safety concurrenthashmap

我有一个类,其中我有一个ConcurrentHashMap,每30秒由一个线程更新,然后我通过调用ConcurrentHashMap从同一个getNextSocket()读取多个读者线程方法

下面是我的单例类,它在初始化时调用connectToSockets()方法来填充我的ConcurrentHashMap,然后启动一个后台线程,通过调用updateSockets()方法每30秒更新一次相同的映射。

然后从多个线程我调用getNextSocket()方法获取下一个可用的实时套接字,该套接字使用相同的映射来获取信息。我还有SocketInfo类,它是不可变的,包含所有套接字的状态,无论它们是否存在。

public class SocketHolder {
  private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
  private final Map<DatacenterEnum, List<SocketInfo>> liveSocketsByDc = new ConcurrentHashMap<>();

  // Lazy Loaded Singleton Pattern
  private static class Holder {
    private static final SocketHolder INSTANCE = new SocketHolder();
  }

  public static SocketHolder getInstance() {
    return Holder.INSTANCE;
  }

  private SocketHolder() {
   connectToSockets();
   scheduler.scheduleAtFixedRate(new Runnable() {
      public void run() {
        updateSockets();
      }
    }, 30, 30, TimeUnit.SECONDS);
  }

  private void connectToSockets() {
    Map<DatacenterEnum, ImmutableList<String>> socketsByDc = TestUtils.SERVERS;
    for (Map.Entry<DatacenterEnum, ImmutableList<String>> entry : socketsByDc.entrySet()) {
      List<SocketInfo> addedColoSockets = connect(entry.getKey(), entry.getValue(), ZMQ.PUSH);
      liveSocketsByDc.put(entry.getKey(), addedColoSockets);
    }
  }

  private List<SocketInfo> connect(DatacenterEnum dc, List<String> addresses, int socketType) {
    List<SocketInfo> socketList = new ArrayList<>();
    // ... some code here
    return socketList;
  }

  // called from multiple reader threads to get next live available socket
  public Optional<SocketInfo> getNextSocket() {
    Optional<SocketInfo> liveSocket =  getLiveSocket(liveSocketsByDc.get(DatacenterEnum.CORP));
    return liveSocket;
  }

  private Optional<SocketInfo> getLiveSocket(final List<SocketInfo> listOfEndPoints) {
    if (!CollectionUtils.isEmpty(listOfEndPoints)) {
      Collections.shuffle(listOfEndPoints);
      for (SocketInfo obj : listOfEndPoints) {
        if (obj.isLive()) {
          return Optional.of(obj);
        }
      }
    }
    return Optional.absent();
  }

  // update CHM map every 30 seconds
  private void updateSockets() {
    Map<DatacenterEnum, ImmutableList<String>> socketsByDc = TestUtils.SERVERS;

    for (Entry<DatacenterEnum, ImmutableList<String>> entry : socketsByDc.entrySet()) {
      List<SocketInfo> liveSockets = liveSocketsByDc.get(entry.getKey());
      List<SocketInfo> liveUpdatedSockets = new ArrayList<>();
      for (SocketInfo liveSocket : liveSockets) {
        Socket socket = liveSocket.getSocket();
        String endpoint = liveSocket.getEndpoint();

        boolean sent = ....;

        boolean isLive = sent ? true : false;

        // is this right here? or will it cause any race condition?
        SocketInfo state = new SocketInfo(socket, liveSocket.getContext(), endpoint, isLive);
        liveUpdatedSockets.add(state);
      }
      // update map with new liveUpdatedSockets
      liveSocketsByDc.put(entry.getKey(), liveUpdatedSockets);
    }
  }
}

问题:

我的上述代码线程是否安全且updateSockets()getNextSocket()方法中没有竞争条件?

在我的updateSockets()方法中,我从List<SocketInfo> ConcurrentHashMap中提取liveSocketsByDc,它已在connectToSockets()方法初始化之前填充,或者在{{1然后我迭代相同的列表updateSockets()并创建一个新的liveSockets对象,具体取决于SocketInfo是真还是假。然后我用这个新的isLive对象更新liveSocketsByDc ConcurrentHashMap。这看起来不错吗?因为来自多个读者线程,我将调用SocketInfo方法来调用getNextSocket()方法,该方法使用相同的映射来获取下一个可用的实时套接字。

我正在迭代getLiveSocket列表,然后通过更改liveSockets字段创建新的SocketInfo对象,其他内容将保持不变。这是对的吗?

如果存在线程安全问题,解决此问题的最佳方法是什么?

1 个答案:

答案 0 :(得分:1)

下面:

 List<SocketInfo> liveSockets = liveSocketsByDc.get(entry.getKey());

您的不同主题可能并行写入/读取相同列表对象。

所以:不是线程安全的。拥有“外部”线程安全数据结构没有帮助;当该线程安全的东西包含数据时......这不是线程安全的;但随后“继续”工作了!