我创建A类线程,每个线程使用ObjectOutputStream将一个序列化对象发送到服务器。
服务器为每个套接字连接创建新线程B(每当新的A客户端连接时)
B 将在共享资源 Mutex 上调用同步方法,这会导致它 (B) wait() 直到出现某些内部条件互斥体是真的。
在这种情况下,A如何知道B当前正在等待?
希望这个描述是清楚的。
类(class)安排:
A1--------->B1-------->| |
A2--------->B2-------->| Mutex |
A3--------->B3-------->| |
编辑: wait()、notify() 或 notifyAll() 是必须的,因为这是一个测试并发的学术项目。
最佳答案
通常情况下,A 会在套接字上进行读取,这会“阻塞”(即不返回、挂起),直到 B 发回一些数据为止。不需要写入来处理 B 的等待状态。它只是读取,这本质上涉及等待读取内容。
更新 因此您希望 A 的用户界面保持响应能力。到目前为止,最好的方法是利用用户界面库的事件队列系统。所有 GUI 框架都有一个中央事件循环,用于将事件分派(dispatch)给处理程序(按钮单击、鼠标移动、计时器等)。通常有一种方法可以让后台线程将某些内容发布到该事件队列,以便它将在主线程上执行。用户界面线程。详细信息取决于您使用的框架。
例如,在 Swing 中,后台线程可以执行以下操作:
SwingUtilities.invokeAndWait(someRunnableObject);
假设您定义了这个接口(interface):
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,您需要一个线程安全的请求对象队列,其中存储每个请求的输入对象和处理程序。还有一个后台线程,它只是从队列中提取请求,将序列化输入发送到服务器,读回回复并将其反序列化,然后执行以下操作:
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
你问:
You mean I can pass "new ObjectOutputStream().writeObject(obj)" as "myInputs" in Communications.callServer?
如果所有信息都作为序列化对象传递,您可以将序列化构建到callServer
中。调用代码只是传递一些支持序列化的对象。 callServer
的实现会将该对象序列化为 byte[]
并将其发布到工作队列。后台线程会将其从队列中弹出并将字节发送到服务器。
请注意,这可以避免在后台线程上序列化对象。这样做的优点是所有后台线程 Activity 都与 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();
即这是一个用作特殊信号的虚拟请求。这允许您使用另一个公共(public)静态方法来允许 UI 要求后台线程退出(可能会在关闭队列之前清除队列)。
请注意该模式的要点:UI 对象永远不会在后台线程上访问。它们只能从 UI 线程进行操作。所有权有明显的分离。数据作为字节数组在线程之间传递。
如果您想支持同时发生的多个请求,您可以启动多个工作线程。
关于java - 在客户端-服务器套接字程序中链接两个线程 - Java,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7698907/