基于生产者-消费者模式的并行计算(Java 实现)

目录

一、前言

二、任务目标

三、技术方案

使用Java实现生产者-消费者模式

四、代码实现

1.生产者类代码

2.消费者类代码

3.缓冲区类代码

4.主类

五、结果与分析

总结


前言

        在分布式与并行计算课程中,生产者-消费者模式是一种经典的线程间通信机制。本实验旨在

通过该模式体验多线程编程,并探索不同线程数对程序性能的影响。


二、任务目标

  • 使用一个生产者线程生成 1000000大于20亿的随机数。
  • 使用消费者线程判断这些随机数是否为素数。
  • 记录不同线程数量下程序的运行时间,对比性能。

实分为三个部分:

  • 实验一:1 个生产者,1 个消费者。
  • 实验二:1 个生产者,2 个消费者。
  • 实验三:2 个生产者,4 个消费者。

三、技术方案

使用Java实现生产者-消费者模式

  • 生产者类:生成大于 20亿 的随机数。[生产者类负责生成大整数,并且将它们放入缓冲区]
  • 消费者类:判断是否为素数。[消费者类从缓冲区中提取数字,并判断其是否为素数,最终统计素数的数量]
  • 缓冲区类:使用 BlockingQueue 类实现。[使用阻塞队列来充当生产者与消费者的共享缓冲区,保证线程安全。采用 LinkedBlockingQueue 作为队列的具体实现] 
  • 主类·:负责启动生产者和消费者线程,记录程序运行时间,并通过特殊“终止信号”通知消费者停止运行。
  • 特殊终止信号:在生产者结束生产时,向缓冲区中插入一个特殊的标志(如-1),消费者在接收到该标志后推出循环,从而避免死循环问题。

四、代码实现

1.生产者类代码

import java.util.Random;

public class Producer implements Runnable {
    private final Buffer buffer;
    private final int totalNumbers;

    public Producer(Buffer buffer, int totalNumbers) {
        this.buffer = buffer;
        this.totalNumbers = totalNumbers;
    }

    @Override
    public void run() {
        Random random = new Random();
        try {
            for (int i = 1; i <= totalNumbers; i++) {
                long number = 2000000000L + random.nextInt(Integer.MAX_VALUE);
                buffer.produce(number);
                if(i % 10000 == 0){
                    System.out.println(Thread.currentThread().getName() + " 生成数字: " + number +
                            " | 进度: " + ((i + 1) * 100 / totalNumbers) + "%");
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

2.消费者类代码

public class Consumer implements Runnable {
    private final Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    private boolean isPrime(long number) {
        if (number < 2) return false;
        for (long i = 2; i * i <= number; i++) {
            if (number % i == 0) return false;
        }
        return true;
    }

    @Override
    public void run() {
        try {
            int primeCount = 0;
            while (true) {
                Long number = buffer.consume();
                if (number == -1L) {  // 接收到终止信号,退出循环
                    break;
                }
                if (isPrime(number)) {
                    primeCount++;
                }
            }
            System.out.println(Thread.currentThread().getName() + " 处理完毕.发现素数: " + primeCount + " 个");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

3.缓冲区类代码

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class Buffer {
    private final BlockingQueue<Long> queue;
    private final int poisonPillCount; // 记录“终止信号”的数量

    public Buffer(int capacity, int consumerCount) {
        this.queue = new LinkedBlockingQueue<>(capacity);
        this.poisonPillCount = consumerCount;  // 终止信号数量与消费者数量一致
    }

    public void produce(Long number) throws InterruptedException {
        queue.put(number);
    }

    // 生产结束后放入特殊的“终止信号”
    public void producePoisonPill() throws InterruptedException {
        for (int i = 0; i < poisonPillCount; i++) {
            queue.put(-1L);  // 以-1表示“结束信号”
        }
    }

    public Long consume() throws InterruptedException {
        return queue.take();
    }
}



4.主类

import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ProducerNConsumerNBufferTest {

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

        int bufferSize = 100;//缓冲区大小
        int totalNumbers = 1_000_000;//100万数字

        Scanner scanner = new Scanner(System.in);

        System.out.print("输入生产者数量: ");
        int producerCount = scanner.nextInt();

        System.out.print("输入消费者数量: ");
        int consumerCount = scanner.nextInt();

        Buffer buffer = new Buffer(bufferSize, consumerCount);

        ExecutorService producerPool = Executors.newFixedThreadPool(producerCount, new ThreadFactory("生产者"));
        ExecutorService consumerPool = Executors.newFixedThreadPool(consumerCount, new ThreadFactory("消费者"));

        // 记录开始时间
        long startTime = System.currentTimeMillis();

        //启动生产者线程
        for(int i = 0; i < producerCount; i++){
            producerPool.execute(new Producer(buffer, totalNumbers / producerCount));
        }

        //启动消费者线程
        for(int i = 0; i < consumerCount; i++){
            consumerPool.execute(new Consumer(buffer));
        }

        // 等待所有生产者完成
        producerPool.shutdown();
        while (!producerPool.isTerminated()) {
            // 等待生产者线程池终止
        }

        // 所有生产者结束后发送终止信号
        buffer.producePoisonPill();

        // 等待所有消费者完成
        consumerPool.shutdown();
        while (!consumerPool.isTerminated()) {
            // 等待消费者线程池终止
        }

        // 记录结束时间并输出总耗时
        long endTime = System.currentTimeMillis();
        System.out.println("所有生产者和消费者处理完毕. 总耗时: " + (endTime - startTime) + " 毫秒");
    }
}

// 自定义线程工厂,用于给线程命名
class ThreadFactory implements java.util.concurrent.ThreadFactory {
    private final String baseName;
    private int counter = 1;

    public ThreadFactory(String baseName) {
        this.baseName = baseName;
    }

    @Override
    public Thread newThread(Runnable r) {
        // 自定义线程名称为 baseName + 计数器
        Thread thread = new Thread(r);
        thread.setName(baseName + counter);
        counter++;
        return thread;
    }
}

五、结果与分析

  • 实验一:1 个生产者,1 个消费者。

  • 实验二:1 个生产者,2 个消费者。

  • 实验三:2 个生产者,4 个消费者。

我们可以观察到,随着消费者线程的增加,程序的运行时间逐渐减少,但并不是线性减少。

在本实验中,CPU 密集型任务(判断数字是否为素数)是主要的性能瓶颈。随着消费者线程的增加,CPU 的利用率提升,因为多个线程同时在执行复杂的素数判定算法。这种情况与 I/O 密集型任务不同,增加消费者能够更多地消耗 CPU 资源,从而减少等待时间。

BlockingQueue作为缓冲区机制在该实验中表现良好,能够有效地保证生产者和消费者之间的数据共享和线程安全。


六、总结

        本实验通过生产者-消费者模式展示了多线程在高并发环境中的应用。实验表明,合理的线程

数配置和缓冲区设计能够大幅度提升程序的性能。然而,由于线程的增加带来了上下文切换和资源

争用的开销,因此线程数并不是越多越好,而是需要结合具体任务的特点进行调整。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值