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

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

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

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

这里有个例子:

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

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

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

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

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

public class MyTask implements Serializable {
private int id;
private int num;
public MyTask(int id, int num) {
this.id = id;
this.num = num;
}
......
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
", num=" + num +
'}';
}
}

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

public class MyMaster {
//1. 需要一个容器来存储待执行任务
private final ConcurrentLinkedQueue<MyTask> tasks = new ConcurrentLinkedQueue<>(); //2. 需要一个容器来存储执行任务的线程 <线程名称, 线程>
private HashMap<String, Thread> workThreads = new HashMap<>(); //3. 需要一个容器来存储每一个线程执行后的结果 <任务id, 任务结果>
private ConcurrentHashMap<Integer, Object> resMap = new ConcurrentHashMap<>(); //4. 构造函数, 将 Worker 传入, 让每个线程都执行相同的方法
public MyMaster(AbstractWorker myWorker, int workerCount) {
myWorker.setTasks(tasks);
myWorker.setResMap(resMap); for (int i = 1; i <= workerCount; i++) {
String name = "worker" + i;
workThreads.put(name, new Thread(myWorker));
}
} //5. 任务提交到容器中
public boolean addTask(MyTask task) {
return tasks.add(task);
} //6. 任务开始执行方法
public void execute(){
for (Map.Entry<String, Thread> worker : workThreads.entrySet()) {
worker.getValue().start();
}
} //7. 判断所有线程是否执行完毕
public boolean isComplated(){
for (Map.Entry<String, Thread> worker : workThreads.entrySet()) {
if(worker.getValue().getState() != Thread.State.TERMINATED){
return false;
}
}
return true;
} //8. 总结归纳, 获取结果
public int getResult(){
int res = 0;
for (Map.Entry<Integer, Object> resItem : resMap.entrySet()) {
res += (int) resItem.getValue();
}
return res;
}
}

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

public abstract class AbstractWorker implements Runnable {
// <线程名称, 线程>, 子类持有 Master 的任务列表, 从中拿取任务
private ConcurrentLinkedQueue<MyTask> tasks; //<任务id, 任务结果>, 子类持有 Master 的结果列表, 将计算结果放进去
private ConcurrentHashMap<Integer, Object> resMap; public void setTasks(ConcurrentLinkedQueue<MyTask> tasks) {
this.tasks = tasks;
} public void setResMap(ConcurrentHashMap<Integer, Object> resMap) {
this.resMap = resMap;
} @Override
public void run() {
while (true) {
MyTask task = tasks.poll();
if (task == null) {
break;
}
Object res = handle(task);
resMap.put(task.getId(), res);
System.out.println(Thread.currentThread().getName() + " 计算 " + task.getNum() + " 结果为 : " + res);
}
}   //这里将具体的实现逻辑放到子类里去, 可以增加扩展性, 此例中现在是算平方, 那通过传入不同的Worker, 也可以算开方
public abstract Object handle(MyTask task);
} public class SquareWorker extends AbstractWorker { public Object handle(MyTask task) {
int res= task.getNum() * task.getNum();
try {
       //模拟运算耗时
Thread.sleep(100);
}
catch (InterruptedException e) {
e.printStackTrace();
}
return res;
}
}

测试:

public static void main(String[] args) throws InterruptedException {
SquareWorker worker = new SquareWorker();
MyMaster master = new MyMaster(worker, 10);
String outPrint = "";
for (int i = 1; i <= 100; i++) {
MyTask task = new MyTask(i, i);
master.addTask(task); outPrint += i + "²" ;
if(i < 100){
outPrint += " + ";
}
}
System.out.println(outPrint);
   //统计下计算时间
long startTime = System.currentTimeMillis();
master.execute(); while (true){
if(!master.isComplated()){
Thread.sleep(50);
continue;
} int result = master.getResult();
System.out.println("计算的结果为 : " + result + ", 耗时为 : " + (System.currentTimeMillis() - startTime));
break;
}
}

结果:

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

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

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

优点是缩短了执行时间

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

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

先对 Master 进行改写:

public class MyMaster {

    // <线程名称, 线程>, 子类持有 Master 的任务列表, 从中拿取任务
private final ConcurrentLinkedQueue<MyTask> tasks = new ConcurrentLinkedQueue<>(); //2. 需要线程池来对线程进行托管
private ThreadPoolExecutor pool; //3. 具体干活的标准
private AbstractWorker worker; //4. 雇佣几个人干活
private int workerCount ; //3. 需要一个容器来存储每一个线程执行后的结果 <任务id, 任务结果>
private ConcurrentHashMap<Integer, Object> resMap = new ConcurrentHashMap<>(); //4. 构造函数, 将 Worker 传入, 让每个线程都执行相同的方法
public MyMaster(AbstractWorker worker, int workerCount) { this.worker = worker;
worker.setResMap(resMap);
worker.setTasks(tasks); this.workerCount = workerCount; pool = new ThreadPoolExecutor(workerCount, workerCount, 0, TimeUnit.SECONDS, new LinkedBlockingQueue());
} public void addTask(MyTask task) {
tasks.add(task);
} public void execute(){
for (int i = 0; i < workerCount; i++) {
pool.execute(worker);
}
} //7. 判断所有线程是否执行完毕
public void finish() {
pool.shutdown();
} public boolean isFinished() {
return pool.isTerminated();
} //8. 总结归纳, 获取结果
public int getResult() {
int res = 0;
for (Map.Entry<Integer, Object> resItem : resMap.entrySet()) {
res += (int) resItem.getValue();
}
return res;
}
}

测试方法:

public static void main(String[] args) throws InterruptedException {
SquareWorker worker = new SquareWorker();
MyMaster master = new MyMaster(worker, 10); String outPrint = "";
long startTime = System.currentTimeMillis();
for (int i = 1; i <= 100; i++) {
MyTask task = new MyTask(i, i);
master.addTask(task); outPrint += i + "²";
if (i < 100) {
outPrint += " + ";
}
}
System.out.println(outPrint); master.execute(); master.finish(); while (!master.isFinished()){
Thread.sleep(10);
} int res = master.getResult();
System.out.println("计算的结果为 : " + res + ", 耗时为 : " + (System.currentTimeMillis() - startTime));
}

结果:

多线程笔记 - 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. [UVA1494] Qin Shi Huang's National Road System

    题目 戳这里 题解 从今天起我要改邪归正,好好刷题准备联赛! 这是一道经典的最小生成树题目. 枚举每一条边作为道士要修的路,求出包含这条边的最小生成树. 先求出原图的最小生成树. 如果要删的边在最小生 ...

  2. RocketMQ 实战之快速入门

    原文地址:https://www.jianshu.com/p/824066d70da8 最近 RocketMQ 刚刚上生产环境,闲暇之时在这里做一些分享,主要目的是让初学者能快速上手RocketMQ. ...

  3. 用JAVA分别实现WebSocket客户端与服务端

    最近公司在搞一个项目刚好需要用到WebSocket技术来实现实时数据的传输,因为之前也没接触过,所以捣鼓了好些天,最近恰巧有空就写写.有误的地方还请大牛们能及时指正. 项目背景:基于spring+sp ...

  4. 对于传统scnece-classfication的分析

    BoW模型最初应用于文本处理领域,用来对文档进行分类和识别.BoW 模型因为其简单有效的优点而得到了广泛的应用.其基本原理可以用以下例子来给予描述.给定两句简单的文档: 文档 1:“我喜欢跳舞,小明也 ...

  5. Nginx-Http服务器常用配置

    #运行用户 user nobody; #启动进程,通常设置成和cpu的数量相等 worker_processes 1; #全局错误日志及PID文件 #error_log logs/error.log; ...

  6. CGI fastCgi php-fpm PHP-CGI 辨析

    CGI fastCgi php-fpm PHP-CGI 辨析 LNMP环境中的nginx是不支持php的,需要通过fastcgi插件来处理有关php的请求.而php需要php-fpm这个组件提供该功能 ...

  7. qt creator源码全方面分析(0)

    本人主攻C++和Qt. 上两天刚研究完Qt install framework(IFW)应用程序安装框架. google没发现有正儿八经的官方文档的翻译,我就进行了翻译哈!! 系列文章具体见:http ...

  8. Jutil 单元测试

    eclipse 自身集成了jtuil,右击项目,点击bulidpath,addjar,如下所示添加jutil 新建一个Junit的测试类用来测试上面的测试方法,新增Junit的测试类方法如下: // ...

  9. Mac 解决IDEA无法启动的问题

    今天重装了IDEA,发现IDEA启动不了,点击IDEA无反应,使用命令行启动idea,然后看看具体的错误,具体的启动方案就是:打开finder->应用程序->idea.app->右键 ...

  10. ROS机器人之话题(一)

     将消息发布到话题上 首先建立一个basic包(名称自己定)其命令为: 1.创建功能包,应用catkin_create_pkg命令     cd  ~/catkin_ws/src #切换到代码空间,也 ...