java - 在客户端-服务器套接字程序中链接两个线程 - Java

标签 java multithreading sockets client-server mutex

我创建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 代码可能完全不知道您正在使用线程进行通信。

回复:waitnotify 等。您不需要编写自己的代码来使用它们。使用 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/

相关文章:

java - 在java中查找和删除重复项时出错

java - 线程未执行

c - listen() 忽略积压值

c - 使用 recv 填充垃圾的缓冲区

linux - 有关网络和微 Controller 的书籍?

java - 为什么 getFile 的这个实例可以与 SVNKit 一起使用?

java - 如何在 Python 中对 Java 进行类似的公共(public)静态声明?

java - Java中HashMap和ArrayList的区别?

c - 在我的代码中添加信号量重试登录

c# - 将两个并行任务的结果合并到一个列表中