四、生产者消费者模式
Object 的 wait/notify/notifyAll 实现生产者消费者
使用 Object 的 wait/notify/notifyAll 实现生产者消费者模式,这种方式是基于 Object 的 wait/notify/notifyAll 与对象监视器(Monitor)实现线程间的等待和通知。
Monitor 的工作原理,借此我们可以得知,这种方式实现的生产者消费者模式是基于内核来实现的,有可能会导致大量的上下文切换,所以性能并不是最理想的。
Lock 中 Condition 的 await/signal/signalAll 实现生产者消费者
相对 Object 类提供的 wait/notify/notifyAll 方法实现的生产者消费者模式,我更推荐使用 java.util.concurrent 包提供的 Lock && Condition 实现的生产者消费者模式。
在接口 Condition 类中定义了 await/signal/signalAll 方法,其作用与 Object 的 wait/notify/notifyAll 方法类似,该接口类与显示锁 Lock 配合,实现对线程的阻塞和唤醒操作。
显示锁 ReentrantLock 或 ReentrantReadWriteLock 都是基于 AQS 实现的,而在 AQS 中有一个内部类 ConditionObject 实现了 Condition 接口。
我们知道 AQS 中存在一个同步队列(CLH 队列),当一个线程没有获取到锁时就会进入到同步队列中进行阻塞,如果被唤醒后获取到锁,则移除同步队列。
除此之外,AQS 中还存在一个条件队列,通过 addWaiter 方法,可以将 await() 方法调用的线程放入到条件队列中,线程进入等待状态。当调用 signal 以及 signalAll 方法后,线程将会被唤醒,并从条件队列中删除,之后进入到同步队列中。条件队列是通过一个单向链表实现的,所以 Condition 支持多个等待队列。
由上可知,Lock 中 Condition 的 await/signal/signalAll 实现的生产者消费者模式,是基于 Java 代码层实现的,所以在性能和扩展性方面都更有优势。
下面来看一个案例,我们通过一段代码来实现一个商品库存的生产和消费。
public class LockConditionTest {
private LinkedList<String> product = new LinkedList<String>();
private int maxInventory = 10; // 最大库存
private Lock lock = new ReentrantLock();// 资源锁
private Condition condition = lock.newCondition();// 库存非满和非空条件
/**
* 新增商品库存
* @param e
*/
public void produce(String e) {
lock.lock();
try {
while (product.size() == maxInventory) {
condition.await();
}
product.add(e);
System.out.println(" 放入一个商品库存,总库存为:" + product.size());
condition.signalAll();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 消费商品
* @return
*/
public String consume() {
String result = null;
lock.lock();
try {
while (product.size() == 0) {
condition.await();
}
result = product.removeLast();
System.out.println(" 消费一个商品,总库存为:" + product.size());
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return result;
}
/**
* 生产者
* @author admin
*
*/
private class Producer implements Runnable {
public void run() {
for (int i = 0; i < 20; i++) {
produce(" 商品 " + i);
}
}
}
/**
* 消费者
* @author admin
*
*/
private class Customer implements Runnable {
public void run() {
for (int i = 0; i < 20; i++) {
consume();
}
}
}
public static void main(String[] args) {
LockConditionTest lc = new LockConditionTest();
new Thread(lc.new Producer()).start();
new Thread(lc.new Customer()).start();
new Thread(lc.new Producer()).start();
new Thread(lc.new Customer()).start();
}
}
看完案例,请你思考下,我们对此还有优化的空间吗?
从代码中应该不难发现,生产者和消费者都在竞争同一把锁,而实际上两者没有同步关系,由于 Condition 能够支持多个等待队列以及不响应中断, 所以我们可以将生产者和消费者的等待条件和锁资源分离,从而进一步优化系统并发性能,代码如下:
private LinkedList<String> product = new LinkedList<String>();
private AtomicInteger inventory = new AtomicInteger(0);// 实时库存
private int maxInventory = 10; // 最大库存
private Lock consumerLock = new ReentrantLock();// 资源锁
private Lock productLock = new ReentrantLock();// 资源锁
private Condition notEmptyCondition = consumerLock.newCondition();// 库存满和空条件
private Condition notFullCondition = productLock.newCondition();// 库存满和空条件
/**
* 新增商品库存
* @param e
*/
public void produce(String e) {
productLock.lock();
try {
while (inventory.get() == maxInventory) {
notFullCondition.await();
}
product.add(e);
System.out.println(" 放入一个商品库存,总库存为:" + inventory.incrementAndGet());
if(inventory.get()<maxInventory) {
notFullCondition.signalAll();
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
productLock.unlock();
}
if(inventory.get()>0) {
try {
consumerLock.lockInterruptibly();
notEmptyCondition.signalAll();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}finally {
consumerLock.unlock();
}
}
}
/**
* 消费商品
* @return
*/
public String consume() {
String result = null;
consumerLock.lock();
try {
while (inventory.get() == 0) {
notEmptyCondition.await();
}
result = product.removeLast();
System.out.println(" 消费一个商品,总库存为:" + inventory.decrementAndGet());
if(inventory.get()>0) {
notEmptyCondition.signalAll();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
consumerLock.unlock();
}
if(inventory.get()<maxInventory) {
try {
productLock.lockInterruptibly();
notFullCondition.signalAll();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}finally {
productLock.unlock();
}
}
return result;
}
/**
* 生产者
* @author admin
*
*/
private class Producer implements Runnable {
public void run() {
for (int i = 0; i < 20; i++) {
produce(" 商品 " + i);
}
}
}
/**
* 消费者
* @author admin
*
*/
private class Customer implements Runnable {
public void run() {
for (int i = 0; i < 20; i++) {
consume();
}
}
}
public static void main(String[] args) {
LockConditionTest2 lc = new LockConditionTest2();
new Thread(lc.new Producer()).start();
new Thread(lc.new Customer()).start();
}
}
我们分别创建 productLock 以及 consumerLock 两个锁资源,前者控制生产者线程并行操作,后者控制消费者线程并发运行;同时也设置两个条件变量,一个是 notEmptyCondition,负责控制消费者线程状态,一个是 notFullCondition,负责控制生产者线程状态。这样优化后,可以减少消费者与生产者的竞争,实现两者并发执行。
我们这里是基于 LinkedList 来存取库存的,虽然 LinkedList 是非线程安全,但我们新增是操作头部,而消费是操作队列的尾部,理论上来说没有线程安全问题。而库存的实际数量 inventory 是基于 AtomicInteger(CAS 锁)线程安全类实现的,既可以保证原子性,也可以保证消费者和生产者之间是可见的。
BlockingQueue 实现生产者消费者
相对前两种实现方式,BlockingQueue 实现是最简单明了的,也是最容易理解的。
因为 BlockingQueue 是线程安全的,且从队列中获取或者移除元素时,如果队列为空,获取或移除操作则需要等待,直到队列不为空;同时,如果向队列中添加元素,假设此时队列无可用空间,添加操作也需要等待。所以 BlockingQueue 非常适合用来实现生产者消费者模式。还是以一个案例来看下它的优化,代码如下:
public class BlockingQueueTest {
private int maxInventory = 10; // 最大库存
private BlockingQueue<String> product = new LinkedBlockingQueue<>(maxInventory);// 缓存队列
/**
* 新增商品库存
* @param e
*/
public void produce(String e) {
try {
product.put(e);
System.out.println(" 放入一个商品库存,总库存为:" + product.size());
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
/**
* 消费商品
* @return
*/
public String consume() {
String result = null;
try {
result = product.take();
System.out.println(" 消费一个商品,总库存为:" + product.size());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
/**
* 生产者
* @author admin
*
*/
private class Producer implements Runnable {
public void run() {
for (int i = 0; i < 20; i++) {
produce(" 商品 " + i);
}
}
}
/**
* 消费者
* @author admin
*
*/
private class Customer implements Runnable {
public void run() {
for (int i = 0; i < 20; i++) {
consume();
}
}
}
public static void main(String[] args) {
BlockingQueueTest lc = new BlockingQueueTest();
new Thread(lc.new Producer()).start();
new Thread(lc.new Customer()).start();
new Thread(lc.new Producer()).start();
new Thread(lc.new Customer()).start();
}
}
在这个案例中,我们创建了一个 LinkedBlockingQueue,并设置队列大小。之后我们创建一个消费方法 consume(),方法里面调用 LinkedBlockingQueue 中的 take() 方法,消费者通过该方法获取商品,当队列中商品数量为零时,消费者将进入等待状态;我们再创建一个生产方法 produce(),方法里面调用 LinkedBlockingQueue 中的 put() 方法,生产方通过该方法往队列中放商品,如果队列满了,生产者就将进入等待状态。
生产者消费者优化电商库存设计
了解完生产者消费者模式的几种常见实现方式,接下来我们就具体看看该模式是如何优化电商库存设计的。
电商系统中经常会有抢购活动,在这类促销活动中,抢购商品的库存实际是存在库存表中的。为了提高抢购性能,我们通常会将库存存放在缓存中,通过缓存中的库存来实现库存的精确扣减。在提交订单并付款之后,我们还需要再去扣除数据库中的库存。如果遇到瞬时高并发,我们还都去操作数据库的话,那么在单表单库的情况下,数据库就很可能会出现性能瓶颈。
而我们库存表如果要实现分库分表,势必会增加业务的复杂度。试想一个商品的库存分别在不同库的表中,我们在扣除库存时,又该如何判断去哪个库中扣除呢?
如果随意扣除表中库存,那么就会出现有些表已经扣完了,有些表中还有库存的情况,这样的操作显然是不合理的,此时就需要额外增加逻辑判断来解决问题。
在不分库分表的情况下,为了提高订单中扣除库存业务的性能以及吞吐量,我们就可以采用生产者消费者模式来实现系统的性能优化。
创建订单等于生产者,存放订单的队列则是缓冲容器,而从队列中消费订单则是数据库扣除库存操作。其中存放订单的队列可以极大限度地缓冲高并发给数据库带来的压力。
我们还可以基于消息队列来实现生产者消费者模式,如今 RabbitMQ、RocketMQ 都实现了事务,我们只需要将订单通过事务提交到 MQ 中,扣除库存的消费方只需要通过消费 MQ 来逐步操作数据库即可。
使用生产者消费者模式来缓冲高并发数据库扣除库存压力,类似这样的例子其实还有很多。
例如,我们平时使用消息队列来做高并发流量削峰,也是基于这个原理。抢购商品时,如果所有的抢购请求都直接进入判断是否有库存和冻结缓存库存等逻辑业务中,由于这些逻辑业务操作会增加资源消耗,就可能会压垮应用服务。此时,为了保证系统资源使用的合理性,我们可以通过一个消息队列来缓冲瞬时的高并发请求。
生产者消费者模式除了可以做缓冲优化系统性能之外,它还可以应用在处理一些执行任务时间比较长的场景中。
例如导出报表业务,用户在导出一种比较大的报表时,通常需要等待很长时间,这样的用户体验是非常差的。通常我们可以固定一些报表内容,比如用户经常需要在今天导出昨天的销量报表,或者在月初导出上个月的报表,我们就可以提前将报表导出到本地或内存中,这样用户就可以在很短的时间内直接下载报表了。
五、装饰器模式
什么是装饰器模式?
在这之前,我先简单介绍下什么是装饰器模式。装饰器模式包括了以下几个角色:接口、具体对象、装饰类、具体装饰类。
接口定义了具体对象的一些实现方法;具体对象定义了一些初始化操作,比如开头设计装修功能的案例中,水电装修、天花板以及粉刷墙等都是初始化操作;装饰类则是一个抽象类,主要用来初始化具体对象的一个类;其它的具体装饰类都继承了该抽象类。
优化电商系统中的商品价格策略
相信你一定不陌生,购买商品时经常会用到的限时折扣、红包、抵扣券以及特殊抵扣金等,种类很多,如果换到开发视角,实现起来就更复杂了。
例如,每逢双十一,为了加大商城的优惠力度,开发往往要设计红包 + 限时折扣或红包 + 抵扣券等组合来实现多重优惠。而在平时,由于某些特殊原因,商家还会赠送特殊抵扣券给购买用户,而特殊抵扣券 + 各种优惠又是另一种组合方式。
要实现以上这类组合优惠的功能,最快、最普遍的实现方式就是通过大量 if-else 的方式来实现。但这种方式包含了大量的逻辑判断,致使其他开发人员很难读懂业务, 并且一旦有新的优惠策略或者价格组合策略出现,就需要修改代码逻辑。
这时,刚刚介绍的装饰器模式就很适合用在这里,其相互独立、自由组合以及方便动态扩展功能的特性,可以很好地解决 if-else 方式的弊端。下面我们就用装饰器模式动手实现一套商品价格策略的优化方案。
首先,我们先建立订单和商品的属性类,在本次案例中,为了保证简洁性,我只建立了几个关键字段。以下几个重要属性关系为,主订单包含若干详细订单,详细订单中记录了商品信息,商品信息中包含了促销类型信息,一个商品可以包含多个促销类型(本案例只讨论单个促销和组合促销):
/**
* 主订单
* @author admin
*
*/
public class Order {
private int id; // 订单 ID
private String orderNo; // 订单号
private BigDecimal totalPayMoney; // 总支付金额
private List<OrderDetail> list; // 详细订单列表
}
/**
* 详细订单
* @author admin
*
*/
public class OrderDetail {
private int id; // 详细订单 ID
private int orderId;// 主订单 ID
private Merchandise merchandise; // 商品详情
private BigDecimal payMoney; // 支付单价
}
/**
* 商品
* @author admin
*
*/
public class Merchandise {
private String sku;// 商品 SKU
private String name; // 商品名称
private BigDecimal price; // 商品单价
private Map<PromotionType, SupportPromotions> supportPromotions; // 支持促销类型
}
/**
* 促销类型
* @author admin
*
*/
public class SupportPromotions implements Cloneable{
private int id;// 该商品促销的 ID
private PromotionType promotionType;// 促销类型 1\优惠券 2\红包
private int priority; // 优先级
private UserCoupon userCoupon; // 用户领取该商品的优惠券
private UserRedPacket userRedPacket; // 用户领取该商品的红包
// 重写 clone 方法
public SupportPromotions clone(){
SupportPromotions supportPromotions = null;
try{
supportPromotions = (SupportPromotions)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return supportPromotions;
}
}
/**
* 优惠券
* @author admin
*
*/
public class UserCoupon {
private int id; // 优惠券 ID
private int userId; // 领取优惠券用户 ID
private String sku; // 商品 SKU
private BigDecimal coupon; // 优惠金额
}
/**
* 红包
* @author admin
*
*/
public class UserRedPacket {
private int id; // 红包 ID
private int userId; // 领取用户 ID
private String sku; // 商品 SKU
private BigDecimal redPacket; // 领取红包金额
}
接下来,我们再建立一个计算支付金额的接口类以及基本类:
/**
* 计算支付金额接口类
* @author admin
*
*/
public interface IBaseCount {
BigDecimal countPayMoney(OrderDetail orderDetail);
}
/**
* 支付基本类
* @author admin
*
*/
public class BaseCount implements IBaseCount{
public BigDecimal countPayMoney(OrderDetail orderDetail) {
orderDetail.setPayMoney(orderDetail.getMerchandise().getPrice());
System.out.println(" 商品原单价金额为:" + orderDetail.getPayMoney());
return orderDetail.getPayMoney();
}
}
然后,我们再建立一个计算支付金额的抽象类,由抽象类调用基本类:
/**
* 计算支付金额的抽象类
* @author admin
*
*/
public abstract class BaseCountDecorator implements IBaseCount{
private IBaseCount count;
public BaseCountDecorator(IBaseCount count) {
this.count = count;
}
public BigDecimal countPayMoney(OrderDetail orderDetail) {
BigDecimal payTotalMoney = new BigDecimal(0);
if(count!=null) {
payTotalMoney = count.countPayMoney(orderDetail);
}
return payTotalMoney;
}
}
然后,我们再通过继承抽象类来实现我们所需要的修饰类(优惠券计算类、红包计算类):
/**
* 计算使用优惠券后的金额
* @author admin
*
*/
public class CouponDecorator extends BaseCountDecorator{
public CouponDecorator(IBaseCount count) {
super(count);
}
public BigDecimal countPayMoney(OrderDetail orderDetail) {
BigDecimal payTotalMoney = new BigDecimal(0);
payTotalMoney = super.countPayMoney(orderDetail);
payTotalMoney = countCouponPayMoney(orderDetail);
return payTotalMoney;
}
private BigDecimal countCouponPayMoney(OrderDetail orderDetail) {
BigDecimal coupon = orderDetail.getMerchandise().getSupportPromotions().get(PromotionType.COUPON).getUserCoupon().getCoupon();
System.out.println(" 优惠券金额:" + coupon);
orderDetail.setPayMoney(orderDetail.getPayMoney().subtract(coupon));
return orderDetail.getPayMoney();
}
}
/**
* 计算使用红包后的金额
* @author admin
*
*/
public class RedPacketDecorator extends BaseCountDecorator{
public RedPacketDecorator(IBaseCount count) {
super(count);
}
public BigDecimal countPayMoney(OrderDetail orderDetail) {
BigDecimal payTotalMoney = new BigDecimal(0);
payTotalMoney = super.countPayMoney(orderDetail);
payTotalMoney = countCouponPayMoney(orderDetail);
return payTotalMoney;
}
private BigDecimal countCouponPayMoney(OrderDetail orderDetail) {
BigDecimal redPacket = orderDetail.getMerchandise().getSupportPromotions().get(PromotionType.REDPACKED).getUserRedPacket().getRedPacket();
System.out.println(" 红包优惠金额:" + redPacket);
orderDetail.setPayMoney(orderDetail.getPayMoney().subtract(redPacket));
return orderDetail.getPayMoney();
}
}
最后,我们通过一个工厂类来组合商品的促销类型:
/**
* 计算促销后的支付价格
* @author admin
*
*/
public class PromotionFactory {
public static BigDecimal getPayMoney(OrderDetail orderDetail) {
// 获取给商品设定的促销类型
Map<PromotionType, SupportPromotions> supportPromotionslist = orderDetail.getMerchandise().getSupportPromotions();
// 初始化计算类
IBaseCount baseCount = new BaseCount();
if(supportPromotionslist!=null && supportPromotionslist.size()>0) {
for(PromotionType promotionType: supportPromotionslist.keySet()) {// 遍历设置的促销类型,通过装饰器组合促销类型
baseCount = protmotion(supportPromotionslist.get(promotionType), baseCount);
}
}
return baseCount.countPayMoney(orderDetail);
}
/**
* 组合促销类型
* @param supportPromotions
* @param baseCount
* @return
*/
private static IBaseCount protmotion(SupportPromotions supportPromotions, IBaseCount baseCount) {
if(supportPromotions.getPromotionType()==PromotionType.COUPON) {
baseCount = new CouponDecorator(baseCount);
}else if(supportPromotions.getPromotionType()==PromotionType.REDPACKED) {
baseCount = new RedPacketDecorator(baseCount);
}
return baseCount;
}
}
public static void main( String[] args ) throws InterruptedException, IOException
{
Order order = new Order();
init(order);
for(OrderDetail orderDetail: order.getList()) {
BigDecimal payMoney = PromotionFactory.getPayMoney(orderDetail);
orderDetail.setPayMoney(payMoney);
System.out.println(" 最终支付金额:" + orderDetail.getPayMoney());
}
}
运行结果:
商品原单价金额为:20
优惠券金额:3
红包优惠金额:10
最终支付金额:7
通过以上案例可知:使用装饰器模式设计的价格优惠策略,实现各个促销类型的计算功能都是相互独立的类,并且可以通过工厂类自由组合各种促销类型。
六、思考
单例模式和享元模式都是为了避免重复创建对象,你知道这两者的区别在哪儿吗?
首先,这两种设计模式的实现方式是不同的。我们使用单例模式是避免每次调用一个类实例时,都要重复实例化该实例,目的是在类本身获取实例化对象的唯一性;而享元模式则是通过一个共享容器来实现一系列对象的共享。
其次,两者在使用场景上也是有区别的。单例模式更多的是强调减少实例化提升性能,因此它一般是使用在一些需要频繁创建和销毁实例化对象,或创建和销毁实例化对象非常消耗资源的类中。
例如,连接池和线程池中的连接就是使用单例模式实现的,数据库操作是非常频繁的,每次操作都需要创建和销毁连接,如果使用单例,可以节省不断新建和关闭数据库连接所引起的性能消耗。而享元模式更多的是强调共享相同对象或对象属性,以此节约内存使用空间。
除了区别,这两种设计模式也有共性,单例模式可以避免重复创建对象,节约内存空间,享元模式也可以避免一个类的重复实例化。总之,两者很相似,但侧重点不一样,假如碰到一些要在两种设计模式中做选择的场景,我们就可以根据侧重点来选择。
除了以上这些多线程的设计模式(线程上下文设计模式、Thread-Per-Message 设计模式、Worker-Thread 设计模式),平时你还使用过其它的设计模式来优化多线程业务吗?
在这一讲的留言区,undifined 同学问到了,如果我们使用 Worker-Thread 设计模式,worker 线程如果是异步请求处理,当我们监听到有请求进来之后,将任务交给工作线程,怎么拿到返回结果,并返回给主线程呢?
回答这个问题的过程中就会用到一些别的设计模式,可以一起看看。
如果要获取到异步线程的执行结果,我们可以使用 Future 设计模式来解决这个问题。假设我们有一个任务,需要一台机器执行,但是该任务需要一个工人分配给机器执行,当机器执行完成之后,需要通知工人任务的具体完成结果。这个时候我们就可以设计一个 Future 模式来实现这个业务。
首先,我们申明一个任务接口,主要提供给任务设计:
public interface Task<T, P> {
T doTask(P param);// 完成任务
}
其次,我们申明一个提交任务接口类,TaskService 主要用于提交任务,提交任务可以分为需要返回结果和不需要返回结果两种:
public interface TaskService<T, P> {
Future<?> submit(Runnable runnable);// 提交任务,不返回结果
Future<?> submit(Task<T,P> task, P param);// 提交任务,并返回结果
}
接着,我们再申明一个查询执行结果的接口类,用于提交任务之后,在主线程中查询执行结果:
public interface Future<T> {
T get(); // 获取返回结果
boolean done(); // 判断是否完成
}
然后,我们先实现这个任务接口类,当需要返回结果时,我们通过调用获取结果类的 finish 方法将结果传回给查询执行结果类:
public class TaskServiceImpl<T, P> implements TaskService<T, P> {
/**
* 提交任务实现方法,不需要返回执行结果
*/
@Override
public Future<?> submit(Runnable runnable) {
final FutureTask<Void> future = new FutureTask<Void>();
new Thread(() -> {
runnable.run();
}, Thread.currentThread().getName()).start();
return future;
}
/**
* 提交任务实现方法,需要返回执行结果
*/
@Override
public Future<?> submit(Task<T, P> task, P param) {
final FutureTask<T> future = new FutureTask<T>();
new Thread(() -> {
T result = task.doTask(param);
future.finish(result);
}, Thread.currentThread().getName()).start();
return future;
}
}
最后,我们再实现这个查询执行结果接口类,FutureTask 中,get 和 finish 方法利用了线程间的通信 wait 和 notifyAll 实现了线程的阻塞和唤醒。当任务没有完成之前通过 get 方法获取结果,主线程将会进入阻塞状态,直到任务完成,再由任务线程调用 finish 方法将结果传回给主线程,并唤醒该阻塞线程:
public class FutureTask<T> implements Future<T> {
private T result;
private boolean isDone = false;
private final Object LOCK = new Object();
@Override
public T get() {
synchronized (LOCK) {
while (!isDone) {
try {
LOCK.wait();// 阻塞等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return result;
}
/**
* 获取到结果,并唤醒阻塞线程
* @param result
*/
public void finish(T result) {
synchronized (LOCK) {
if (isDone) {
return;
}
this.result = result;
this.isDone = true;
LOCK.notifyAll();
}
}
@Override
public boolean done() {
return isDone;
}
}
我们可以实现一个造车任务,然后用任务提交类提交该造车任务:
public class MakeCarTask<T, P> implements Task<T, P> {
@SuppressWarnings("unchecked")
@Override
public T doTask(P param) {
String car = param + " is created success";
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return (T) car;
}
}
最后运行该任务:
public class App {
public static void main(String[] args) {
// TODO Auto-generated method stub
TaskServiceImpl<String, String> taskService = new TaskServiceImpl<String, String>();// 创建任务提交类
MakeCarTask<String, String> task = new MakeCarTask<String, String>();// 创建任务
Future<?> future = taskService.submit(task, "car1");// 提交任务
String result = (String) future.get();// 获取结果
System.out.print(result);
}
}
运行结果:
car1 is created success
从 JDK1.5 起,Java 就提供了一个 Future 类,它可以通过 get() 方法阻塞等待获取异步执行的返回结果,然而这种方式在性能方面会比较糟糕。在 JDK1.8 中,Java 提供了 CompletableFuture 类,它是基于异步函数式编程。相对阻塞式等待返回结果,CompletableFuture 可以通过回调的方式来处理计算结果,所以实现了异步非阻塞,从性能上来说它更加优越了。
在 Dubbo2.7.0 版本中,Dubbo 也是基于 CompletableFuture 实现了异步通信,基于回调方式实现了异步非阻塞通信,操作非常简单方便。
我们可以用生产者消费者模式来实现瞬时高并发的流量削峰,然而这样做虽然缓解了消费方的压力,但生产方则会因为瞬时高并发,而发生大量线程阻塞。面对这样的情况,你知道有什么方式可以优化线程阻塞所带来的性能问题吗?
无论我们的程序优化得有多么出色,只要并发上来,依然会出现瓶颈。虽然生产者消费者模式可以帮我们实现流量削峰,但是当并发量上来之后,依然有可能导致生产方大量线程阻塞等待,引起上下文切换,增加系统性能开销。这时,我们可以考虑在接入层做限流。
限流的实现方式有很多,例如,使用线程池、使用 Guava 的 RateLimiter 等。但归根结底,它们都是基于这两种限流算法来实现的:漏桶算法和令牌桶算法。
漏桶算法是基于一个漏桶来实现的,我们的请求如果要进入到业务层,必须经过漏桶,漏桶出口的请求速率是均衡的,当入口的请求量比较大的时候,如果漏桶已经满了,请求将会溢出(被拒绝),这样我们就可以保证从漏桶出来的请求量永远是均衡的,不会因为入口的请求量突然增大,致使进入业务层的并发量过大而导致系统崩溃。
令牌桶算法是指系统会以一个恒定的速度在一个桶中放入令牌,一个请求如果要进来,它需要拿到一个令牌才能进入到业务层,当桶里没有令牌可以取时,则请求会被拒绝。Google 的 Guava 包中的 RateLimiter 就是基于令牌桶算法实现的。
我们可以发现,漏桶算法可以通过限制容量池大小来控制流量,而令牌算法则可以通过限制发放令牌的速率来控制流量。
责任链模式、策略模式与装饰器模式有很多相似之处。在平时,这些设计模式除了在业务中被用到之外,在架构设计中也经常被用到,你是否在源码中见过这几种设计模式的使用场景呢?
责任链模式经常被用在一个处理需要经历多个事件处理的场景。为了避免一个处理跟多个事件耦合在一起,该模式会将多个事件连成一条链,通过这条链路将每个事件的处理结果传递给下一个处理事件。责任链模式由两个主要实现类组成:抽象处理类和具体处理类。
另外,很多开源框架也用到了责任链模式,例如 Dubbo 中的 Filter 就是基于该模式实现的。而 Dubbo 的许多功能都是通过 Filter 扩展实现的,比如缓存、日志、监控、安全、telnet 以及 RPC 本身,责任链中的每个节点实现了 Filter 接口,然后由 ProtocolFilterWrapper 将所有的 Filter 串连起来。
策略模式与装饰器模式则更为相似,策略模式主要由一个策略基类、具体策略类以及一个工厂环境类组成,与装饰器模式不同的是,策略模式是指某个对象在不同的场景中,选择的实现策略不一样。例如,同样是价格策略,在一些场景中,我们就可以使用策略模式实现。基于红包的促销活动商品,只能使用红包策略,而基于折扣券的促销活动商品,也只能使用折扣券。