多线程三种设计模式-

Future、Master-Worker和生产者-消费者模型

2018年03月06日 22:32:08 努力做最好的自己 阅读数:391

并行设计模式属于设计优化的一部分,它是对一些常用的多线程结构的总结和抽象。与串行结构相比,并行程序的结构通常更为复杂。因此合理的使用并行模式在多线程开发中更具有意义,在这里主要介绍Future、Master-Worker和生产者-消费者模型。

Future模式

Future模式有点类似于商品订单。比如在网购时,当看重某一件商品时,就可以提交订单,当订单处理完成后,在家里等待商品送货上门即可。或者说更形象的我们发送ajax请求的时候,页面是异步的进行后台处理,用户无须一直等待请求的结果,可以继续浏览或操作其他内容。

Future模式会异步创建一个子线程,去完成相关请求任务,然后将处理结果返回给主线程main。在子线程请求并处理数据的过程中,主线程可以继续做别的事情,即异步加载数据。

Future模式非常适合在处理耗时很长的业务逻辑时进行使用,可以有效减少系统的响应时间,提高系统的吞吐量。

 Main.java

 
  1. public class Main {

  2.  
  3. public static void main(String[] args) throws InterruptedException {

  4.  
  5. FutureClient fc = new FutureClient();

  6. Data data = fc.request("请求参数");

  7. System.out.println("请求发送成功!");

  8. System.out.println("做其他的事情...");

  9.  
  10. String result = data.getRequest();

  11. System.out.println(result);

  12. }

  13. }

FutureClient.java

 
  1. public class FutureClient {

  2.  
  3. public Data request(final String queryStr){

  4. //1 我想要一个代理对象(Data接口的实现类)先返回给发送请求的客户端,告诉他请求已经接收到,可以做其他的事情

  5. final FutureData futureData = new FutureData();

  6. //2 启动一个新的线程,去加载真实的数据,传递给这个代理对象

  7. new Thread(new Runnable() {

  8. @Override

  9. public void run() {

  10. //3 这个新的线程可以去慢慢的加载真实对象,然后传递给代理对象

  11. RealData realData = new RealData(queryStr);

  12. futureData.setRealData(realData);

  13. }

  14. }).start();

  15.  
  16. return futureData;

  17. }

  18.  
  19. }

FutureData.java

 
  1. public class FutureData implements Data{

  2.  
  3. private RealData realData ;

  4.  
  5. private boolean isReady = false;

  6.  
  7. public synchronized void setRealData(RealData realData) {

  8. //如果已经装载完毕了,就直接返回

  9. if(isReady){

  10. return;

  11. }

  12. //如果没装载,进行装载真实对象

  13. this.realData = realData;

  14. isReady = true;

  15. //进行通知

  16. notify();

  17. }

  18.  
  19. @Override

  20. public synchronized String getRequest() {

  21. //如果没装载好 程序就一直处于阻塞状态

  22. while(!isReady){

  23. try {

  24. wait();

  25. } catch (InterruptedException e) {

  26. e.printStackTrace();

  27. }

  28. }

  29. //装载好直接获取数据即可

  30. return this.realData.getRequest();

  31. }

  32.  
  33.  
  34. }

RealData.java

 
  1. public class RealData implements Data{

  2.  
  3. private String result ;

  4.  
  5. public RealData (String queryStr){

  6. System.out.println("根据" + queryStr + "进行查询,这是一个很耗时的操作..");

  7. try {

  8. Thread.sleep(5000);

  9. } catch (InterruptedException e) {

  10. e.printStackTrace();

  11. }

  12. System.out.println("操作完毕,获取结果");

  13. result = "查询结果";

  14. }

  15.  
  16. @Override

  17. public String getRequest() {

  18. return result;

  19. }

  20.  
  21. }


上述程序执行的过程:在main主线程中,创建FutureClient对象并调用其request方法。在request方法体执行过程中,返回一个FutureData代理对象给main主线程,同时开启一个子线程A开始做真正请求处理工作。main主线程得到FutureData代理对象后,继续向下执行代码,执行String result = data.getRequest();,即调用FutureData对象中的getRequest()方法,这个方法是一个同步方法,使用了synchronized关键字修饰,main主线程获取当前FutureData的对象锁之后,执行了wait()方法,main主线程处于阻塞状态,并释放了FutureData的对象锁。在子线程A中创建RealData对象,执行RealData的构造函数,输出内容并使当前线程休眠5S并继续输出内容。RealData对象创建完毕后,在子线程A中执行futureData.setRealData(realData);,这个方法同样是一个同步方法,使用了synchronized关键字修饰,子线程A获取到FutureData对象的锁,执行notify(),发出通知,此时子线程A结束,阻塞的主线程main被唤醒,继续执行getRequest()中的return this.realData.getRequest();。最后,主线程再次执行输出。

Eclipse中console输出如下:

 

JDK中Future模式的封装

其实,在JDK中已经提供了Future模式的封装,使用示例如下:

UseFuture.java

 
  1. import java.util.concurrent.Callable;

  2. import java.util.concurrent.ExecutorService;

  3. import java.util.concurrent.Executors;

  4. import java.util.concurrent.Future;

  5. import java.util.concurrent.FutureTask;

  6.  
  7. public class UseFuture implements Callable<String>{

  8. private String para;

  9.  
  10. public UseFuture(String para){

  11. this.para = para;

  12. }

  13.  
  14. /**

  15. * 这里是真实的业务逻辑,其执行可能很慢

  16. */

  17. @Override

  18. public String call() throws Exception {

  19. //模拟执行耗时

  20. Thread.sleep(5000);

  21. String result = this.para + "处理完成";

  22. return result;

  23. }

  24.  
  25. //主控制函数

  26. public static void main(String[] args) throws Exception {

  27. String queryStr = "query";

  28. //构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类一定是实现了Callable接口的类

  29. FutureTask<String> future = new FutureTask<String>(new UseFuture(queryStr));

  30. FutureTask<String> future2 = new FutureTask<String>(new UseFuture(queryStr));

  31. //创建一个固定线程的线程池且线程数为2,

  32. ExecutorService executor = Executors.newFixedThreadPool(2);

  33. //这里提交任务future,则开启线程执行RealData的call()方法执行

  34. //submit和execute的区别: 第一点是submit可以传入实现Callable接口的实例对象, 第二点是submit方法有返回值

  35. Future f1 = executor.submit(future);//单独启动一个线程A去执行

  36. Future f2 = executor.submit(future2);//单独启动一个线程B去执行

  37. System.out.println("请求完毕");

  38.  
  39. try {

  40. //在线程A、B的执行过程中,主线程main可以做额外的数据操作,也就是主程序执行其他业务逻辑

  41. System.out.println("处理实际的业务逻辑...");

  42. Thread.sleep(1000);

  43. } catch (Exception e) {

  44. e.printStackTrace();

  45. }

  46. //调用获取数据方法,如果call()方法没有执行完成,则依然会进行等待

  47. System.out.println("数据:" + future.get());//future.get()获取线程A执行任务的结果

  48. System.out.println("数据:" + future2.get());//future2.get()获取线程B执行任务的结果

  49.  
  50. executor.shutdown();

  51. }

  52.  
  53. }

创建了2个FutureTask对象,并且传入实现了Callable接口并进行真实业务逻辑处理的类的对象作为参数。创建一个固定数量为2的一个线程池,通过executor.submit(FutureTask对象)来将task任务交给线程池中的线程进行处理。在两个处理task任务的子线程执行过程中,main主线程可以继续执行下面的代码System.out.println("请求完毕");。主线程main执行到future.get()时,若处理该task的子线程已经将该任务处理完毕,则future.get()可以获得子线程A的任务处理结果,同理future2.get()可以获得子线程B的任务处理结果。若future.get()代码执行时,处理future任务的子线程A还没有处理完成,则主线程main需要等待,直到子线程A处理完成,则future.get()获得任务处理结果后,则main主线程才可以继续向下执行代码。最后,将线程池关闭。

Eclipse的console输出:

 

Master-Worker模式

Master-Worker模式是常用的并行计算模式,它的核心思想是系统由两类进程协作工作:Master进程和Worker进程。Master负责接收和分配任务,Worker负责处理子任务。当各个Worker子进程处理完成后,会将结果返回给Master,由Master做归纳和总结。其好处是能将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量。

在系统的数据量不是很大的场景,用Hadoop或者Storm有点大材小用,可以考虑Master-Worker。

Main.java

 
  1. import java.util.Random;

  2.  
  3. public class Main {

  4.  
  5. public static void main(String[] args) {

  6.  
  7. System.out.println("本机器可用processor数量:"+Runtime.getRuntime().availableProcessors());

  8. Master master = new Master(new Worker(), Runtime.getRuntime().availableProcessors());

  9.  
  10. Random r = new Random();

  11. for(int i = 1; i <= 100; i++){

  12. Task t = new Task();

  13. t.setId(i);

  14. t.setPrice(r.nextInt(1000));

  15. master.submit(t);

  16. }

  17. master.execute();

  18. long start = System.currentTimeMillis();

  19.  
  20. while(true){

  21. if(master.isComplete()){

  22. long end = System.currentTimeMillis() - start;

  23. int priceResult = master.getResult();

  24. System.out.println("最终结果:" + priceResult + ", 执行时间:" + end);

  25. break;

  26. }

  27. }

  28.  
  29. }

  30. }

Master.java

 
  1. import java.util.HashMap;

  2. import java.util.Map;

  3. import java.util.concurrent.ConcurrentHashMap;

  4. import java.util.concurrent.ConcurrentLinkedQueue;

  5.  
  6. public class Master {

  7.  
  8. //1 有一个盛放任务的容器

  9. private ConcurrentLinkedQueue<Task> workQueue = new ConcurrentLinkedQueue<Task>();

  10.  
  11. //2 需要有一个盛放worker的集合

  12. private HashMap<String, Thread> workers = new HashMap<String, Thread>();

  13.  
  14. //3 需要有一个盛放每一个worker执行任务的结果集合

  15. private ConcurrentHashMap<String, Object> resultMap = new ConcurrentHashMap<String, Object>();

  16.  
  17. //4 构造方法

  18. public Master(Worker worker , int workerCount){

  19. worker.setWorkQueue(this.workQueue);

  20. worker.setResultMap(this.resultMap);

  21.  
  22. for(int i = 0; i < workerCount; i ++){

  23. this.workers.put(Integer.toString(i), new Thread(worker));

  24. }

  25.  
  26. }

  27.  
  28. //5 需要一个提交任务的方法

  29. public void submit(Task task){

  30. this.workQueue.add(task);

  31. }

  32.  
  33. //6 需要有一个执行的方法,启动所有的worker方法去执行任务

  34. public void execute(){

  35. for(Map.Entry<String, Thread> me : workers.entrySet()){

  36. me.getValue().start();

  37. }

  38. }

  39.  
  40. //7 判断是否运行结束的方法

  41. public boolean isComplete() {

  42. for(Map.Entry<String, Thread> me : workers.entrySet()){

  43. if(me.getValue().getState() != Thread.State.TERMINATED){

  44. return false;

  45. }

  46. }

  47. return true;

  48. }

  49.  
  50. //8 计算结果方法

  51. public int getResult() {

  52. int priceResult = 0;

  53. for(Map.Entry<String, Object> me : resultMap.entrySet()){

  54. priceResult += (Integer)me.getValue();

  55. }

  56. return priceResult;

  57. }

  58. }

Task.java

 
  1. public class Task {

  2.  
  3. private int id;

  4. private int price ;

  5. public int getId() {

  6. return id;

  7. }

  8. public void setId(int id) {

  9. this.id = id;

  10. }

  11. public int getPrice() {

  12. return price;

  13. }

  14. public void setPrice(int price) {

  15. this.price = price;

  16. }

  17.  
  18. }

Worker.java

 
  1. import java.util.concurrent.ConcurrentHashMap;

  2. import java.util.concurrent.ConcurrentLinkedQueue;

  3.  
  4. public class Worker implements Runnable {

  5.  
  6. private ConcurrentLinkedQueue<Task> workQueue;

  7. private ConcurrentHashMap<String, Object> resultMap;

  8.  
  9. public void setWorkQueue(ConcurrentLinkedQueue<Task> workQueue) {

  10. this.workQueue = workQueue;

  11. }

  12.  
  13. public void setResultMap(ConcurrentHashMap<String, Object> resultMap) {

  14. this.resultMap = resultMap;

  15. }

  16.  
  17. @Override

  18. public void run() {

  19. while(true){

  20. Task input = this.workQueue.poll();//获取并移除队列的头元素

  21. if(input == null) break;

  22. Object output = MyWorker.handle(input);

  23. this.resultMap.put(Integer.toString(input.getId()), output);

  24. }

  25. }

  26.  
  27. /*private Object handle(Task input) {

  28. Object output = null;

  29. try {

  30. //处理任务的耗时。。 比如说进行操作数据库。。。

  31. Thread.sleep(500);

  32. output = input.getPrice();

  33. } catch (InterruptedException e) {

  34. e.printStackTrace();

  35. }

  36. return output;

  37. }*/

  38.  
  39. private static Object handle(Task input) {

  40.  
  41. return null;

  42. }

  43. }

MyWorker.java

 
  1. import test.Worker;

  2.  
  3. public class MyWorker extends Worker{

  4.  
  5. public static Object handle(Task input){

  6. Object output = null;

  7. try {

  8. //处理任务的耗时。。 比如说进行操作数据库。。。

  9. Thread.sleep(500);

  10. output = input.getPrice();

  11. } catch (InterruptedException e) {

  12. e.printStackTrace();

  13. }

  14. return output;

  15. }

  16. }

Eclipse的console输出:

 

生产者-消费者

生产者-消费者也是一个非常经典的多线程模式,我们在实际开发中应用非常广泛的的思想理念。在生产者-消费者模式中:通常有两类线程,即若干个生产者的线程和若干个消费者的线程。生产者线程负责提交用户请求,消费者线程负责具体处理生产者提交的任务,在生产者和消费者之间通过共享内存缓存区进行通信。

Main.java

 
  1. import java.util.concurrent.BlockingQueue;

  2. import java.util.concurrent.ExecutorService;

  3. import java.util.concurrent.Executors;

  4. import java.util.concurrent.LinkedBlockingQueue;

  5.  
  6. public class Main {

  7.  
  8. public static void main(String[] args) throws Exception {

  9. //内存缓冲区,生产者和消费者都需要拥有内存缓冲区的引用

  10. BlockingQueue<Data> queue = new LinkedBlockingQueue<Data>(10);

  11. //生产者

  12. Provider p1 = new Provider(queue);

  13. Provider p2 = new Provider(queue);

  14. Provider p3 = new Provider(queue);

  15. //消费者

  16. Consumer c1 = new Consumer(queue);

  17. Consumer c2 = new Consumer(queue);

  18. Consumer c3 = new Consumer(queue);

  19.  
  20. //创建线程池运行,这是一个缓存的线程池,可以创建无穷大的线程,没有任务的时候不创建线程。空闲线程存活时间为60s(默认值)

  21. ExecutorService cachePool = Executors.newCachedThreadPool();

  22. //将3个生产者、3个消费者交给线程池去执行,线程池会分配线程去执行这些生产者、消费者的任务

  23. cachePool.execute(p1);//execute方法的参数为实现了Runnable接口的类的对象

  24. cachePool.execute(p2);

  25. cachePool.execute(p3);

  26. cachePool.execute(c1);

  27. cachePool.execute(c2);

  28. cachePool.execute(c3);

  29.  
  30. try {

  31. Thread.sleep(3000);

  32. } catch (InterruptedException e) {

  33. e.printStackTrace();

  34. }

  35. p1.stop();

  36. p2.stop();

  37. p3.stop();

  38. try {

  39. Thread.sleep(2000);

  40. } catch (InterruptedException e) {

  41. e.printStackTrace();

  42. }

  43. // cachePool.shutdown();

  44. // cachePool.shutdownNow();

  45. }

  46.  
  47. }

Provider.java

 
  1. import java.util.Random;

  2. import java.util.concurrent.BlockingQueue;

  3. import java.util.concurrent.TimeUnit;

  4. import java.util.concurrent.atomic.AtomicInteger;

  5.  
  6. public class Provider implements Runnable{

  7. //共享缓存区

  8. private BlockingQueue<Data> queue;

  9. //多线程间是否启动变量,有强制从主内存中刷新的功能。即时返回线程的状态

  10. private volatile boolean isRunning = true;

  11. //id生成器

  12. private static AtomicInteger count = new AtomicInteger();

  13. //随机对象

  14. private static Random r = new Random();

  15.  
  16. public Provider(BlockingQueue queue){

  17. this.queue = queue;

  18. }

  19.  
  20. @Override

  21. public void run() {

  22. while(isRunning){

  23. try {

  24. //随机休眠0 - 1000 毫秒 表示获取数据(产生数据的耗时)

  25. Thread.sleep(r.nextInt(1000));

  26. //获取的数据进行累计...

  27. int id = count.incrementAndGet();

  28. //比如通过一个getData方法获取了

  29. Data data = new Data(Integer.toString(id), "数据" + id);

  30. System.out.println("当前线程:" + Thread.currentThread().getName() + ", 获取了数据,id为:" + id + ", 进行装载到公共缓冲区中...");

  31. if(!this.queue.offer(data, 2, TimeUnit.SECONDS)){

  32. System.out.println("提交缓冲区数据失败....");

  33. //do something... 比如重新提交

  34. }

  35. } catch (InterruptedException e) {

  36. e.printStackTrace();

  37. }

  38. }

  39. }

  40.  
  41. public void stop(){

  42. this.isRunning = false;

  43. }

  44.  
  45. }

Data.java

 
  1. public final class Data {

  2.  
  3. private String id;

  4. private String name;

  5.  
  6. public Data(String id, String name){

  7. this.id = id;

  8. this.name = name;

  9. }

  10.  
  11. public String getId() {

  12. return id;

  13. }

  14.  
  15. public void setId(String id) {

  16. this.id = id;

  17. }

  18.  
  19. public String getName() {

  20. return name;

  21. }

  22.  
  23. public void setName(String name) {

  24. this.name = name;

  25. }

  26.  
  27. @Override

  28. public String toString(){

  29. return "{id: " + id + ", name: " + name + "}";

  30. }

  31.  
  32. }

Consumer.java

 
  1. import java.util.Random;

  2. import java.util.concurrent.BlockingQueue;

  3. import java.util.concurrent.TimeUnit;

  4.  
  5. public class Consumer implements Runnable{

  6.  
  7. private BlockingQueue<Data> queue;

  8.  
  9. public Consumer(BlockingQueue queue){

  10. this.queue = queue;

  11. }

  12.  
  13. //随机对象

  14. private static Random r = new Random();

  15.  
  16. @Override

  17. public void run() {

  18. while(true){

  19. try {

  20. //获取数据

  21. Data data = this.queue.take();

  22. //进行数据处理。休眠0 - 1000毫秒模拟耗时

  23. Thread.sleep(r.nextInt(1000));

  24. System.out.println("当前消费线程:" + Thread.currentThread().getName() + ", 消费成功,消费数据为id: " + data.getId());

  25. } catch (InterruptedException e) {

  26. e.printStackTrace();

  27. }

  28. }

  29. }

  30. }

Eclipse的console中输出:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值