1.前言

第二篇设计模式的文章我们谈谈Visitor模式。

当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点。

  • 一,基于线程池的实现并发Visitor模式。
  • 二,讨论下并发场景下的一些细节处理。
  • 三,用模拟数据测试并做补充说明。

2.背景

当从网站的某个页面进入购物车时,服务端需要做各种数据处理,比如刷新商品价格,计算促销价、校验库存等等。这些操作会随着业务的增加不断扩展,那么Visitor模式就适合这种场景,这也是它的优点之一,易于新增操作。

3.实现

上图是书上的UML原图,原生实现这里就不重复了,我们直接进入正题。

3.1 并发的visit操作

先来看关键的visit方法

public void visit(Cart cart) {
// 当商品数量不超过两个时,不做并发处理,直接主线程执行,避免浪费线程资源
if (cart.getItems().size() <= 2) {
for (AbstractItem item : cart.getItems()) {
item.accept(this);
}
} else {
//CountDownLatch 用来保证并发执行之后的同步,计数器为商品数量,只有所有visit操作完成后才继续主流程
CountDownLatch countDownLatch = new CountDownLatch(cart.getItems().size());
// 提交task到线程池
cart.getItems().forEach(item -> {
threadPoolExecutor.submit(new SubVisitor(item, countDownLatch));
});
try {
// 等待所有子线程执行完毕
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

SubVisitor很简单,实现Runnalbe接口,负责执行具体的visit逻辑,并在执行完成之后同步操作。

private class SubVisitor implements Runnable {
CountDownLatch countDownLatch;
AbstractItem abstractItem;
public SubVisitor(AbstractItem abstractItem, CountDownLatch countDownLatch) {
this.abstractItem = abstractItem;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
abstractItem.accept(AbstractCartVisitor.this);
// 执行完毕之后同步countDownLatch,即计算器减一
countDownLatch.countDown();
}
}

这里只需注意两点,

  • 在商品数量不超过两个时不使用线程池,以避免浪费资源。
  • 通过并发包中CountDownLatch来同步所有操作。

3.2 线程池配置

另外一个核心的配置是线程池,代码如下。

    int availableProcessors = Runtime.getRuntime().availableProcessors();
threadPoolExecutor = new ThreadPoolExecutor(availableProcessors, availableProcessors,
10, TimeUnit.MINUTES, new SynchronousQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy());

corePoolSize和maximumPoolSize配置为当前cpu核数,之所以一致是因为这样可以让线程池一直处于最大运行状态,我们追求的是最大效率,不需要销毁一部分来达到减少内存占用。

接下来的参数workQueue是线程池的缓冲队列,超出容量的任务会被放入此队列,这里使用SynchronousQueue,它是一个零容量队列,意味着线程池没有缓冲区。

RejectedExecutionHandler是则是拒绝策略,当线程池和缓冲区都满了后将会执行拒绝策略。这里我们使用CallerRunsPolicy,翻译过来就是主线程直接执行,我们可以看下它的实现。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}

这样的RejectedExecutionHandler和workQueue的配置可以达到我们想要的一个效果:

如果线程池未满,则将任务提交给线程池执行;

线程池满了后,不缓冲,由主线程直接执行任务,不阻塞请求。

3.3 测试

我们模拟一个刷新价格的测试场景来看一下效果。

为了简单起见,我们将corePoolSize设为4。

new ThreadPoolExecutor(4, 4 ...

并假设价格查询接口耗时20ms:

public double queryPriceByItemId(int id) {
//模拟调用时间20毫秒
try {
Thread.sleep(20);
} catch (InterruptedException e) {
}
return 1;
}

以下为1~6个商品时执行结果

数量 1 金额 1.0 耗时  23
数量 2 金额 2.0 耗时 40
数量 3 金额 3.0 耗时 20
数量 4 金额 4.0 耗时 20
数量 5 金额 5.0 耗时 21
数量 6 金额 6.0 耗时 40

2个及以下商品时耗时为线性增长,这是由于我们对这种场景没做并发处理。

4个商品时耗时为20ms,符合我们的预期。

为什么5个的时候也是20ms?

其原因在于我们的执行策略是SynchronousQueue+CallerRunsPolicy,第5个商品被主线程直接处理了,因此相当于我们实际的执行线程是5个,也达到了预期效果。

#
以上为并发的Visitor模式的全部内容,完成demo见Github:

https://github.com/wchukai/basic-practice

作者:初开

原文链接:https://wchukai.com/article/design-patterns-concurrent-visitor-patterns-based-on-thread-pools

本文由MetaCLBlog于2017-10-24 09:00:51自动同步至cnblogs

本文基于 知识共享-署名-非商业性使用-禁止演绎 4.0 国际许可协议发布,转载必须保留署名及链接。

设计模式:基于线程池的并发Visitor模式的更多相关文章

  1. python 并发编程 基于线程池实现并发的套接字通信

    不应该让服务端随着 并发的客户端数量增多,而无数起线程,应该用线程池,限制线程数量,控制最大并发数 io密集型程序,最大并发数是2 客户端 from socket import * client = ...

  2. 基于线程池的多并发Socket程序的实现

    Socket“服务器-客户端”模型的多线程并发实现效果的大体思路是:首先,在Server端建立“链接循环”,每一个链接都开启一个“线程”,使得每一个Client端都能通过已经建立好的线程来同时与Ser ...

  3. Java线程池(Callable+Future模式)

    转: Java线程池(Callable+Future模式) Java线程池(Callable+Future模式) Java通过Executors提供四种线程池 1)newCachedThreadPoo ...

  4. 基于线程池的多线程售票demo2.0(原创)

    继上回基于线程池的多线程售票demo,具体链接: http://www.cnblogs.com/xifenglou/p/8807323.html以上算是单机版的实现,特别使用了redis 实现分布式锁 ...

  5. 基于线程池的多线程售票demo(原创)

    废话不多说,直接就开撸import org.springframework.util.StopWatch;import java.util.concurrent.*;/** * 基于线程池实现的多线程 ...

  6. requests模块session处理cookie 与基于线程池的数据爬取

    引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests模块常规操作时,往往达不到我们想要的目的,例如: #!/usr/bin/ ...

  7. 【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)

    了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...

  8. Python网络爬虫之cookie处理、验证码识别、代理ip、基于线程池的数据爬去

    本文概要 session处理cookie proxies参数设置请求代理ip 基于线程池的数据爬取 引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时, ...

  9. Java多线程设计模式(4)线程池模式

    前序: Thread-Per-Message Pattern,是一种对于每个命令或请求,都分配一个线程,由这个线程执行工作.它将“委托消息的一端”和“执行消息的一端”用两个不同的线程来实现.该线程模式 ...

随机推荐

  1. 201521123030《Java程序设计》第1周学习总结

    #1. 本章学习总结 你对于本章知识的学习总结 了解了java的发展简介 认识了jvm/jre/jdk的联系,学习安装jdk java好麻烦的说... 1.为什么java程序可以跨平台运行?执行jav ...

  2. Java课程设计-计算器

    1.团队课程设计博客链接 http://www.cnblogs.com/yuanj/p/7072137.html 2.个人负责模块或任务说明 监听器的设置 3.自己的代码提交记录截图 //注册各个组件 ...

  3. MarkDown换行

    现象:1,MarkDown编辑两行显示,但是实际显示为一行: 2,编辑状态中空一行,此时显示为也空了一行,界面显示不友好: 解决:在第一行中最后输入至少2个空格+回车即可显示正常:

  4. Ajax跨域问题的出现和解决

    什么是跨域? 1).请求是执行过去了,但是响应的数据拿不到 2).浏览器有一个安全限制叫同源策略(针对ajax请求): 从http://localhost:80/member/apply.html页面 ...

  5. python之异常

    一.异常与错误 1.语法错误 错误一: if 错误二: def text: pass 错误三: print(sjds 2.逻辑错误 #用户输入不完整(比如输入为空)或者输入非法(输入不是数字) num ...

  6. 关于学习Python的一些心得

    1.关注函数参数的类型,如列表,字符串,int,而不是关注函数的功能 2.导入模块numpy,dir(numpy) 查看所有属性 3.''.join(列表)  将列表拆成字符串

  7. JavaScript中的alert、confirm、prompt

    alert: var a=alert('Alert');//界面只有一個確定alert(a);   //返回值為undefined confirm: var c= confirm('Confirm') ...

  8. Unity 3D 之贪吃蛇 Text 心得 & Audio

    当我们需要在游戏街面上增加文本时, 我们就需要用到Text 组件 注意: 当文本的长度或者宽度不够时,字体将无法显示. 因为是面对组件编程,所以每一个组件的component都可以同过GetCompo ...

  9. 自学Unity3D 之 贪吃蛇

    从一个Java程序员转换去做VR ,先开始自学U3D 吧, 最近跟着一起做一个贪吃蛇的项目 从传课网上面再学 第一天: 因为之前已经对VR 的开发有了一些了解,也买了本书,了解了Unity的基本操作. ...

  10. [js高手之路] es6系列教程 - Set详解与抽奖程序应用实战

    我们还是从一些现有的需求和问题出发,为什么会有set,他的存在是为了解决什么问题? 我们看一个这样的例子,为一个对象添加键值对 var obj = Object.create( null ); obj ...