使用
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(() -> {
log.debug("通过Runnable方式执行任务");
}).start();
// new Thread(new Runnable() {
// @Override
// public void run() {
//
// }
// }).start();
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
log.debug("通过Callable方式执行任务");
Thread.sleep(3000);
return "返回任务结果";
}
});
new Thread(task).start();
log.debug("结果:{}", task.get());
}
使用案例:促销活动中商品信息查询
在维护促销活动时需要查询商品信息(包括商品基本信息、商品价格、商品库存、商品图片、商品销售状态等)。这些信息分布在不同的业务中心,由不同的系统提供服务。如果采用同步方式,假设一个接口需要50ms,那么一个商品查询下来就需要200ms-300ms,这对于我们来说是不满意的。如果使用Future改造则需要的就是最长耗时服务的接口,也就是50ms左右。
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task1 = new FutureTask<>(new T1Task());
FutureTask<String> task2 = new FutureTask<>(new T2Task());
FutureTask<String> task3 = new FutureTask<>(new T3Task());
FutureTask<String> task4 = new FutureTask<>(new T4Task());
FutureTask<String> task5 = new FutureTask<>(new T5Task());
//构建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
Future<?> future1 = executorService.submit(task1);
Future<?> future2 = executorService.submit(task2);
Future<?> future3 = executorService.submit(task3);
Future<?> future4 = executorService.submit(task4);
Future<?> future5 = executorService.submit(task5);
//获取执行结果
System.out.println(task1.get());
System.out.println(task2.get());
System.out.println(task3.get());
System.out.println(task4.get());
System.out.println(task5.get());
executorService.shutdown();
}
static class T1Task implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("T1:查询商品基本信息...");
TimeUnit.MILLISECONDS.sleep(50);
return "商品基本信息查询成功";
}
}
static class T2Task implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("T2:查询商品基本信息...");
TimeUnit.MILLISECONDS.sleep(50);
return "商品基本信息查询成功";
}
}
static class T3Task implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("T3:查询商品基本信息...");
TimeUnit.MILLISECONDS.sleep(50);
return "商品基本信息查询成功";
}
}
static class T4Task implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("T4:查询商品基本信息...");
TimeUnit.MILLISECONDS.sleep(50);
return "商品基本信息查询成功";
}
}
static class T5Task implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("T5:查询商品基本信息...");
TimeUnit.MILLISECONDS.sleep(50);
return "商品基本信息查询成功";
}
}
从本质上说,Future表示一个异步计算的结果。它提供了isDone()来检测计算是否已经完成,并且在计算结束后,可以通过get()方法来获取计算结果。在异步计算中,Future确实是个非常优秀的接口。但是,它的本身也确实存在着许多限制:
Callable+Future 可以实现多个task并行执行,但是如果遇到前面的task执行较慢时需要阻塞等待前面的task执行完后面task才能取得结果
。而CompletionService的主要功能就是一边生成任务,一边获取任务的返回值
。让两件事分开执行,任务之间不会互相阻塞,可以实现先执行完的先取结果,不再依赖任务顺序了
。(CompletionService没有任务的顺序,而futuretask需要依赖任务顺序,会产生阻塞)
CompletionService原理
内部通过阻塞队列+FutureTask,实现了任务先完成可优先获取到,即结果按照完成先后顺序排序,内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,通过调用它的take方法或poll方法可以获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果
使用案例
询价应用:向不同电商平台询价,并保存价格
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<Integer> future1 = executorService.submit(() -> getPriceByS1());
Future<Integer> future2 = executorService.submit(() -> getPriceByS2());
Future<Integer> future3 = executorService.submit(() -> getPriceByS3());
executorService.execute(()-> {
try {
save(future1.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executorService.execute(()-> {
try {
save(future2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executorService.execute(()-> {
try {
save(future3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executorService.shutdown();
}
private static Integer getPriceByS1() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(5000);
System.out.println("电商S1询价信息1200");
return 1200;
}
private static void save(Integer r) {
System.out.println("保存询价结果:"+r);
}
如果获取电商S1报价的耗时很长,那么即便获取电商S2报价的耗时很短,也无法让保存S2报价的操作先执行,因为这个主线程都阻塞 在了f1.get()操作上。
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executorService);
completionService.submit(() -> getPriceByS1());
completionService.submit(() -> getPriceByS2());
completionService.submit(() -> getPriceByS3());
for (int i = 0; i < 3; i++) {
Integer integer = completionService.take().get();
executorService.execute(()->save(integer));
}
executorService.shutdown();
批量提交异步任务
的时候建议你使用CompletionService。异步任务的执行结果有序化
。先执行完的先进入阻塞队列,利用这个特性,你可以轻松实现后续处理的有序性,避免无谓的等待,同时还可以快速实现诸如Forking Cluster这样的需求。线程池隔离
。CompletionService支持自己创建线程池,这种隔离性能避免几个特别耗时的任务拖垮整个应用的风险。CompletionService适用于一组非阻塞
的异步任务,futuretask可以用于异步任务
CompletionStage 是 Java 8 新增的接口,用于:异步执行中的阶段处理
场景:一个大的任务可以拆分成多个子任务,并且子任务之间有明显的先后顺序或者一个子任务依赖另一个子任务完成的结果时,那么 CompletionStage 是一个不错的选择
CompletionStage 就是实现了将一个大任务分成若个子任务,这些子任务基于一定的并行、串行组合形成任务的不同阶段
CompletionStage 接口中方法很多,但不难发现有规律可循:基本都是由 then、apply、async、accept、run、combine、both、either、after、compose、when、handle 等关键词组合而成。这些关键字可以理解如下:
CompletableFuture是Future接口的扩展和增强
。CompletableFuture实现了Future接
口,并在此基础上进行了丰富地扩展,完美地弥补了Future上述的种种问题。更为重要的是,同时CompletableFuture 实现了 CompletionStage 接口,使CompletableFuture实现了对任务的编排能力
。借助这项能力,我们可以轻松地组织不同任务的运行顺序、规则以及方式。从某种程度上说,这项能力是它的核心能力。而在以往,虽然通过CountDownLatch等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力且难以维护。
jdk8 API文档:https://docs.oracle.com/javase/8/docs/api/
应用场景
描述依赖关系:
CompletableFuture.supplyAsync(() -> 100).thenApply(x -> x * 100);
CompletableFuture.supplyAsync(() -> 100).thenCompose(x -> CompletableFuture.supplyAsync(() -> x * 100));
描述and聚合关系:
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 100);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 200);
Integer result = future1.thenCombine(future2, Integer::sum).get();
future1.thenAcceptBoth(future2, (x, y) -> System.out.println(x + y));
future1.runAfterBoth(future2, () -> System.out.println(Thread.currentThread().getName()));
描述or聚合关系:
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
Thread.sleep(1000);
return 100;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
Thread.sleep(500);
return 200;
});
System.out.println(future1.applyToEither(future2, x -> x * 100).get());
异步操作
CompletableFuture 提供了四个静态方法来创建一个异步操作:
public static CompletableFuture runAsync(Runnable runnable)
public static CompletableFuture runAsync(Runnable runnable, Executor executor)
public static CompletableFuture supplyAsync(Supplier supplier)
public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)
这四个方法区别在于:
获取结果
join&get
join()和get()方法都是用来获取CompletableFuture异步之后的返回值。join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)
结果处理
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是这几个的方法:
public CompletableFuture whenComplete(BiConsumer action)
public CompletableFuture whenCompleteAsync(BiConsumer action)
public CompletableFuture whenCompleteAsync(BiConsumer action, Executor executor)
public CompletableFuture exceptionally(Function fn)
结果转换
所谓结果转换,就是将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果
thenApply
thenApply 接收一个函数作为参数,使用该函数处理上一个CompletableFuture 调用的结果,并返回一个具有处理结果的Future对象
thenCompose
thenCompose 的参数为一个返回 CompletableFuture 实例的函数,该函数的参数是先前计算步骤的结果
thenApply 和 thenCompose的区别
结果消费
与结果处理和结果转换系列函数返回一个新的 CompletableFuture 不同,结果消费系列函数只对结果执行Action,而不返回新的计算值。
根据对结果的处理方式,结果消费函数又分为:
thenAccept
通过观察该系列函数的参数类型可知,它们是函数式接口Consumer,这个接口只有输入,没有返回值。
thenAcceptBoth
thenAcceptBoth 函数的作用是,当两个 CompletionStage 都正常完成计算的时候,就会执行提供的action消费两个异步的结果
thenRun
thenRun 也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun 会在上一阶段 CompletableFuture 计算完成的时候执行一个Runnable,Runnable并不使用该CompletableFuture 计算的结果
结果组合
thenCombine
thenCombine 方法,合并两个线程任务的结果,并进一步处理。
任务交互
所谓线程交互,是指将两个线程任务获取结果的速度相比较,按一定的规则进行下一步处理。
applyToEither
两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的转化操作。
acceptEither
两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的消费操作。
runAfterEither
两个线程任务相比较,有任何一个执行完成,就进行下一步操作,不关心运行
runAfterBoth
两个线程任务相比较,两个全部执行完成,才进行下一步操作,不关心运行结果结果。
并行执行
anyOf
anyOf 方法的参数是多个给定的 CompletableFuture,当其中的任何一个完成时,方法返回
这个 CompletableFuture。
allOf
allOf方法用来实现多 CompletableFuture 的同时返回。
著名数学家华罗庚先生在《统筹方法》这篇文章里介绍了一个烧水泡茶的例子,文中提到最优的工序应该是下面这样:
对于烧水泡茶这个程序,一种最优的分工方案:用两个线程 T1 和 T2 来完成烧水泡茶程序,T1 负责洗水壶、烧开水、泡茶这三道工序,T2 负责洗茶壶、洗茶杯、拿茶叶三道工序,其中 T1 在执行泡茶这道工序时需要等待 T2 完成拿茶叶的工序
public class T2 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("T2:洗茶壶...");
TimeUnit.SECONDS.sleep(1);
System.out.println("T2:洗茶杯...");
TimeUnit.SECONDS.sleep(2);
System.out.println("T2:拿茶叶...");
TimeUnit.SECONDS.sleep(1);
return "龙井";
}
}
public class T1 implements Callable<String> {
private FutureTask<String> t2;
public T1(FutureTask<String> t2) {
this.t2 = t2;
}
@Override
public String call() throws Exception {
System.out.println("T1:洗水壶...");
TimeUnit.SECONDS.sleep(1);
System.out.println("T1:烧开水...");
TimeUnit.SECONDS.sleep(15);
// 获取 T2 的茶叶
String chaYe = t2.get();
System.out.println("T1:拿到茶叶:" + chaYe);
System.out.println("T1:泡茶...");
return "上茶:" + chaYe;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task2 = new FutureTask<>(new T2());
FutureTask<String> task1 = new FutureTask<>(new T1(task2));
new Thread(task1).start();
new Thread(task2).start();
System.out.println(task1.get());
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//任务1:洗水壶->烧开水
CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {
System.out.println("T1:洗水壶...");
sleep(1, TimeUnit.SECONDS);
System.out.println("T1:烧开水...");
sleep(15, TimeUnit.SECONDS);
});
//任务2:洗茶壶->洗茶杯->拿茶叶
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
System.out.println("T2:洗茶壶...");
sleep(1, TimeUnit.SECONDS);
System.out.println("T2:洗茶杯...");
sleep(2, TimeUnit.SECONDS);
System.out.println("T2:拿茶叶...");
sleep(1, TimeUnit.SECONDS);
return "龙井";
});
//任务3:任务1和任务2完成后执行:泡茶
CompletableFuture<String> f3 = task1.thenCombine(task2, (x, y) -> {
System.out.println("T1:拿到茶叶:" + y);
System.out.println("T1:泡茶...");
return "上茶:" + y;
});
//等待任务3执行结果
System.out.println(f3.join());
}
static void sleep(int t, TimeUnit u) {
try {
u.sleep(t);
} catch (InterruptedException e) {
}
}
Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级)。基于Disruptor开发的系统单线程能支撑每秒600万订单,2010年在QCon演讲后,获得了业界关注。2011年,企业应用软件专家Martin Fowler专门撰写长文介绍。同年它还获得了Oracle官方的Duke大奖。目前,包括Apache Storm、Camel、Log4j 2在内的很多知名项目都应用了Disruptor以获取高性能。注意,这里所说的队列是系统内部的内存队列,而不是Kafka这样的分布式队列。
Github:https://github.com/LMAX-Exchange/disruptor
Disruptor实现了队列的功能并且是一个有界队列,可以用于生产者-消费者模型。
特点:
无锁,高并发,使用环形buffer,直接覆盖(不用清除)旧的数据,降低GC频率
实现了基于事件的生产者消费者模式(观察者模式)
一个线程中每秒处理600w订单
速度最快的MQ,单机
性能极高,无锁cas,单机支持高并发
Disruptor的设计方案
Disruptor通过以下设计来解决队列速度慢的问题:
RingBuffer数据结构
使用RingBuffer来作为队列的数据结构,RingBuffer就是一个可自定义大小的环形数组。除数组外还有一个序列号(sequence),用以指向下一个可用的元素,供生产者与消费者使用。原理图如下所示:
采用数组实现,没有首尾指针
对比concurrentLinkedQueue,用数组实现的速度更快
Disruptor要求设置数组长度为2的n次幂。在知道索引(index)下标的情况下,存与取数组上的元素时间复杂度只有O(1),而这个index我们可以通过序列号与数组的长度取模来计算得出,index=sequence % entries.length。也可以用位运算来计算效率更高,此时array.length必须是2的幂次方,index=sequece&(entries.length-1)
当所有位置都放满了,再放下一个时,就会把0号位置覆盖掉
假如长度为8,当添加到第12个元素的时候在哪个序号上呢?用12%8决定
当Buffer被填满的时候到底是覆盖还是等待,由Producer决定
长度设定为2的n次幂,利于二进制计算,例如:12%8=12&(8-1) pos=num&(size - 1)
当需要覆盖数据时,会执行一个策略,Disruptor给提供多种策略,比较常用的:
BlockingWaitStrategy策略,常见且默认的等待策略,当这个队列里满了,不执行覆盖,而是阻塞等待。使用ReentrantLock+Condition实现阻塞,最节省cpu,但高并发场景下性能最差。适合CPU资源紧缺,吞吐量和延迟并不重要的场景
SleepingWaitStrategy策略,会在循环中不断等待数据
。先进行自旋等待如果不成功,则使用Thread.yield()让出CPU,并最终使用LockSupport.parkNanos(1L)进行线程休眠,以确保不占用太多的CPU资源。因此这个策略会产生比较高的平均延时。典型的应用场景就是异步日志。(常用)
YieldingWaitStrategy策略,这个策略用于低延时的场合
。消费者线程会不断循环监控缓冲区变化,在循环内部使用Thread.yield()让出CPU给别的线程执行时间。如果需要一个高性能的系统,并且对延时比较有严格的要求,可以考虑这种策略。(常用)
BusySpinWaitStrategy策略: 采用死循环,消费者线程会尽最大努力监控缓冲区的变化。对延时非常苛刻的场景使用,cpu核数必须大于消费者线程数量。推荐在线程绑定到固定的CPU的场景下使用
public Disruptor(EventFactory<T> eventFactory, int ringBufferSize, ThreadFactory threadFactory, ProducerType producerType, WaitStrategy waitStrategy) {
this(RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy), executor);
}
事件发布模板(生产者)
public class OrderEventProducer {
//事件队列
private RingBuffer<OrderEvent> ringBuffer;
public OrderEventProducer(RingBuffer<OrderEvent> ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void onData(long value,String name){
// 获取事件队列 的下一个槽
long sequence = ringBuffer.next();
try {
//获取消息(事件)
OrderEvent orderEvent = ringBuffer.get(sequence);
// 写入消息数据
orderEvent.setName(name);
orderEvent.setValue(value);
} catch (Exception e) {
// TODO 异常处理
e.printStackTrace();
} finally {
System.out.println("生产者"+ Thread.currentThread().getName()
+"发送数据value:"+value+",name:"+name);
//发布事件
ringBuffer.publish(sequence);
}
}
}
消息体
/**
* 消息载体(事件)
*/
@Data
public class OrderEvent {
private long value;
private String name;
}
/**
* 事件工厂
*/
public class OrderEventFactory implements EventFactory<OrderEvent> {
@Override
public OrderEvent newInstance() {
return new OrderEvent();
}
}
/**
* 消费者
*/
public class OrderEventHandler implements EventHandler<OrderEvent>, WorkHandler<OrderEvent> {
@Override
public void onEvent(OrderEvent event, long sequence, boolean endOfBatch) throws Exception {
// TODO 消费逻辑
System.out.println("消费者"+ Thread.currentThread().getName()
+"获取数据value:"+ event.getValue()+",name:"+event.getName());
}
@Override
public void onEvent(OrderEvent event) throws Exception {
// TODO 消费逻辑
System.out.println("消费者"+ Thread.currentThread().getName()
+"获取数据value:"+ event.getValue()+",name:"+event.getName());
}
}
单生产者多消费者模式
public static void main(String[] args) {
//创建disruptor
Disruptor<OrderEvent> disruptor = new Disruptor<OrderEvent>(
OrderEvent::new,
1024*1024,
Executors.defaultThreadFactory(),
ProducerType.SINGLE,
new YieldingWaitStrategy()
);
//设置消费者用于处理RingBuffer的事件
//如果消费者是多个,只需要在调用 handleEventsWith 方法时将多个消费者传递进去。(广播)
// disruptor.handleEventsWith(new OrderEventHandler()); //类似mq的cluster
//设置多消费者,消息会被重复消费
disruptor.handleEventsWith(new OrderEventHandler(),new OrderEventHandler()); //类似mq的broadcast
//启动disruptor
disruptor.start();
//创建ringbuffer容器
RingBuffer<OrderEvent> ringBuffer = disruptor.getRingBuffer();
//创建生产者
OrderEventProducer producer = new OrderEventProducer(ringBuffer);
for (int i = 0; i < 100; i++) {
producer.onData(i,"yuyang"+i);
}
disruptor.shutdown();
}
多生产者多消费者模式
public static void main(String[] args) {
//创建disruptor
Disruptor<OrderEvent> disruptor = new Disruptor<OrderEvent>(
OrderEvent::new,
1024*1024,
Executors.defaultThreadFactory(),
ProducerType.SINGLE,
new YieldingWaitStrategy()
);
//设置多消费者,消费者要实现WorkHandler接口,一条消息只会被一个消费者消费
disruptor.handleEventsWithWorkerPool(new OrderEventHandler(), new OrderEventHandler());
//启动disruptor
disruptor.start();
//创建ringbuffer容器
RingBuffer<OrderEvent> ringBuffer = disruptor.getRingBuffer();
new Thread(()->{
//创建生产者
OrderEventProducer producer = new OrderEventProducer(ringBuffer);
for (int i = 0; i < 100; i++) {
producer.onData(i,"yuyang"+i);
}
},"producer1").start();
new Thread(()->{
//创建生产者
OrderEventProducer producer = new OrderEventProducer(ringBuffer);
for (int i = 0; i < 100; i++) {
producer.onData(i,"yuyang"+i);
}
},"producer2").start();
disruptor.shutdown();
}
消费者优先级模式
在实际场景中,通常会因为业务逻辑而形成一条消费链。比如一个消息必须由 消费者A ->消费者B -> 消费者C 的顺序依次进行消费。在配置消费者时,可以通过 .then 方法去实现顺序消费
public static void main(String[] args) {
//创建disruptor
Disruptor<OrderEvent> disruptor = new Disruptor<OrderEvent>(
OrderEvent::new,
1024*1024,
Executors.defaultThreadFactory(),
ProducerType.SINGLE, //单生产者
new YieldingWaitStrategy() //等待策略
);
//按消费者优先级消费 消费者A -> (消费者B 消费者C) -> 消费者D
disruptor.handleEventsWith(new OrderEventHandler())
.thenHandleEventsWithWorkerPool(new OrderEventHandler(), new OrderEventHandler())
.then(new OrderEventHandler());
//启动disruptor
disruptor.start();
//创建ringbuffer容器
RingBuffer<OrderEvent> ringBuffer = disruptor.getRingBuffer();
new Thread(()->{
//创建生产者
OrderEventProducer producer = new OrderEventProducer(ringBuffer);
for (int i = 0; i < 100; i++) {
producer.onData(i,"yuyang"+i);
}
},"producer1").start();
disruptor.shutdown();
}
===============================
什么是进程?
OS操作系统分配CPU资源的基础单位为进程
OS操作系统调度(执行)CPU资源的基础单位为线程
单核CPU设定多线程是否有意义?
线程数是不是设置的越大越好?
线程切换也要消耗资源
工作线程数(线程池中线程数量)设置多少合适?
主要是看等待时间与计算时间的比率和CPU的利用率了(默认情况下CPU的利用率是100%)
JAVA的6中线程状态:
停止线程的方法为什么不建议使用stop()?
会导致数据不一致
上锁的本质?
将并发操作序列化
悲观锁和乐观锁区别
悲观锁认为这个操作会被其他线程打断 synchronized
乐观锁认为这个操作不会被其他线程打断 CAS操作
CAS=compare And Set/Swap/Exchange