我有这种方法将消息写入套接字:
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 之后拨打电话会发出错误。我怎么能这样做?
答案 0 :(得分:2)
考虑使用单线程ExecutorService
来执行消息编写。发送主题只需尝试通过调用execute(Runnable)
或submit(Callable)
来“发送”他们的消息。一旦您希望停止发送消息,您就会关闭ExecutorService
(shutdown()
),从而导致后续调用提交/执行以产生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设置替换同步块以使其公平。