Future & CompletionService & CompletetableFuture
FutureTask
使用
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的局限性
从本质上说,Future表示一个异步计算的结果。它提供了isDone()来检测计算是否已经完成,并且在计算结束后,可以通过get()方法来获取计算结果。在异步计算中,Future确实是个非常优秀的接口。但是,它的本身也确实存在着许多限制:
- 并发执行多任务:Future只提供了get()方法来获取结果,并且是阻塞的。所以,除了等待你别无他法;
- 无法对多个任务进行链式调用:如果你希望在计算任务完成后执行特定动作,比如发邮件,但Future却没有提供这样的能力;
- 无法组合多个任务:如果你运行了10个任务,并期望在它们全部执行结束后执行特定动作,那么在Future中这是无能为力的;
- 没有异常处理:Future接口中没有关于异常处理的方法;
CompletionService
Callable+Future 可以实现多个task并行执行,但是如果遇到前面的task执行较慢时需要阻塞等待前面的task执行完后面task才能取得结果
。而CompletionService的主要功能就是一边生成任务,一边获取任务的返回值
。让两件事分开执行,任务之间不会互相阻塞,可以实现先执行完的先取结果,不再依赖任务顺序了
。(CompletionService没有任务的顺序,而futuretask需要依赖任务顺序,会产生阻塞)
CompletionService原理
内部通过阻塞队列+FutureTask,实现了任务先完成可优先获取到,即结果按照完成先后顺序排序,内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,通过调用它的take方法或poll方法可以获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果
使用案例
询价应用:向不同电商平台询价,并保存价格
- 采用“ThreadPoolExecutor+Future”的方案:异步执行询价然后再保存
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()操作上。
- 使用CompletionService实现先获取的报价先保存到数据库
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。
CompletionService将线程池Executor和阻塞队列BlockingQueue的功能融合在了一起,能够让批量异步任务的管理更简单。 - CompletionService能够让
异步任务的执行结果有序化
。先执行完的先进入阻塞队列,利用这个特性,你可以轻松实现后续处理的有序性,避免无谓的等待,同时还可以快速实现诸如Forking Cluster这样的需求。 线程池隔离
。CompletionService支持自己创建线程池,这种隔离性能避免几个特别耗时的任务拖垮整个应用的风险。
CompletionService适用于一组非阻塞
的异步任务,futuretask可以用于异步任务
CompletionStage
CompletionStage 是 Java 8 新增的接口,用于:异步执行中的阶段处理
场景:一个大的任务可以拆分成多个子任务,并且子任务之间有明显的先后顺序或者一个子任务依赖另一个子任务完成的结果时,那么 CompletionStage 是一个不错的选择
CompletionStage 就是实现了将一个大任务分成若个子任务,这些子任务基于一定的并行、串行组合形成任务的不同阶段
CompletionStage 接口中方法很多,但不难发现有规律可循:基本都是由 then、apply、async、accept、run、combine、both、either、after、compose、when、handle 等关键词组合而成。这些关键字可以理解如下:
- then:表示阶段先后顺序,即一个阶段等待另一个阶段完成
- apply:和 Function 一样,表示消费一个参数并提供一个结果
- async:异步标志,即阶段任务的执行相对于当前线程是同步还是异步
- accept:和 Consumer 一样,表示消费一个参数
- run:既不消费也不铲除,同 Runnable 接口含义
- combine:合并两个阶段结果并返回新的阶段
- both:表示二者条件都成立再做其它事
- either:表示二者之一条件成立再做其它事,对应 both
- after:表先后顺序,一个任务发生在另一个任务之后,和 then 相似
- compose:表示根据已有结果生成新的结果,同 Function,细看 compose 的参数和 - thenApply 有区别,具体区别再下面陈述
- when:等同于 whenComplete,当前阶段正常完成或异常完成时执行 BiConsumer 动作
- handle:当前阶段正常完成或异常完成后触发一个 BiFunction 动作
CompletableFuture使用详解
CompletableFuture是Future接口的扩展和增强
。CompletableFuture实现了Future接
口,并在此基础上进行了丰富地扩展,完美地弥补了Future上述的种种问题。更为重要的是,同时CompletableFuture 实现了 CompletionStage 接口,使CompletableFuture实现了对任务的编排能力
。借助这项能力,我们可以轻松地组织不同任务的运行顺序、规则以及方式。从某种程度上说,这项能力是它的核心能力。而在以往,虽然通过CountDownLatch等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力且难以维护。
jdk8 API文档:https://docs.oracle.com/javase/8/docs/api/
应用场景
描述依赖关系:
- thenApply() 把前面异步任务的结果,交给后面的Function
CompletableFuture.supplyAsync(() -> 100).thenApply(x -> x * 100);
- thenCompose()用来连接两个有依赖关系的任务,结果由第二个任务返回
CompletableFuture.supplyAsync(() -> 100).thenCompose(x -> CompletableFuture.supplyAsync(() -> x * 100));
描述and聚合关系:
- thenCombine:任务合并,有返回值
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 100);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 200);
Integer result = future1.thenCombine(future2, Integer::sum).get();
- thenAccepetBoth:两个任务执行完成后,将结果交给thenAccepetBoth消耗,无返回值。
future1.thenAcceptBoth(future2, (x, y) -> System.out.println(x + y));
- runAfterBoth:两个任务都执行完成后,执行下一步操作(Runnable)。
future1.runAfterBoth(future2, () -> System.out.println(Thread.currentThread().getName()));
描述or聚合关系:
- applyToEither:两个任务谁执行的快,就使用那一个结果,有返回值。
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());
- acceptEither: 两个任务谁执行的快,就消耗那一个结果,无返回值。
- runAfterEither: 任意一个任务执行完成,进行下一步操作(Runnable)。
并行执行:
CompletableFuture类也提供了anyOf()和allOf()用于支持多个CompletableFuture并行执行 - allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
- anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture
异步操作
CompletableFuture 提供了四个静态方法来创建一个异步操作:
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
这四个方法区别在于:
- runAsync 方法以Runnable函数式接口类型为参数,没有返回结果,supplyAsync方法Supplier函数式接口类型为参数,返回结果类型为U;Supplier 接口的 get() 方法是有返回值的(会阻塞)
- 没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
默认情况下 CompletableFuture 会使用公共的 ForkJoinPool 线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置 ForkJoinPool 线程池的线程数)。如果所有 CompletableFuture 共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能,强烈建议要根据不同的业务类型创建不同的线程池,以避免互相干扰
获取结果
join&get
join()和get()方法都是用来获取CompletableFuture异步之后的返回值。join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)
结果处理
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是这几个的方法:
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super T hrowable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
- Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。
- 方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
- 这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常
结果转换
所谓结果转换,就是将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果
thenApply
thenApply 接收一个函数作为参数,使用该函数处理上一个CompletableFuture 调用的结果,并返回一个具有处理结果的Future对象
thenCompose
thenCompose 的参数为一个返回 CompletableFuture 实例的函数,该函数的参数是先前计算步骤的结果
thenApply 和 thenCompose的区别
- thenApply 转换的是泛型中的类型,返回的是同一个CompletableFuture;
- thenCompose 将内部的 CompletableFuture 调用展开来并使用上一个CompletableFutre 调用的结果在下一步的 CompletableFuture 调用中进行运算,是生成一个新的CompletableFuture。
结果消费
与结果处理和结果转换系列函数返回一个新的 CompletableFuture 不同,结果消费系列函数只对结果执行Action,而不返回新的计算值。
根据对结果的处理方式,结果消费函数又分为:
- thenAccept系列:对单个结果进行消费
- thenAcceptBoth系列:对两个结果进行消费
- thenRun系列:不关心结果,只对结果执行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 的同时返回。
CompletableFuture常用方法总结
著名数学家华罗庚先生在《统筹方法》这篇文章里介绍了一个烧水泡茶的例子,文中提到最优的工序应该是下面这样:
对于烧水泡茶这个程序,一种最优的分工方案:用两个线程 T1 和 T2 来完成烧水泡茶程序,T1 负责洗水壶、烧开水、泡茶这三道工序,T2 负责洗茶壶、洗茶杯、拿茶叶三道工序,其中 T1 在执行泡茶这道工序时需要等待 T2 完成拿茶叶的工序
- 基于 Future 的实现
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());
}
- 基于 CompletableFuture 的实现
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
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通过以下设计来解决队列速度慢的问题:
- 环形数组结构
为了避免垃圾回收,采用数组而非链表。同时,数组对处理器的缓存机制更加友好(空间局部性原理)。 - 元素位置定位
数组长度2^n,通过位运算,加快定位的速度。下标采取递增的形式。不用担心index溢出的
问题。index是long类型,即使100万QPS的处理速度,也需要30万年才能用完。 - 无锁设计
每个生产者或者消费者线程,会先申请可以操作的元素在数组中的位置,申请到之后,直接在该位置写入或者读取数据。- 利用缓存行填充解决了伪共享的问题
- 实现了基于事件驱动的生产者消费者模型(观察者模式)
消费者时刻关注着队列里有没有消息,一旦有新消息产生,消费者线程就会立刻把它消费
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的场景下使用
Disruptor构造器
public Disruptor(EventFactory<T> eventFactory, int ringBufferSize, ThreadFactory threadFactory, ProducerType producerType, WaitStrategy waitStrategy) {
this(RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy), executor);
}
- EventFactory:创建事件(任务)的工厂类。
- ringBufferSize:容器的长度。
- ThreadFactory :用于创建执行任务的线程。
- ProductType:生产者类型:单生产者、多生产者。
- WaitStrategy:等待策略。
Disruptor核心概念
- RingBuffer(环形缓冲区):基于数组的内存级别缓存,是创建sequencer(序号)与定义WaitStrategy(拒绝策略)的入口。
- Disruptor(总体执行入口):对RingBuffer的封装,持有RingBuffer、消费者线程池Executor、消费之集合ConsumerRepository等引用。
- Sequence(序号分配器):对RingBuffer中的元素进行序号标记,通过顺序递增的方式来管理进行交换的数据(事件/Event),一个Sequence可以跟踪标识某个事件的处理进度,同时还能消除伪共享。
- Sequencer(数据传输器):Sequencer里面包含了Sequence,是Disruptor的核心,Sequencer有两个实现类:SingleProducerSequencer(单生产者实现)、
MultiProducerSequencer(多生产者实现),Sequencer主要作用是实现生产者和消费者之间快速、正确传递数据的并发算法 - SequenceBarrier(消费者屏障):用于控制RingBuffer的Producer和Consumer之间的平衡关系,并且决定了Consumer是否还有可处理的事件的逻辑。
- WaitStrategy(消费者等待策略):决定了消费者如何等待生产者将Event生产进Disruptor,WaitStrategy有多种实现策略
- Event:从生产者到消费者过程中所处理的数据单元,Event由使用者自定义。
- EventHandler:由用户自定义实现,就是我们写消费者逻辑的地方,代表了Disruptor中的一个消费者的接口。
- EventProcessor:这是个事件处理器接口,实现了Runnable,处理主要事件循环,处理Event,拥有消费者的Sequence
Disruptor开发步骤
事件发布模板(生产者)
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中线程状态:
- NEW : 线程刚刚创建,还没有启动
- RUNNABLE : 可运行状态,由线程调度器可以安排执行
- 包括READY和RUNNING两种细分状态
- WAITING: 等待被唤醒
- TIMED WAITING: 隔一段时间后自动唤醒
- BLOCKED: 被阻塞,正在等待锁
- TERMINATED: 线程结束
停止线程的方法为什么不建议使用stop()?
会导致数据不一致
this溢出
上锁的本质?
将并发操作序列化
悲观锁和乐观锁区别
悲观锁认为这个操作会被其他线程打断 synchronized
乐观锁认为这个操作不会被其他线程打断 CAS操作
CAS=compare And Set/Swap/Exchange
AutomicInteger和LongAdder