多线程的 Master-Worker 从字面上也是可以理解的.

Master 相当于领导, 一个就够了, 如果是多个, 那么听谁的, 是个大问题. Master负责指派任务给 Worker. 然后对每个人完成的情况进行汇总.

Worker 相当于具体干活的人, 完成领导分配的任务, 然后把成果交给领导.

这种模式, 有点类似大数据的 MapReduce. 但是比那个简单很多.

这里有个例子:

计算 1²+2²+......+100²的结果.

假如使用 Master-Worker 的方式来计算, 先假设 平方计算 比较耗时, 此处假设每一次平方运算耗时为 100ms.

那么此处要得到计算结果, 就至少需要 100 * 100ms = 10000 ms, 也就是 10s 的时间.

如果此处通过 Master-Worker 的模式来解决此问题, 那么时间会大大缩短.

MyTask 用来存储待计算的数值, 如: 1, 2, 3

  1. public class MyTask implements Serializable {
  2. private int id;
  3. private int num;
  4. public MyTask(int id, int num) {
  5. this.id = id;
  6. this.num = num;
  7. }
  8. ......
  9. @Override
  10. public String toString() {
  11. return "MyTask{" +
  12. "id=" + id +
  13. ", num=" + num +
  14. '}';
  15. }
  16. }

Master既然是领导, 肯定需要知道来了哪些任务, 要分配给哪些下属, 且下属干活的成果是什么

  1. public class MyMaster {
  2. //1. 需要一个容器来存储待执行任务
  3. private final ConcurrentLinkedQueue<MyTask> tasks = new ConcurrentLinkedQueue<>();
  4.  
  5. //2. 需要一个容器来存储执行任务的线程 <线程名称, 线程>
  6. private HashMap<String, Thread> workThreads = new HashMap<>();
  7.  
  8. //3. 需要一个容器来存储每一个线程执行后的结果 <任务id, 任务结果>
  9. private ConcurrentHashMap<Integer, Object> resMap = new ConcurrentHashMap<>();
  10.  
  11. //4. 构造函数, 将 Worker 传入, 让每个线程都执行相同的方法
  12. public MyMaster(AbstractWorker myWorker, int workerCount) {
  13. myWorker.setTasks(tasks);
  14. myWorker.setResMap(resMap);
  15.  
  16. for (int i = 1; i <= workerCount; i++) {
  17. String name = "worker" + i;
  18. workThreads.put(name, new Thread(myWorker));
  19. }
  20. }
  21.  
  22. //5. 任务提交到容器中
  23. public boolean addTask(MyTask task) {
  24. return tasks.add(task);
  25. }
  26.  
  27. //6. 任务开始执行方法
  28. public void execute(){
  29. for (Map.Entry<String, Thread> worker : workThreads.entrySet()) {
  30. worker.getValue().start();
  31. }
  32. }
  33.  
  34. //7. 判断所有线程是否执行完毕
  35. public boolean isComplated(){
  36. for (Map.Entry<String, Thread> worker : workThreads.entrySet()) {
  37. if(worker.getValue().getState() != Thread.State.TERMINATED){
  38. return false;
  39. }
  40. }
  41. return true;
  42. }
  43.  
  44. //8. 总结归纳, 获取结果
  45. public int getResult(){
  46. int res = 0;
  47. for (Map.Entry<Integer, Object> resItem : resMap.entrySet()) {
  48. res += (int) resItem.getValue();
  49. }
  50. return res;
  51. }
  52. }

Worker 作为干活的人, 也需要知道任务列表, 当干完一个之后, 可以再领取一个任务撸起袖子加油干. 然后还需要知道需要将干活的成果放到哪里去.

  1. public abstract class AbstractWorker implements Runnable {
  2. // <线程名称, 线程>, 子类持有 Master 的任务列表, 从中拿取任务
  3. private ConcurrentLinkedQueue<MyTask> tasks;
  4.  
  5. //<任务id, 任务结果>, 子类持有 Master 的结果列表, 将计算结果放进去
  6. private ConcurrentHashMap<Integer, Object> resMap;
  7.  
  8. public void setTasks(ConcurrentLinkedQueue<MyTask> tasks) {
  9. this.tasks = tasks;
  10. }
  11.  
  12. public void setResMap(ConcurrentHashMap<Integer, Object> resMap) {
  13. this.resMap = resMap;
  14. }
  15.  
  16. @Override
  17. public void run() {
  18. while (true) {
  19. MyTask task = tasks.poll();
  20. if (task == null) {
  21. break;
  22. }
  23. Object res = handle(task);
  24. resMap.put(task.getId(), res);
  25. System.out.println(Thread.currentThread().getName() + " 计算 " + task.getNum() + " 结果为 : " + res);
  26. }
  27. }
  28.  
  29.   //这里将具体的实现逻辑放到子类里去, 可以增加扩展性, 此例中现在是算平方, 那通过传入不同的Worker, 也可以算开方
  30. public abstract Object handle(MyTask task);
  31. }
  32.  
  33. public class SquareWorker extends AbstractWorker {
  34.  
  35. public Object handle(MyTask task) {
  36. int res= task.getNum() * task.getNum();
  37. try {
  38.        //模拟运算耗时
  39. Thread.sleep(100);
  40. }
  41. catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. return res;
  45. }
  46. }

测试:

  1. public static void main(String[] args) throws InterruptedException {
  2. SquareWorker worker = new SquareWorker();
  3. MyMaster master = new MyMaster(worker, 10);
  4. String outPrint = "";
  5. for (int i = 1; i <= 100; i++) {
  6. MyTask task = new MyTask(i, i);
  7. master.addTask(task);
  8.  
  9. outPrint += i + "²" ;
  10. if(i < 100){
  11. outPrint += " + ";
  12. }
  13. }
  14. System.out.println(outPrint);
       //统计下计算时间
  15. long startTime = System.currentTimeMillis();
  16. master.execute();
  17.  
  18. while (true){
  19. if(!master.isComplated()){
  20. Thread.sleep(50);
  21. continue;
  22. }
  23.  
  24. int result = master.getResult();
  25. System.out.println("计算的结果为 : " + result + ", 耗时为 : " + (System.currentTimeMillis() - startTime));
  26. break;
  27. }
  28. }

结果:

可以看到, 耗时才 1s 多一点, 比之前的 10s 中, 确实缩短了很多.

此处的输出顺序, 并不是有序的. 这也是多线程的一个特点, 正常情况下代码书写顺序和多线程的执行顺序, 往往不是一致的.

当然任何方式, 都是有优点和缺点的.

优点是缩短了执行时间

缺点却是起了更多的线程, 要知道起线程(还有的优化)是有开销的. 起了更多的线程, 就占了更多的空间. 相当于是用空间换时间的一种干法.

这里可以先做一个小优化, 将线程交给线程池托管. 可以不用知道下面具体是那些人干活, 有点外包的意思. master 只要发布任务, 然后拿到自己想要的结果就行了.

先对 Master 进行改写:

  1. public class MyMaster {
  2.  
  3. // <线程名称, 线程>, 子类持有 Master 的任务列表, 从中拿取任务
  4. private final ConcurrentLinkedQueue<MyTask> tasks = new ConcurrentLinkedQueue<>();
  5.  
  6. //2. 需要线程池来对线程进行托管
  7. private ThreadPoolExecutor pool;
  8.  
  9. //3. 具体干活的标准
  10. private AbstractWorker worker;
  11.  
  12. //4. 雇佣几个人干活
  13. private int workerCount ;
  14.  
  15. //3. 需要一个容器来存储每一个线程执行后的结果 <任务id, 任务结果>
  16. private ConcurrentHashMap<Integer, Object> resMap = new ConcurrentHashMap<>();
  17.  
  18. //4. 构造函数, 将 Worker 传入, 让每个线程都执行相同的方法
  19. public MyMaster(AbstractWorker worker, int workerCount) {
  20.  
  21. this.worker = worker;
  22. worker.setResMap(resMap);
  23. worker.setTasks(tasks);
  24.  
  25. this.workerCount = workerCount;
  26.  
  27. pool = new ThreadPoolExecutor(workerCount, workerCount, 0, TimeUnit.SECONDS, new LinkedBlockingQueue());
  28. }
  29.  
  30. public void addTask(MyTask task) {
  31. tasks.add(task);
  32. }
  33.  
  34. public void execute(){
  35. for (int i = 0; i < workerCount; i++) {
  36. pool.execute(worker);
  37. }
  38. }
  39.  
  40. //7. 判断所有线程是否执行完毕
  41. public void finish() {
  42. pool.shutdown();
  43. }
  44.  
  45. public boolean isFinished() {
  46. return pool.isTerminated();
  47. }
  48.  
  49. //8. 总结归纳, 获取结果
  50. public int getResult() {
  51. int res = 0;
  52. for (Map.Entry<Integer, Object> resItem : resMap.entrySet()) {
  53. res += (int) resItem.getValue();
  54. }
  55. return res;
  56. }
  57. }

测试方法:

  1. public static void main(String[] args) throws InterruptedException {
  2. SquareWorker worker = new SquareWorker();
  3. MyMaster master = new MyMaster(worker, 10);
  4.  
  5. String outPrint = "";
  6. long startTime = System.currentTimeMillis();
  7. for (int i = 1; i <= 100; i++) {
  8. MyTask task = new MyTask(i, i);
  9. master.addTask(task);
  10.  
  11. outPrint += i + "²";
  12. if (i < 100) {
  13. outPrint += " + ";
  14. }
  15. }
  16. System.out.println(outPrint);
  17.  
  18. master.execute();
  19.  
  20. master.finish();
  21.  
  22. while (!master.isFinished()){
  23. Thread.sleep(10);
  24. }
  25.  
  26. int res = master.getResult();
  27. System.out.println("计算的结果为 : " + res + ", 耗时为 : " + (System.currentTimeMillis() - startTime));
  28. }

结果:

多线程笔记 - Master-Worker的更多相关文章

  1. 2016/1/25 多线程 作业 方法一 继承Thread 方法二 实现Runnable 多线程笔记

    /* * 1,尝试定义一个继承Thread类的类,并覆盖run()方法, * 在run()方法中每隔100毫秒打印一句话.*/ package Stream; //方法一 继承Thread 实现多线程 ...

  2. Java基础知识强化之多线程笔记01:多线程基础知识(详见Android(java)笔记61~76)

    1. 基础知识: Android(java)学习笔记61:多线程程序的引入    ~    Android(java)学习笔记76:多线程-定时器概述和使用 

  3. Java多线程笔记总结

    1.线程的三种创建方式 对比三种方式: 通过继承Thread类实现 通过实现Runnable接口 实现Callable接口 第1种方式无法继承其他类,第2,3种可以继承其他类: 第2,3种方式多线程可 ...

  4. 多线程笔记 - provider-consumer

    通过多线程实现一个简单的生产者-消费者案例(笔记). 首先定义一个要生产消费的数据类 : public class Data { private String id; private String n ...

  5. iOS多线程笔记

    在iOS开发中,有三种多线程处理方式: 1. 利用NSThread 2. NSOperation和NSOperationQueue 3. 利用GCD(Grand Central Dispatch) 使 ...

  6. MongoDB学习笔记——Master/Slave主从复制

    Master/Slave主从复制 主从复制MongoDB中比较常用的一种方式,如果要实现主从复制至少应该有两个MongoDB实例,一个作为主节点负责客户端请求,另一个作为从节点负责从主节点映射数据,提 ...

  7. Java基础知识强化之多线程笔记05:Java中继承thread类 与 实现Runnable接口的区别

    1. Java中线程的创建有两种方式:  (1)通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中. (2)通过实现Runnable接口,实例化Thread类. 2. ...

  8. QT多线程笔记

    1.QT多线程涉及到主线程和子线程之间交互大量数据的时候,使用QThread并不方便,因为run()函数本身不能接受任何参数,因此只能通过信号和槽的交互来获取数据,如果只是单方面简单交互数据还过得去, ...

  9. Java基础知识强化之多线程笔记05:Java程序运行原理 和 JVM的启动是多线程的吗

    1. Java程序运行原理:     Java 命令会启动Java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程.该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 m ...

随机推荐

  1. python3小脚本-监控服务器性能并插入mysql数据库

    操作系统: centos版本 7.4 防火墙 关闭 selinux 关闭 python版本 3.6 mysql版本 5.7 #操作系统性能脚本 [root@localhost sql]# cat cp ...

  2. 深入理解Java虚拟机:JVM高级特性与最佳实践

    第一部分走近Java第1章走近Java21.1概述21.2Java技术体系31.3Java发展史51.4Java虚拟机发展史91.4.1SunClassicExactVM91.4.2SunHotSpo ...

  3. 每天敲一点code

    下面这段代码摘自 <C#并发编程经典实例> 并行LINQ static IEnumerable<int> MultiplyBy2(IEnumerable<int> ...

  4. SpringCloud与微服务Ⅹ --- SpringCloud Config分布式配置中心

    一.SpringCloud Config是什么 分布式系统面临的问题 --- 配置问题 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务.由于每个 ...

  5. python 不可变字典 inmutabledict的实现

    python inmutabledict的实现 关于在python中如何实现不可变字典的方法.早在pep416中,就建议python官方实现inmutabledict,但是官方否认了.理由主要是 根据 ...

  6. C++函数模板详解(一):概念和特性

    函数模板是指这样的一类函数:可以用多种不同数据类型的参数进行调用,代表了一个函数家族.它的外表和普通的函数很相似,唯一的区别就是:函数中的有些元素是未确定的,这些元素将在使用的时候才被实例化.先来看一 ...

  7. 构建一个学生Student,根据类Student的定义,创建五个该类的对象,输出每个学生的信息,计算并输出这五个学生Java语言成绩的平均值,以及计算并输出他们Java语言成绩的最大值和最小值。

    定义一个表示学生信息的类Student,要求如下: (1)类Student的成员变量: sNO 表示学号: sName表示姓名: sSex表示性别: sAge表示年龄: sJava:表示Java课程成 ...

  8. 如何播放 WAV 文件?

    from http://www.vckbase.com/index.php/wv/434 平时,你在多媒体软件的设计中是怎样处理声音文件的呢?使用Windows 提供的API函数 sndPlaySou ...

  9. Codeforces Global Round 3(A-D)

    我凉了..感觉自己啥都不会做,搞不好起床就绿了啊 代码和反思起床补,今天要反了个大思的 A. Another One Bites The Dust 把所有的ab排在一起然后两边叉a和b #includ ...

  10. requests的post提交form-data; boundary=????

    提交这种用boundary分隔的表单数据时,有两种方法,一种是以传入files参数,另一种是传入data参数,data参数需要自己用boundary来分隔为指定的形式,而files参数则以元组的形式传 ...