繁体   English   中英

Java中一个主线程创建的两个线程之间的通信

[英]Communication between two threads created by a main thread in Java

所以我必须创建三个 Teller 线程和 100 个 Client 线程。

每个线程必须遵循一系列动作,打印每个动作的记录。

Teller 线程必须:通知客户端它可用,接受来自客户端的 id 和事务,响应客户端,等等

客户端有一组类似的操作:排队等候、收到信号时选择免费柜员等

我不明白的是,我如何让这些线程进行通信?

所以我从 main 方法中创建了三个 Teller 线程,100 个客户端线程,我究竟如何将客户端线程连接到一个柜员线程? 很明显,当三个出纳员线程打开时,它们只能接受3个客户端,因此其他97个客户端线程将在等待。 如何停止客户端的 run() 方法,以便线程在等待打开的 Teller 时仍处于活动状态?

您可以使用倒计时锁存器

基本上它是这样工作的:倒计时锁存信号有助于让客户端等待,如果此时所有出纳员都忙,并在信号计数达到 0 时通知客户端线程(这意味着轮到我了)。

我的建议是,为每个客户端创建一个计数等于 1 的倒计时锁存信号并将它们存储在一个数据结构中(我推荐一个队列),所以每次出纳员完成一个客户端时,只需从客户端弹出一个倒计时锁存信号队列,然后将弹出信号的计数减少到 0,因此具有此信号的客户端会得到通知。

我推荐使用队列,因为每次弹出后它都会删除元素,因此没有任何数据竞争(线程同时读取同一个元素,这使得客户端由两个线程提供服务)。

我希望这有帮助。

类之间的数据交换是通过类方法完成的。 同样,在Thread之间交换消息是通过类方法完成的。

如果一切都是本地的,即您不必通过在线网络发送任何内容,那么您可以尝试以下代码:

import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;
import java.util.Random;

public class Main {

    public static class MessageSource {
    }

    public static class Message<S extends MessageSource> {
        private final S origin;

        public Message(final S origin) {
            this.origin = Objects.requireNonNull(origin);
        }

        public S getOrigin() {
            return origin;
        }
    }

    public static class ClientRequest extends Message<Client> {
        public ClientRequest(final Client origin) {
            super(origin);
        }
    }

    public static class TellerResponse extends Message<Teller> {
        public TellerResponse(final Teller origin) {
            super(origin);
        }
    }

    public static class Teller extends MessageSource implements Runnable {
        private final Queue<ClientRequest> sharedQueue;

        public Teller(final Queue<ClientRequest> sharedQueue) {
            this.sharedQueue = Objects.requireNonNull(sharedQueue);
        }

        @Override
        public void run() {
            try {
                final Random rand = new Random();
                while (true) {
                    final ClientRequest r;
                    synchronized (sharedQueue) {
                        while (sharedQueue.isEmpty()) {
                            System.out.println("Teller " + hashCode() + " says queue is empty.");
                            sharedQueue.wait();
                        }
                        r = sharedQueue.poll();
                    }
                    System.out.println("Teller " + hashCode() + " currently seving request from Client " + r.getOrigin().hashCode() + "...");
                    Thread.sleep(250 + rand.nextInt(250)); //Delay a bit, to simulate serving the request and creating the response...
                    r.getOrigin().response(new TellerResponse(this)); //One could say that this simulates an RPC call :P
                }
            }
            catch (final InterruptedException ix) {
                System.err.println("Teller " + hashCode() + " stopped serving clients abruptly: " + ix);
            }
        }
    }

    public static class Client extends MessageSource implements Runnable {
        private final Queue<ClientRequest> sharedQueue;
        private TellerResponse privateQueue; //Since responses will be received here, I call this a queue (although it's not, because we know we can serve only one response at a time).

        public Client(final Queue<ClientRequest> sharedQueue) {
            this.sharedQueue = Objects.requireNonNull(sharedQueue);
        }

        public synchronized void response(final TellerResponse r) {
            privateQueue = r;
            notifyAll(); //Could be notify(). No difference would it make in this specific case.
        }

        @Override
        public void run() {
            //I'm just implementing random count of random-data requests...
            final Random rand = new Random();
            final int numberOfRequests = 5 + rand.nextInt(6);
            try {
                for (int i = 0; i < numberOfRequests; ++i) {
                    final ClientRequest req = new ClientRequest(this);
                    synchronized (sharedQueue) {
                        sharedQueue.add(req);
                        sharedQueue.notifyAll(); //Could be notify(). No difference would it make in this specific case.
                    }
                    synchronized (this) {
                        while (privateQueue == null)
                            wait();
                        System.out.println("Client " + hashCode() + " can consume the " + privateQueue.getOrigin().hashCode() + " Teller's response...");
                        privateQueue = null;
                    }
                }
            }
            catch (final InterruptedException ix) {
                System.err.println("Client " + hashCode() + " stopped receiving responses abruptly: " + ix);
            }
        }
    }

    public static void main(final String[] args) {
        final Queue<ClientRequest> requests = new LinkedList<>();

        for (int i = 0; i < 100; ++i)
            new Thread(new Client(requests)).start();

        for (int i = 0; i < 3; ++i)
            new Thread(new Teller(requests)).start();
    }
}

单个ClientRequest由具有单个TellerResponseTeller TellerResponse 现在您必须根据您的需要扩展类ClientRequestTellerResponse (即实现应该交换的数据)。

这个实现是一个生产者-消费者模式,其中柜员和客户都是生产者和消费者。 此实现使用共享队列将消息从客户端交换到柜员。

出纳员没有停止条件(这使他们在客户端请求结束时无限等待),这将使程序在客户端请求结束时永远等待。 但这是意料之中的,因为我们没有终止条件。

客户端将运行从 5 到 10 个请求的创建。 柜员会将每个响应延迟 250 到 500 毫秒。 有 3 个柜员和 100 个客户端,这使我们的运行时间大约为 42 到 167 秒。

我想一种更现实的通信方法是使用PipedInputStreamPipedOutputStream来模拟阻塞流(例如网络流量)上的流量。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM