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

时间:2011-10-08 18:31:25

标签: java multithreading sockets client-server mutex

我创建了类 A 的线程,并使用ObjectOutputStream将序列化对象发送到服务器

服务器为每个套接字连接创建新的主题 B (每当新的 A 客户端连接时)

B 会在共享资源 Mutex 上调用同步方法,导致它(B)等待(),直到某些内部条件为止互斥是真的。

在这种情况下, A 如何知道 B 目前正在等待?

希望这个描述很清楚。

课程安排:

A1--------->B1-------->|       |
A2--------->B2-------->| Mutex |
A3--------->B3-------->|       |

修改 拥有wait(),notify()或notifyAll()是必须的,因为这是针对测试并发性的学术项目。

1 个答案:

答案 0 :(得分:4)

通常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)”as   Communications.callServer中的“myInputs”?

如果所有信息都作为序列化对象传递,则可以将序列化构建到callServer。调用代码只传递一些支持序列化的对象。 callServer的实现会将该对象序列化为byte[]并将其发布到工作队列中。后台线程将从队列中弹出它并将字节发送到服务器。

请注意,这可以避免在后台线程上序列化对象。这样做的好处是所有后台线程活动都与UI代码分开。 UI代码可能完全没有意识到您正在使用线程进行通信。

Re: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线程操纵。所有权明显分离。数据在线程之间作为字节数组传递。

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