【问题标题】:Producer-Consumer multithreading FIFO in JavaJava中的生产者-消费者多线程FIFO
【发布时间】:2021-03-25 23:08:12
【问题描述】:

社区,我正在尝试用 10 个线程解决这个生产者/消费者问题,但我在实现它时遇到了困难。

问题如下:

Problem Scheme

程序本身在插入包含 (id, timeout) 的消息时应该有一个循环,按 id (1,2,3,4...) 升序排列,并且应该简单地打印出来的消息的 id ,按照它进入的顺序,就像一个队列。

例如在上面的照片中,3 条消息 Message(1,200)、Message(2, 1000) 和 Message(3,20) 是生产者将产生的前 3 条消息。 虽然应该首先打印分配有 Message(3,20) 的线程(因为它的超时时间(20)最低),但我希望它等待有 200ms 超时的第一条消息打印,然后再次等待message2 用 1000 毫秒打印,然后打印自己。所以一切都是递增的(也许使用 id 作为订购号?)。

到目前为止,我已经实现了这个:

public class Main {

    private static BlockingQueue<Message> queue = new ArrayBlockingQueue<>(5);

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            try {
                producer();
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                consumer();
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }

    public static void producer() throws InterruptedException {

        while (true) {
            queue.put(new Message());
        }
    }

    public static void consumer() throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 1000; i++) {
            executorService.submit(queue.take());
        }
        executorService.shutdown();
    }
}

我在这里有我的消息类:

public class Message implements Runnable {
        public static int totalIds = 0;
        public int id;
        public int timeout;
        public Random random = new Random();

    public Message() {
        this.id = totalIds;
        totalIds++;
        this.timeout = random.nextInt(5000);
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", timeout=" + timeout +
                '}';
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "[RECEIVED] Message = " + toString());

        try {
            Thread.sleep(timeout);
        } catch (InterruptedException exception) {
            exception.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "[DONE] Message = " + toString() + "\n");
    }
}

到目前为止,除了线程应该等待具有优先级 id 的部分之外,它一切正常...这里是输出的第一部分:

All tasks submitted
pool-1-thread-9[RECEIVED] Message = Message{id=13, timeout=1361}
pool-1-thread-10[RECEIVED] Message = Message{id=14, timeout=92}
pool-1-thread-3[RECEIVED] Message = Message{id=7, timeout=3155}
pool-1-thread-5[RECEIVED] Message = Message{id=9, timeout=562}
pool-1-thread-2[RECEIVED] Message = Message{id=6, timeout=4249}
pool-1-thread-1[RECEIVED] Message = Message{id=0, timeout=1909}
pool-1-thread-7[RECEIVED] Message = Message{id=11, timeout=2468}
pool-1-thread-4[RECEIVED] Message = Message{id=8, timeout=593}
pool-1-thread-8[RECEIVED] Message = Message{id=12, timeout=3701}
pool-1-thread-6[RECEIVED] Message = Message{id=10, timeout=806}
pool-1-thread-10[DONE] Message = Message{id=14, timeout=92}

pool-1-thread-10[RECEIVED] Message = Message{id=15, timeout=846}
pool-1-thread-5[DONE] Message = Message{id=9, timeout=562}

pool-1-thread-5[RECEIVED] Message = Message{id=16, timeout=81}
pool-1-thread-4[DONE] Message = Message{id=8, timeout=593}

pool-1-thread-4[RECEIVED] Message = Message{id=17, timeout=4481}
pool-1-thread-5[DONE] Message = Message{id=16, timeout=81}

pool-1-thread-5[RECEIVED] Message = Message{id=18, timeout=2434}
pool-1-thread-6[DONE] Message = Message{id=10, timeout=806}

pool-1-thread-6[RECEIVED] Message = Message{id=19, timeout=10}
pool-1-thread-6[DONE] Message = Message{id=19, timeout=10}

pool-1-thread-6[RECEIVED] Message = Message{id=20, timeout=3776}
pool-1-thread-10[DONE] Message = Message{id=15, timeout=846}

pool-1-thread-10[RECEIVED] Message = Message{id=21, timeout=2988}
pool-1-thread-9[DONE] Message = Message{id=13, timeout=1361}

pool-1-thread-9[RECEIVED] Message = Message{id=22, timeout=462}
pool-1-thread-9[DONE] Message = Message{id=22, timeout=462}

pool-1-thread-9[RECEIVED] Message = Message{id=23, timeout=3074}
pool-1-thread-1[DONE] Message = Message{id=0, timeout=1909}

pool-1-thread-1[RECEIVED] Message = Message{id=24, timeout=725}
pool-1-thread-7[DONE] Message = Message{id=11, timeout=2468}

我的一个朋友告诉我应该使用信号量来完成(从未使用过它们),但我真的不知道如何实现信号量以便它们做我想做的事情。

感谢任何解决此问题的线索!

【问题讨论】:

  • 在线程中使用 ExecutorService 有什么意义?
  • 另外,你的生产者不断地产生一条随机睡眠时间的消息。所以如果你尝试只产生 10 条消息,消费 10 条消息,你会看到线程是按睡眠时间顺序运行的,但是当queue.take() 一个线程不知道睡眠时间所以这个日志是正常的。
  • 我在该消费者线程中使用 ExecutorService,因为我希望有 10 个线程处理消息。另外,我不是试图产生 10 条消息并消耗 10 条消息,我试图产生 N 条消息,它与数字无关,或者它甚至可以是无限数量的消息......基本上我能做到的全部要做的就是告诉我的线程互相等待,以便它们按照输入的顺序打印消息。 (即升序:1,2,3,4,5,6...)
  • 是的,这就是异步编程的重点。

标签: java multithreading parallel-processing semaphore producer-consumer


【解决方案1】:

据我了解,您需要两件事:

  1. 一起启动所有生产者的工作线程,让它们并行运行,但是...
  2. 等待线程按 FIFO 顺序完成(根据它们的创建 ID)。

所以,你可以一个接一个地启动线程,让它们并行运行,还可以维护一个FIFO队列,它们按id升序排列,每个线程都按它们的顺序排列join添加到该队列中。

这是一个演示代码,说明如何做到这一点:

import java.util.LinkedList;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public class Main {
    
    private static class Message implements Runnable {
        private final TimeUnit sleepUnit;
        private final long sleepAmount;
        private final int id;
        
        public Message(final int id,
                       final TimeUnit sleepUnit,
                       final long sleepAmount) {
            this.sleepUnit = Objects.requireNonNull(sleepUnit);
            this.sleepAmount = sleepAmount;
            this.id = id;
        }
        
        @Override
        public void run() {
            try {
                System.out.println(toString() + " started and waiting...");
                sleepUnit.sleep(sleepAmount);
            }
            catch (final InterruptedException ix) {
                System.out.println(toString() + " interrupted: " + ix);
            }
        }
        
        @Override
        public String toString() {
            return "Message{" + id + ", " + sleepUnit + "(" + sleepAmount + ")}";
        }
    }
    
    private static class Producer {
        
        private final int parallelism;
        private final Consumer<? super Producer> consumer;
        
        public Producer(final int parallelism,
                        final Consumer<? super Producer> consumer) {
            this.parallelism = parallelism;
            this.consumer = Objects.requireNonNull(consumer);
        }
        
        public void produceWithExecutor() {
            System.out.println("Producing with Executor...");
            final Random rand = new Random();
            final ExecutorService service = Executors.newFixedThreadPool(parallelism);
            final LinkedList<Future> q = new LinkedList<>();
            for (int i = 0; i < parallelism; ++i) {
                final Message msg = new Message(i, TimeUnit.MILLISECONDS, 500 + rand.nextInt(3000));
                q.addLast(service.submit(msg, msg));
            }
            service.shutdown();
            while (!q.isEmpty())
                try {
                    System.out.println(q.removeFirst().get().toString() + " joined."); //Will wait for completion of each submitted task (in FIFO sequence).
                }
                catch (final InterruptedException ix) {
                    System.out.println("Interrupted: " + ix);
                }
                catch (final ExecutionException xx) {
                    System.out.println("Execution failed: " + xx);
                }
            consumer.accept(this);
        }
        
        public void produceWithPlainThreads() throws InterruptedException {
            System.out.println("Producing with Threads...");
            final Random rand = new Random();
            final LinkedList<Thread> q = new LinkedList<>();
            for (int i = 0; i < parallelism; ++i) {
                final Message msg = new Message(i, TimeUnit.MILLISECONDS, 500 + rand.nextInt(3000));
                final Thread t = new Thread(msg, msg.toString());
                t.start();
                q.add(t);
            }
            while (!q.isEmpty()) {
                final Thread t = q.removeFirst();
                t.join(); //Will wait for completion of each submitted task (in FIFO sequence).
                System.out.println(t.getName() + " joined.");
            }
            consumer.accept(this);
        }
    }
    
    public static void main(final String[] args) throws InterruptedException {
        final Consumer<Producer> consumer = producer -> System.out.println("Consuming.");
        final int parallelism = 10;
        new Producer(parallelism, consumer).produceWithExecutor();
        new Producer(parallelism, consumer).produceWithPlainThreads();
    }
}

如您所见,这里有两种生产实现:一种使用 ExecutorService 运行所有提交的线程,另一种使用(几乎)同时启动的普通线程。

这会产生如下输出:

使用 Executor 进行生产...
消息{1, MILLISECONDS(692)} 已启动并等待...
消息 {2, MILLISECONDS(1126)} 已启动并等待...
消息 {0, MILLISECONDS(3403)} 已启动并等待...
消息 {3, MILLISECONDS(1017)} 已启动并等待...
消息 {4, MILLISECONDS(2861)} 已启动并等待...
消息 {5, MILLISECONDS(2735)} 已启动并等待...
消息 {6, MILLISECONDS(2068)} 已启动并等待...
消息 {7, MILLISECONDS(947)} 已启动并等待...
消息 {8, MILLISECONDS(1091)} 已启动并等待...
消息 {9, MILLISECONDS(1599)} 已启动并等待...
消息 {0, MILLISECONDS(3403)} 已加入。
消息{1, MILLISECONDS(692)} 已加入。
消息{2, MILLISECONDS(1126)} 已加入。
消息{3, MILLISECONDS(1017)} 已加入。
消息 {4, MILLISECONDS(2861)} 已加入。
消息 {5, MILLISECONDS(2735)} 已加入。
消息 {6, MILLISECONDS(2068)} 已加入。
消息 {7, MILLISECONDS(947)} 已加入。
消息 {8, MILLISECONDS(1091)} 已加入。
消息 {9, MILLISECONDS(1599)} 已加入。
消费。
用线程生产...
消息 {0, MILLISECONDS(3182)} 已启动并等待...
消息 {1, MILLISECONDS(2271)} 已启动并等待...
消息 {2, MILLISECONDS(2861)} 已启动并等待...
消息 {3, MILLISECONDS(2942)} 已启动并等待...
消息 {4, MILLISECONDS(2714)} 已启动并等待...
消息 {5, MILLISECONDS(1228)} 已启动并等待...
消息 {6, MILLISECONDS(2000)} 已启动并等待...
消息 {7, MILLISECONDS(2372)} 已启动并等待...
消息 {8, MILLISECONDS(764)} 已启动并等待...
消息 {9, MILLISECONDS(587)} 已启动并等待...
消息 {0, MILLISECONDS(3182)} 已加入。
消息 {1, MILLISECONDS(2271)} 已加入。
消息{2, MILLISECONDS(2861)} 已加入。
消息 {3, MILLISECONDS(2942)} 已加入。
消息 {4, MILLISECONDS(2714)} 已加入。
消息 {5, MILLISECONDS(1228)} 已加入。
消息 {6, MILLISECONDS(2000)} 已加入。
消息 {7, MILLISECONDS(2372)} 已加入。
消息 {8, MILLISECONDS(764)} 已加入。
消息 {9, MILLISECONDS(587)} 已加入。
消费。

您可以在输出中看到,在这两种情况下,线程(几乎)都是通过循环一起启动的,但以 FIFO 有序的方式连接。在第一种情况下,您可以看到线程可能以不同的顺序启动,这是启动线程本身的副作用。在使用普通线程的第二种情况下,碰巧所有线程都按照它们创建和启动的顺序调用了它们的run 方法,因为这发生在很短的时间内。但是根据此代码,每个线程的加入将始终按 id 升序排列。如果您多次运行此代码,您可以在两种情况下实现线程 eg 2 在线程 eg 1 之前在其运行方法中打印,但是我们在 Producer 的方法中等待线程完成的顺序将始终以id 升序。

所有线程都应该以睡眠升序而不是 id 升序退出/完成它们的run 方法。但是输出将始终按 id 升序排列,因为我们正在遍历队列并以有序的方式等待它们到达join

因此,如果您想按 id 升序获取每个 Thread 的结果,则相应的代码必须在您的 Producer 的生产方法中(在您 join 每个线程之后)和 不要在每个Messagerun方法的末尾(为了避免额外的同步和线程间通信)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-17
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 2012-04-30
    • 1970-01-01
    • 2017-02-01
    • 1970-01-01
    相关资源
    最近更新 更多