上面我们列举的几种获取多线程执行结果的方式,都是通过不同技术方法来实现的,而生产者消费者模式本身跟你运用的技术实现没有太多关系,接触过多线程开发的同学应该都有所了解;

生产者消费者模式如下图所示

生产者消费者模式是一种能够解耦与同步生产线程、消费线程、数据集合的多线程设计模式,一个或一组生产者线程负责向数据队列中生产数据,也就是线程执行结果;另外一个或一组消费者线程负责消费处理数据队列中的数据,生产者线程与消费者线程相互之间并没有直接的关联,数据的交互都是通过数据队列,通过这种模式能够很好的在一定程度上解决多线程开发中存在线程同步与安全的问题,同时程序也会看起来更加清晰与方便理解;

当然一个完善的生产者消费者模式我们需要考虑很多其他方面,  但最关键的还是以下两个要素:

1、线程安全,生产者与消费者分别执行读写操作,特别是在多个生产线程与消费线程时,一定会存在数据读写的并发操作,所以数据队列一定要保证线程安全;

2、生产与消费的协调,数据队列满时生产线程是否停止写入,数据队列空时消费线程是否停止消费,这里一方面需要结合你的应用场景,同时也是需要考虑不必要的性能浪费;

下面看下基本的代码实现

首先定义一个全局的数据队列,这里我用的JDK提供的阻塞队列ArrayBlockingQueue,这里同样也直接可以上面讲到的completionService,当然也可以用其他线程安全的数据集合或者自己定义实现,但要注意无论使用哪种都要注意上面的两个关键要素,平常使用中JDK封装的阻塞队列已经基本满足要求;

public class Container {
    public static ArrayBlockingQueue<Result> arrayBlockingQueue = new ArrayBlockingQueue<>(100);//这里最好根据系统负载量评估一个阈值,避免OOM问题
}

生产者线程实现,队列数据插入时是采用put还是offer结合应用场景调整

public class ProducerThread extends Thread {
    
    public void run() {    
        try {
            Thread.sleep(1000*3);//模拟程序执行
            Result result = new Result();
            result.setValue(Thread.currentThread().getName()+"线程执行完毕,输出结果");
            Container.arrayBlockingQueue.put(result);//超过阻塞队列最大阈值时阻塞,一直阻塞
//            if(!Container.arrayBlockingQueue.offer(result, 5, TimeUnit.SECONDS)) {//规定时间内数据入队失败
//                System.err.println("数据入队失败");
//            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

消费者线程实现,消费者线程是常驻线程,队列中没有数据时就线程阻塞

public class ConsumerThread extends Thread {
    
    public void run() {
         while (!this.isInterrupted()) {
             try {
                Result result = Container.arrayBlockingQueue.take();//有数据就消费,没有就阻塞等待
                System.out.println(result.getValue());
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
         }
    }
}

 主线程中同时启动生产线程与消费线程

public class MainThread  {
    public static void main(String[] args) {
        
        ExecutorService exec = new ThreadPoolExecutor(10, 20, 1000, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        
        for(int i=0;i<100;i++) {//使用线程池模拟生成者生产数据
            exec.execute(new ProducerThread());
        }
        
        for(int i=0;i<2;i++) {//启动两个消费者线程
            new ConsumerThread().start();
        }
    }
}

消费者线程中会轮询获取生产者线程执行并放到阻塞队列中的结果

pool-1-thread-13线程执行完毕,输出结果
pool-1-thread-2线程执行完毕,输出结果
pool-1-thread-1线程执行完毕,输出结果
pool-1-thread-10线程执行完毕,输出结果
pool-1-thread-9线程执行完毕,输出结果
pool-1-thread-15线程执行完毕,输出结果
pool-1-thread-4线程执行完毕,输出结果
pool-1-thread-5线程执行完毕,输出结果
pool-1-thread-8线程执行完毕,输出结果
pool-1-thread-12线程执行完毕,输出结果
pool-1-thread-16线程执行完毕,输出结果
.....................................................
.....................................................