目录
一、前言
在分布式与并行计算课程中,生产者-消费者模式是一种经典的线程间通信机制。本实验旨在
通过该模式体验多线程编程,并探索不同线程数对程序性能的影响。
二、任务目标
- 使用一个生产者线程生成 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作为缓冲区机制在该实验中表现良好,能够有效地保证生产者和消费者之间的数据共享和线程安全。
六、总结
本实验通过生产者-消费者模式展示了多线程在高并发环境中的应用。实验表明,合理的线程
数配置和缓冲区设计能够大幅度提升程序的性能。然而,由于线程的增加带来了上下文切换和资源
争用的开销,因此线程数并不是越多越好,而是需要结合具体任务的特点进行调整。
977

被折叠的 条评论
为什么被折叠?



