场景:
生产者消费者都分别使用多线程,生产者多个线程不断产生以下分学课数据,由消费者多个线程处理数据,并输出处理每条数据时的当前科目的平均分、top3的学生名和分数。
- //张1 数学 50
- //张1 语文 70
- //张1 英语 60
- //张2 数学 20
- //张2 语文 40
- //张2 英语 20
- //张3 数学 50
- //张3 语文 60
- //张3 英语 55
- //张4 数学 22
- //张4 语文 11
- //张4 英语 70
- //张4 **科 74
- //张5 **科 75
设计
生产者:多个线程向线程安全的 LinkedBlockingQueue 存放每个学生的成绩数据
消费者:按科目分为多个 PriorityQueue 小顶堆,存放TOP成绩数据,其线程安全由业务代码保证;全局总分与人数记录,使用非线程安全的 HashMap。
核心代码与注释
全局共享数据
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.*;
/**
* @Author Snail
* @Describe 全局变量,部分变量是线程安全的,部分线程不安全的变量由单机锁去控制线程安全问题
* @CreateTime 2021/8/31
*/
public class Global {
//存放学生数据,是线程安全的
public static LinkedBlockingQueue<Student> scoreBlockingQueue = new LinkedBlockingQueue();
//PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全。小顶堆存放各个科目成绩的top3
public static PriorityQueue<Student> heapEnglish = new PriorityQueue<>(3, (Student m, Student n) -> {
return m.getScore() - n.getScore();
});
public static PriorityQueue<Student> heapMathematics = new PriorityQueue<>(3, (Student m, Student n) -> {
return m.getScore() - n.getScore();
});
public static PriorityQueue<Student> heapChinese = new PriorityQueue<>(3, (Student m, Student n) -> {
return m.getScore() - n.getScore();
});
//全局科目 总分与人数
public static Map<Subject, SubjectScore> subjectInfo = new HashMap<>();
public ThreadPoolExecutor poolTaskExecutor = null;
public ThreadPoolExecutor getPoolTaskExecutor() {
if (poolTaskExecutor == null) {
synchronized (this) {
if (poolTaskExecutor == null) {
BlockingQueue<Runnable> linkedQueue = new LinkedBlockingQueue<>();
poolTaskExecutor = new ThreadPoolExecutor(8, 8, 60, TimeUnit.SECONDS, linkedQueue);
RejectedExecutionHandler rejectHandler = new ThreadPoolExecutor.AbortPolicy();
poolTaskExecutor.setRejectedExecutionHandler(rejectHandler);
}
}
}
return poolTaskExecutor;
}
}
生产者:
package executor.ali.interview;
import java.util.Random;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
public class ScoreProducer {
final int producerProcessors = 4;
AtomicInteger msgTotal = new AtomicInteger(0);
/**
* 循环生产数据
*
* @param poolTaskExecutor
* @throws Exception
*/
public void producer(ThreadPoolExecutor poolTaskExecutor) throws Exception {
for (int i = 0; i < producerProcessors; i++) {
poolTaskExecutor.submit(() -> {
try {
synchronized (this) {
//msgTotal 是线程安全的计算器,但两个msgTotal 的复合操作,是线程不安全的,需要加锁;
//如果仅仅是在 data2BlockQueue 的参数中使用 msgTotal 时,那么没有线程安全问题,因为方法栈是线程私有的,其参数也是值拷贝过去的。
while (msgTotal.getAndIncrement() < 1000) {
// TimeUnit.MILLISECONDS.sleep(100);//非同步情况下,可以放大线程安全问题
data2BlockQueue(msgTotal.get());
}
}
} catch (Exception e) {
//记录错误,并告警
System.err.println("生产中断");
e.printStackTrace();
}
});
}
}
private void data2BlockQueue(int total) throws Exception {
Random random = new Random();
// student.setName("张" + msgTotal.get());//此处的 msgTotal.get() 读会受到多线的影响
for (int i = 0; i < Subject.values().length; i++) {
Student student = new Student();
student.setName("张" + total);
// student.setScore(55);
student.setScore(random.nextInt(101));
student.setSubject(Subject.values()[i]);
Global.scoreBlockingQueue.put(student);
System.out.println(Thread.currentThread().getName() + "[生产者投放数据]" + student.toString());
}
}
}
消费者:
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import static executor.ali.interview.Global.*;
/**
* @Author Snail
* @Describe 消费者完成计算
* @CreateTime 2021/8/31
*/
public class ScoreConsumer {
final int processors = 4;
AtomicInteger msg = new AtomicInteger(0);
/**
* 方法中,操作的 共享变量 均是线程安全的,所以,消费过程中,不再需要额外加锁了
*
* @param poolTaskExecutor
*/
public void consumer(ThreadPoolExecutor poolTaskExecutor) {
for (int i = 0; i < processors; i++) {
poolTaskExecutor.submit(() -> {
while (true) {
try {
Student student = scoreBlockingQueue.take();
System.out.println(Thread.currentThread().getName() + "【消费者】获取数据:" + student.toString());
calcAverageAndTop3(student);
} catch (Exception e) {
System.err.println("消费者异常");
e.printStackTrace();
}
}
});
}
}
private synchronized void calcAverageAndTop3(Student student) throws Exception {
//计算平均分,通过向全局的subjectInfo添加数据,然后计算平均分
SubjectScore subjectScore = subjectInfo.get(student.getSubject()) == null ? new SubjectScore() : subjectInfo.get(student.getSubject());
subjectScore.setTotalScore(subjectScore.getTotalScore() + student.getScore());
subjectScore.setTotalStudent(subjectScore.getTotalStudent() + 1);
subjectInfo.put(student.getSubject(), subjectScore);
StringBuilder sb = new StringBuilder(Thread.currentThread().getName() + "【消费者】科目,平均分\n" +
student.getSubject() + "," + subjectScore.getTotalScore() / subjectScore.getTotalStudent() + "\n" + "【消费者】姓名,分数 \n");
//top3,按student的字段消息,向小顶堆中添加元素,拿到top3
Subject subject = student.getSubject();
PriorityQueue<Student> oneHeap;
switch (subject) {
case Chinese:
oneHeap = heapChinese;
break;
case English:
oneHeap = heapEnglish;
break;
case Mathematics:
oneHeap = heapMathematics;
break;
default:
throw new RuntimeException("数据异常");
}
addHeap(oneHeap, 3, student);
Iterator<Student> iterator = oneHeap.iterator();
while (iterator.hasNext()) {
Student s = iterator.next();
sb.append(s.getName() + "," + s.getScore() + "\n");
}
System.out.println(sb.toString());
}
private void addHeap(PriorityQueue<Student> oneHeap, int k, Student student) {
if (oneHeap.size() < k) {
oneHeap.offer(student);
} else {
if (oneHeap.peek().getScore() < student.getScore()) {
oneHeap.poll();
oneHeap.offer(student);
}
}
}
}
程序入口:
import java.util.Scanner;
/**
* //阿里直招评测题目: 基于生产者消费者多线程模式的分组排序取top
* //生产者消费者都分别使用多线程,生产者多个线程不断产生以下分学课数据,由消费者多个线程处理数据,并输出处理每条数据时的
* 当前科目 的平均分、top3的学生名和分数。
* <p>
* 考虑有大量科目和数据条数生产出来,但是计算时可以使用long处理,数据条数< (Long.MAX_VALUE/100) ,每科每分数最高100。
* 思路:
* 生产者:blockingqueue存放成绩数据,
* 消费者:使用3个全局小顶堆存放成绩数据,全局总分与人数记录,需要线程安全
* <p>
* <p>
* //张1 数学 50
* //张1 语文 70
* //张1 英语 60
* //张2 数学 20
* //张2 语文 40
* //张2 英语 20
* //张3 数学 50
* //张3 语文 60
* //张3 英语 55
* //张4 数学 22
* //张4 语文 11
* //张4 英语 70
* //张4 **科 74
* //张5 **科 75
* <p>
* <p>
* <p>
* //注意事项:
* //1、请注意异常处理、代码规范、代码可读性、可测试性等,如果有单元测试更佳。 请考虑是大量的学生科目
*/
/**
*
* @Author Snail
* @Describe 入口
* @CreateTime 2021/8/31
*/
public class Main {
public static void main(String[] args) {
try {
Global g = new Global();
ScoreProducer producer = new ScoreProducer();
producer.producer(g.getPoolTaskExecutor());
ScoreConsumer consumer = new ScoreConsumer();
consumer.consumer(g.getPoolTaskExecutor());
} catch (Exception e) {
e.printStackTrace();
}
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
}
}