我创建了类A的线程,每个线程都使用ObjectOutputStream将序列化的对象发送到服务器。
服务器为每个套接字连接创建新的线程B(每当新的A客户端连接时)
B将在共享资源互斥对象上调用一个同步方法,这将导致它(B)等待(),直到互斥对象中的某些内部条件为真。
在这种情况下,A如何知道B当前正在等待?
希望这个描述清楚。
班级安排:
A1--------->B1-------->| |
A2--------->B2-------->| Mutex |
A3--------->B3-------->| |
编辑:
必须具有wait(),notify()或notifyAll(),因为这是用于测试并发性的学术项目。
最佳答案
通常,A会在套接字上读取数据,这会“阻塞”(即不返回,挂断),直到B向其发送一些数据为止。不需要写入它来处理B的等待状态。它只需读取这本质上涉及等待阅读的内容。
更新,因此您希望A的用户界面保持响应状态。到目前为止,最好的方法是利用用户界面库的事件队列系统。所有GUI框架都有一个中央事件循环,该循环将事件分派给处理程序(按钮单击,鼠标移动,计时器等)。通常,后台线程可以通过某种方式将某些内容发布到该事件队列中,以便在主线程上执行该事件。 UI线程。详细信息将取决于您使用的框架。
例如,在Swing中,后台线程可以执行以下操作:
SwingUtilities.invokeAndWait(someRunnableObject);
因此,假设您定义此接口:
public interface ServerReplyHandler {
void handleReply(Object reply);
}
然后为要向服务器提交请求的GUI代码制作一个不错的API:
public class Communications {
public static void callServer(Object inputs, ServerReplyHandler handler);
}
因此,您的客户端代码可以像这样调用服务器:
showWaitMessage();
Communications.callServer(myInputs, new ServerReplyHandler() {
public void handleReply(Object myOutputs) {
hideWaitMessage();
// do something with myOutputs...
}
});
要实现上述API,您将拥有一个请求对象的线程安全队列,该队列存储
inputs
对象和每个请求的处理程序。还有一个后台线程,除了从队列中提取请求外,什么也不做,将序列化的输入发送到服务器,读回答复并反序列化,然后执行以下操作:final ServerReplyHandler currentHandler = ...
final Object currentReply = ...
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
currentHandler.handleReply(currentReply);
}
});
因此,一旦后台线程回读了答复,它将通过回调将其传递回主UI线程。
这正是浏览器通过JS代码进行异步通信的方式。如果您熟悉jQuery,则上述
Communications.callServer
方法与以下模式相同:showWaitMessage();
$.get('http://...', function(reply) {
hideWaitMessage();
// do something with 'reply'
});
在这种情况下,唯一的区别是您是手工编写整个通信堆栈。
更新2
您询问:
您的意思是我可以将“ new ObjectOutputStream()。writeObject(obj)”传递为
Communications.callServer中的“ myInputs”?
如果所有信息都作为序列化对象传递,则可以将序列化构建为
callServer
。调用代码只是传递一些支持序列化的对象。 callServer
的实现会将该对象序列化为byte[]
并将其发布到工作队列。后台线程会将其从队列中弹出,然后将字节发送到服务器。请注意,这避免了序列化后台线程上的对象。这样做的好处是所有后台线程活动都与UI代码分开。 UI代码可能完全不知道您正在使用线程进行通信。
回复:
wait
和notify
,等等。您不需要编写自己的代码即可使用它们。使用BlockingQueue接口的标准实现之一。在这种情况下,您可以将LinkedBlockingQueue
与默认构造函数一起使用,以便它可以接受无限数量的项目。这意味着提交到队列将始终发生而不会阻塞。所以:private static class Request {
public byte[] send;
public ServerReplyHandler handler;
};
private BlockingQueue<Request> requestQueue;
public static callServer(Object inputs, ServerReplyHandler handler) {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
new ObjectOutputStream(byteStream).writeObject(inputs);
Request r = new Request();
r.send = byteStream.toByteArray();
r.handler = handler;
requestQueue.put(r);
}
同时,后台工作线程正在执行此操作:
for (;;) {
Request r = requestQueue.take();
if (r == shutdown) {
break;
}
// connect to server, send r.send bytes to it
// read back the response as a byte array:
byte[] response = ...
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
currentHandler.handleReply(
new ObjectInputStream(
new ByteArrayInputStream(response)
).readObject()
);
}
});
}
shutdown
变量就是:private static Request shutdown = new Request();
也就是说,这是一个虚拟请求,用作特殊信号。这使您可以使用另一个公共静态方法,以允许UI要求退出后台线程(大概在将
shutdown
放入队列之前清除队列)。请注意该模式的要点:永远不要在后台线程上访问UI对象。它们仅从UI线程进行操作。所有权明显分开。数据在线程之间作为字节数组传递。
如果您想支持同时发生的多个请求,则可以启动多个工作人员。