发送所有待处理消息后关闭套接字

时间:2010-11-09 15:22:03

标签: java multithreading sockets networking concurrency

我有这种方法将消息写入套接字:

public void sendMessage(byte[] msgB) {
    try {
        synchronized (writeLock) {
            log.debug("Sending message (" + msgB.length + "): " + HexBytes.toHex(msgB));
            ous.write(HEADER_MSG);
            ous.writeInt(msgB.length);
            ous.write(msgB);
            ous.flush();
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

现在一个名为 Bob 的线程希望在某个不确定的时刻 X 关闭套接字,这意味着可能仍有线程等待writeLock到发送他们的消息,在编写它的过程中甚至可能有一个线程。

我可以通过让 Bob 在关闭套接字之前获取writeLock来解决后者,但我仍然可能丢失尚未开始发送的消息,因为据我所知{{ 1}}不公平, Bob 可以在等待更长时间的其他线程之前获得锁定。

我需要的是,在 X 之前拨打synchronized的所有电话都正常工作,而在 X 之后拨打电话会发出错误。我怎么能这样做?

  • 细节: Bob 是从套接字输入流中读取的线程, X 是在该流上收到“关闭”消息时。

3 个答案:

答案 0 :(得分:2)

考虑使用单线程ExecutorService来执行消息编写。发送主题只需尝试通过调用execute(Runnable)submit(Callable)来“发送”他们的消息。一旦您希望停止发送消息,您就会关闭ExecutorServiceshutdown()),从而导致后续调用提交/执行以产生RejectedExecutionException

这种方法的优点是,与多个线程等待自己编写消息相比,您只有一个I / O绑定线程和更少的锁争用。这也是一个更好的关注点分离。

这是一个简单的例子,OO-更多地解决问题:

public interface Message {
  /**
   * Writes the message to the specified stream.
   */
  void writeTo(OutputStream os);
}

public class Dispatcher {
  private final ExecutorService sender;
  private final OutputStream out;

  public Dispatcher() {
    this.sender = Executors.newSingleThreadExecutor();
    this.out = ...; // Set up output stream.
  }

  /**
   * Enqueue message to be sent.  Return a Future to allow calling thread
   * to perform a blocking get() if they wish to perform a synchronous send.
   */
  public Future<?> sendMessage(final Message msg) {
    return sender.submit(new Callable<Void>() {
      public Void call() throws Exception {
        msg.writeTo(out);
        return null;
      }
    });
  }

  public void shutDown() {
    sender.shutdown(); // Waits for all tasks to finish sending.

    // Close quietly, swallow exception.
    try {
      out.close();
    } catch (IOException ex) {
    }
  }
}

答案 1 :(得分:2)

您可以在此处使用执行程序。由于每个发送消息都是同步的(我假设在一个共同的对象上),你可以使用线程限制。

static final ExecutorService executor = Executors.newSingleThreadExecutor();

public void sendMessage(byte[] msgB) {
    executor.submit(new Runnable() {
        public void run() {
            try {
                log.debug("Sending message (" + msgB.length + "): " + HexBytes.toHex(msgB));
                ous.write(HEADER_MSG);
                ous.writeInt(msgB.length);
                ous.write(msgB);
                ous.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    });
}
public static void atMomentX(){
    executor.shutdown();
}

完成后,另一个线程可以调用atMomentX();

来自javadoc的shutdown方法说:

  

启动有序关机   以前提交的任务是   执行,但没有新的任务   公认。调用没有   如果已经关闭,则会产生额   下来。

答案 2 :(得分:1)

我想我可以用ReentrantLock设置替换同步块以使其公平。