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

回复: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();


也就是说,这是一个虚拟请求,用作特殊信号。这使您可以使用另一个公共静态方法,以允许UI要求退出后台线程(大概在将shutdown放入队列之前清除队列)。

请注意该模式的要点:永远不要在后台线程上访问UI对象。它们仅从UI线程进行操作。所有权明显分开。数据在线程之间作为字节数组传递。

如果您想支持同时发生的多个请求,则可以启动多个工作人员。

09-16 06:16