多线程系列:附、基于生产者消费者多线程模式的分组排序取top

场景:

   生产者消费者都分别使用多线程,生产者多个线程不断产生以下分学课数据,由消费者多个线程处理数据,并输出处理每条数据时的当前科目的平均分、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();
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值