避免在对象上使用同步锁定

时间:2017-11-10 17:24:15

标签: java multithreading synchronization thread-safety

我在SocketManager类中有我的下面的方法,每隔60秒由后台线程调用。它将ping一个套接字并检查它是否有效,并将所有内容放在liveSocketsByDatacenter地图中。

  private final Map<Datacenters, List<SocketHolder>> liveSocketsByDatacenter =
      new ConcurrentHashMap<>();

  // runs every 60 seconds to ping all the socket to make sure whether they are alive or not
  private void updateLiveSockets() {
    Map<Datacenters, List<String>> socketsByDatacenter = Utils.SERVERS;

    for (Map.Entry<Datacenters, List<String>> entry : socketsByDatacenter.entrySet()) {
      List<SocketHolder> liveSockets = liveSocketsByDatacenter.get(entry.getKey());
      List<SocketHolder> liveUpdatedSockets = new ArrayList<>();
      for (SocketHolder liveSocket : liveSockets) {
        Socket socket = liveSocket.getSocket();
        String endpoint = liveSocket.getEndpoint();
        Map<byte[], byte[]> holder = populateMap();
        Message message = new Message(holder, Partition.COMMAND);

        // pinging to see whether a socket is live or not
        boolean status = SendToSocket.getInstance().execute(message.getAdd(), holder, socket);
        boolean isLive = (status) ? true : false;

        SocketHolder zmq = new SocketHolder(socket, liveSocket.getContext(), endpoint, isLive);
        liveUpdatedSockets.add(zmq);
      }
      liveSocketsByDatacenter.put(entry.getKey(), Collections.unmodifiableList(liveUpdatedSockets));
    }
  }

此外,我在同一SocketManager课程中有以下方法。多个读取器线程(最多10个线程)将同时调用getNextSocket()方法来获取下一个实时套接字。

  // this method will be called by multiple threads concurrently to get the next live socket
  public Optional<SocketHolder> getNextSocket() {
    for (Datacenters dc : Datacenters.getOrderedDatacenters()) {
      Optional<SocketHolder> liveSocket = getLiveSocket(liveSocketsByDatacenter.get(dc));
      if (liveSocket.isPresent()) {
        return liveSocket;
      }
    }
    return Optional.absent();
  }

  private Optional<SocketHolder> getLiveSocket(final List<SocketHolder> listOfEndPoints) {
    if (!listOfEndPoints.isEmpty()) {
      // The list of live sockets
      List<SocketHolder> liveOnly = new ArrayList<>(listOfEndPoints.size());
      for (SocketHolder obj : listOfEndPoints) {
        if (obj.isLive()) {
          liveOnly.add(obj);
        }
      }
      if (!liveOnly.isEmpty()) {
        // The list is not empty so we shuffle it an return the first element
        return Optional.of(liveOnly.get(random.nextInt(liveOnly.size()))); // just pick one
      }
    }
    return Optional.absent();
  }

问题陈述:

我想确保所有这10个线程+计时器线程在调用getNextSocket()方法后永远不应该使用相同的套接字。

  • 如果计时器线程在socketA方法updateLiveSockets()上工作,那么所有这10个线程应该在其他实时套接字上工作(其中每个都在不同的实时套接字上工作)
  • 并且所有这10个线程应该始终在不同的实时套接字上工作。
  • 此外,如果我们有更多的读取器线程,而不是我们有可用的套接字,那么我仍然需要确保每个读取器线程在不同的实时套接字上工作。只要一个读者线程使用实时套接字完成,那么其他读者线程可以立即使用该套接字或任何更好的方法。我希望尽可能地减少阻塞时间。

解决此问题的最佳方法是什么?我可以在这10个读取器线程的套接字上加synchronize加上定时器线程,这将保证只有线程正在该套接字上工作,但我不想在这里使用同步。必须有一种更好的方法来确保每个线程同时使用不同的单个实时套接字而不是特定套接字上的同步。我有大约60个插槽和大约10个读取器线程加上1个计时器线程。我需要在这里使用ThreadLocal概念吗?

3 个答案:

答案 0 :(得分:1)

针对您的问题的最佳解决方案是使用ConcurrentQueue 您不需要使用ThreadLocal。 ConcurrentQueue是非阻塞的,对于多线程环境非常有效。 例如,这是如何删除不活动的套接字并保留活动的套接字。

    private final Map<Datacenters, ConcurrentLinkedQueue<SocketHolder>> liveSocketsByDatacenter =
          new ConcurrentHashMap<>();


// runs every 60 seconds to ping 70 sockets the socket to make sure whether they are alive or not (it does not matter if you ping more sockets than there are in the list because you are rotating the que)
  private void updateLiveSockets() {
    Map<Datacenters, List<String>> socketsByDatacenter = Utils.SERVERS;

    for (Map.Entry<Datacenters, List<String>> entry : socketsByDatacenter.entrySet()) {
    Queue<SocketHolder> liveSockets = liveSocketsByDatacenter.get(entry.getKey());
      for (int i = 0; i<70; i++) {
            SocketHolder s = liveSockets.poll();
        Socket socket = s.getSocket();
        String endpoint = s.getEndpoint();
        Map<byte[], byte[]> holder = populateMap();
        Message message = new Message(holder, Partition.COMMAND);

        // pinging to see whether a socket is live or not
        boolean status = SendToSocket.getInstance().execute(message.getAdd(), holder, socket);
        boolean isLive = (status) ? true : false;

        SocketHolder zmq = new SocketHolder(socket, s.getContext(), endpoint, isLive);
        liveSockets.add(zmq);
      }
    }
  }

答案 1 :(得分:0)

从你的第一个要求开始:“我想确保所有这10个线程+定时器线程在调用getNextSocket()方法之后永远不应该使用相同的套接字。” 我的回答是,如果没有ThreadLocal,你就无法做到这一点。

至于最大化这些套接字的使用,你肯定需要某种套接字池来管理它们,以便你可以请求和释放它们。每次你请求一个套接字时,你将它“标记”为当前线程所使用的,就在这里ThreadLocal进来你将套接字引用移动到ThreadLocal中,以便下一次调用getNextSocket()进入那里并且检查是否存在并返回,如果没有,则转到套接字轮询并拉出一个新的并在ThreadLocal上设置它。 您将始终需要将套接字释放回池中,不要忘记。也许为套接字本身创建一些抽象,这样你就可以使用AutoCloseable接口来释放套接字池了。

答案 2 :(得分:0)

看起来你需要实现某种类型的Socket池,并在完成后让线程将套接字释放回池中。只有在从/向池中检索和释放套接字时才会发生同步。一旦检索到套接字,就可以在不同步的情况下完成对它的所有访问。恕我直言,我不知道ThreadLocal如何在这里帮助你。

以下是显示方法的代码:

  public synchrnozed Optional<SocketHolder> getNextSocket() {
    for (Datacenters dc : Datacenters.getOrderedDatacenters()) {
      Optional<SocketHolder> liveSocket = getLiveSocket(liveSocketsByDatacenter.get(dc));
      if ( liveSocket.isPresent() && !liveSocket.isUsed() ) {
        liveSocket.setUsed(true);
        return liveSocket;
      }
    }
    return Optional.absent();
  }

  public synchrnozed release(SocketHolder s){
     s.setUsed(false);
  }

每个帖子都是这样的:

  new Thread( new Runnable(){
     public void run(){
       SocketHolder sh = null;
       try{
           sh = socketManager.getNextSocket();
           //do some work on socker
       }finally{
           if (sh != null) socketManager.release(sh);
       }
     }
  }; ) ).start();