并发编程十四:Future、CompletionService、CompletableFuture和Disruptor队列实战

Future、CompletionService、CompletableFuture和Disruptor队列实战

Future

直接继承Thread或者实现Runnable接口都可以创建线程,但是这两种方法都有缺陷:不能返回一个返回值 ,不能抛出 checked Exception
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。 必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future实际采用FutureTask实现,该对象相当于是消费者和生产者的桥梁,消费者通过 FutureTask 存储任务的处理结果,更新任务的状态:未开始、正在处理、已完成等。而生产者拿到的 FutureTask 被转型为 Future 接口,可以阻塞式获取任务的处理结果,非阻塞式获取任 务处理状态。 FutureTask既可以被当做Runnable来执行,也可以被当做Future来获取Callable的返回结果。

案例

@Slf4j
public class FutureTaskDemo {
	private final static ExecutorService pool = Executors.newFixedThreadPool(3);
	//2个A组件、1个B组件、3个C组件 组成一个商品  通过查询各个组件数量计算出能够组成多少商品
	//任意两个种组件 组成需要5秒
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		FutureTask<Integer> taskA = new FutureTask<Integer>(()->{
			log.info("搜索A组件需要3秒");
			Thread.sleep(3000);
			return 2;
		});FutureTask<Integer> taskB = new FutureTask<Integer>(()->{
			log.info("搜索B组件需要5秒");
			Thread.sleep(5000);
			return 1;
		});FutureTask<Integer> taskC = new FutureTask<Integer>(()->{
			log.info("搜索C组件需要1秒");
			Thread.sleep(1000);
			return 3;
		});
		pool.execute(taskA);
		pool.execute(taskB);
		pool.execute(taskC);
		Integer A = taskA.get();
		Integer B = taskB.get();
		Integer C = taskC.get();
		log.info("组件A:"+A+"个;组件B:"+B+"个;组件C:"+C+"个\n计算得出商品有1个");
		pool.shutdown();
		log.info("任意两种拼接成半成品需要5秒");
		Thread.sleep(5000);
		log.info("半成品和剩下的组件拼接需要2秒");
		Thread.sleep(2000);
		log.info("商品拼接完成");
	}
}

在这里插入图片描述
查询耗时5秒。组件拼接耗时7秒。总耗时12秒
如果上述案例串行执行的耗时就是16秒、
Future 注意事项

  • 当 for 循环批量获取 Future 的结果时容易 block,get 方法调用时应使用 timeout 限制
  • Future 的生命周期不能后退。一旦完成了任务,它就永久停在了“已完成”的状 态,不能从头再来

Future的局限性
从本质上说,Future表示一个异步计算的结果。它提供了isDone()来检测计算是否已经完成, 并且在计算结束后,可以通过get()方法来获取计算结果。
在异步计算中,Future确实是个非常 优秀的接口。但是,它的本身也确实存在着许多限制:

  • 并发执行多任务:Future只提供了get()方法来获取结果,并且是阻塞的。所以,除了等待你别无他法;
  • 无法对多个任务进行链式调用:如果你希望在计算任务完成后执行特定动作,比如发邮件,但Future却没有提供这样的能力;
  • 无法组合多个任务:如果你运行了10个任务,并期望在它们全部执行结束后执行特定动作,那么在Future中这是无能为力的;
  • 没有异常处理:Future接口中没有关于异常处理的方法;

CompletionService

Callable+Future 可以实现多个task并行执行,但是如果遇到前面的task执行较慢时需要阻塞等待前面的task执行完后面task才能取得结果。
例如上面的案例,因为B查询慢需要5秒,AC查询相对较快,但是要等待B的查询才能做拼接。
而CompletionService的主要功能就是一 边生成任务,一边获取任务的返回值。让两件事分开执行,任务之间不会互相阻塞,可以实现先执行完的先取结果,不再依赖任务顺序了。

案例

@Slf4j
public class CompletionServiceDemo {
	private final static ExecutorService pool = Executors.newFixedThreadPool(3);
	//2个A组件、1个B组件、3个C组件 组成一个商品  通过查询各个组件数量计算出能够组成多少商品
	//任意两个种组件 组成需要5秒
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		//CompletionService 是一个接口 ExecutorCompletionService该接口的实现类
		CompletionService<Integer> cs = new ExecutorCompletionService<>(pool);
		cs.submit(()->{
			log.info("搜索A组件需要3秒");
			Thread.sleep(3000);
			return 2;
		});
		cs.submit(()->{
			log.info("搜索B组件需要5秒");
			Thread.sleep(5000);
			return 1;
		});
		cs.submit(()->{
			log.info("搜索C组件需要1秒");
			Thread.sleep(1000);
			return 3;
		});
		Integer aa = cs.take().get();
		Integer bb = cs.take().get();
		log.info("任意两种拼接成半成品需要5秒");
		Thread.sleep(5000);
		Integer cc = cs.take().get();
		log.info("半成品和剩下的组件拼接需要2秒");
		Thread.sleep(2000);
		log.info("商品拼接完成");
		pool.shutdown();
	}
}

在这里插入图片描述
耗时10秒 ,比用Future快了2秒,因为查询B的的时候AC先查询完了,那么AC就可以先拼接,不会被B阻塞。

CompletionService原理

内部通过阻塞队列+FutureTask,实现了任务先完成可优先获取到,即结果按照完成先后顺序排序,内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,通过调用它的 take方法或poll方法可以获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果

应用场景

  • 当需要批量提交异步任务的时候建议使用CompletionService。 CompletionService将线程池Executor和阻塞队列BlockingQueue的功能融合在了一 起,能够让批量异步任务的管理更简单。
  • CompletionService能够让异步任务的执行结果有序化。先执行完的先进入阻塞队列,利用这个特性,你可以轻松实现后续处理的有序性,避免无谓的等待,同时还可以 快速实现诸如Forking Cluster这样的需求。
  • 线程池隔离。CompletionService支持自己创建线程池,这种隔离性能避免几个特别耗时的任务拖垮整个应用的风险。

CompletableFuture

简单的任务,用Future获取结果还好,但我们并行提交的多个异步任务,往往并不是独立 的,很多时候业务逻辑处理存在串行[依赖]、并行、聚合的关系。如果要我们手动用 Fueture 实现,是非常麻烦的。
CompletableFuture是Future接口的扩展和增强。CompletableFuture实现了Future接 口,并在此基础上进行了丰富地扩展,完美地弥补了Future上述的种种问题。更为重要的是, CompletableFuture实现了对任务的编排能力。借助这项能力,我们可以轻松地组织不同任 务的运行顺序、规则以及方式。从某种程度上说,这项能力是它的核心能力。而在以往,虽然 通过CountDownLatch等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精 力且难以维护。

应用场景

描述依赖关系:

  • thenApply() 把前面异步任务的结果,交给后面的Function
  • thenCompose()用来连接两个有依赖关系的任务,结果由第二个任务返回

描述and聚合关系:

  1. thenCombine:任务合并,有返回值
  2. thenAccepetBoth:两个任务执行完成后,将结果交给thenAccepetBoth消耗, 无返回值。
  3. runAfterBoth:两个任务都执行完成后,执行下一步操作(Runnable)。

描述or聚合关系:

  1. applyToEither:两个任务谁执行的快,就使用那一个结果,有返回值。
  2. acceptEither: 两个任务谁执行的快,就消耗那一个结果,无返回值。
  3. runAfterEither: 任意一个任务执行完成,进行下一步操作(Runnable)。

并行执行: CompletableFuture类自己也提供了anyOf()和allOf()用于支持多个CompletableFuture 并行执行

CompletableFuture使用
CompletableFuture 提供了四个静态方法来创建一个异步操作:

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor e xecutor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Exe cutor executor)

这四个方法区别在于:

  • runAsync 方法以Runnable函数式接口类型为参数,没有返回结果,supplyAsync 方法Supplier函数式接口类型为参数,返回结果类型为U;Supplier 接口的 get() 方法 是有返回值的(会阻塞)
  • 没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。如果所有 CompletableFuture 共享一个线程池,那么一旦有任务执行 一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程 饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的 线程池,以避免互相干扰
public class CompletableFutureDemoA {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture.runAsync(() -> {
			System.out.println("异步执行没有返回值的任务");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}, pool);

		CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
			System.out.println("异步执行有返回值的任务");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return 100;
		}, pool);
		Integer aa = future.get();
		// join和get都是获取返回值 区别在于get需要抛出异常 join不用
		//future.join();
		System.out.println(aa);
	}
}

在这里插入图片描述

案例

依赖关系案例
thenApply() 的使用

把前面异步任务的结果,交给后面的Function

@Slf4j
public class CompletableFutureDemoB {
	public static void main(String[] args)  {
		ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
			log.info("开始查询北京->广州的飞机票价格");
			sleep(2, TimeUnit.SECONDS);
			return 2000;
		}, pool);
		//把查询机票的结果传给第二个任务 第二个任务会阻塞直到拿到结果
		CompletableFuture<Double> cf1 = cf.thenApply((price) -> {
			log.info("开始查询该会员折扣");
			sleep(2, TimeUnit.SECONDS);
			log.info("查询折扣为9折");
			log.info("计算得出最终票价为" + price * 0.9 + "元");
			return price * 0.9;
		});
		log.info("票价为:"+cf1.join().toString());
		pool.shutdown();
	}

	static void sleep(int t, TimeUnit u){
		try {
			u.sleep(t);
		} catch (InterruptedException e) {
		}
	}
}

在这里插入图片描述
CompletableFuture可以使用链式调用,下面的案例均使用链式调用展示
thenCompose()的使用

ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture<Double> cf = CompletableFuture.supplyAsync(() -> {
			log.info("开始查询北京->广州的飞机票价格");
			sleep(2, TimeUnit.SECONDS);
			return 2000.0;
		}, pool).thenCompose((price)->{ //thenCompose 返回的是CompletableFuture
			return CompletableFuture.supplyAsync(()->{
				log.info("开始查询该会员折扣");
				sleep(2, TimeUnit.SECONDS);
				log.info("查询折扣为9折");
				log.info("计算得出最终票价为" + price * 0.9 + "元");
				return price * 0.9;
			});
		});
		log.info("票价为:"+cf.join().toString());
		pool.shutdown();

thenApply 和 thenCompose的区别

thenApply 转换的是泛型中的类型,返回的是同一个CompletableFuture;
thenCompose 将内部的 CompletableFuture 调用展开来并使用上一个CompletableFutre 调用的结果在下一步的CompletableFuture 调用中进行运算,是生成一个新的CompletableFuture。

and聚合关系
thenCombine
任务合并,有返回值

public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture<Double> cf = CompletableFuture.supplyAsync(() -> {
			log.info("开始查询北京->广州的飞机票价格");
			sleep(2, TimeUnit.SECONDS);
			return 2000.0;
		}, pool).thenCombine(CompletableFuture.supplyAsync(()->{ //第一个任务不会阻塞第二个任务
			log.info("开始查询广州->北京的飞机票价格");
			sleep(2, TimeUnit.SECONDS);
			return 2000.0;
		}),(p1,p2)->{ //得到第一个任务和第二个任务的返回值 
			log.info("计算往返票价");
			return p1+p2;
		});
		log.info("往返票价为:"+cf.join().toString());
		pool.shutdown();
	}

在这里插入图片描述

thenAccepetBoth
两个任务执行完成后,将结果交给thenAccepetBoth消耗, 无返回值。

public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture<Double> cf = CompletableFuture.supplyAsync(() -> {
			log.info("开始查询北京->广州的飞机票价格");
			sleep(2, TimeUnit.SECONDS);
			return 2000.0;
		}, pool);
		CompletableFuture<Double> cf1 = CompletableFuture.supplyAsync(() -> {
			log.info("开始查询广州->北京的飞机票价格");
			sleep(2, TimeUnit.SECONDS);
			return 2000.0;
		}, pool);

		cf.thenAcceptBoth(cf1, (p1, p2) -> {
			log.info("计算往返票价" + (p1 + p2));
		});
		pool.shutdown();
	}

runAfterBoth
两个任务都执行完成后,执行下一步操作(Runnable)

public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture<Double> cf = CompletableFuture.supplyAsync(() -> {
			log.info("开始查询北京->广州的飞机票价格");
			sleep(2, TimeUnit.SECONDS);
			return 2000.0;
		}, pool);
		CompletableFuture<Double> cf1 = CompletableFuture.supplyAsync(() -> {
			log.info("开始查询广州->北京的飞机票价格");
			sleep(2, TimeUnit.SECONDS);
			return 2000.0;
		}, pool);
		//第二个参数是Runnable 类型的任务
		cf.runAfterBoth(cf1, () -> {
			log.info("往返票价都执行完成");
		});
		pool.shutdown();
	}

thenCombine、thenAccepetBoth、runAfterBoth 的区别

  • thenCombine 有返回值
  • thenAccepetBoth 无返回值
  • runAfterBoth 执行的是Runnable任务

or聚合关系

applyToEither
两个任务谁执行的快,就使用那一个结果,有返回值

public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
			log.info("886公交车正在路上");
			sleep(5, TimeUnit.SECONDS);
			log.info("886公交车到了");
			return "886公交车";
		}, pool).applyToEither(CompletableFuture.supplyAsync(() -> {
			log.info("996公交车正在路上");
			sleep(3, TimeUnit.SECONDS);
			log.info("996公交车到了");
			return "996公交车";
		},pool),car->{
			return car;
		});
		log.info("我再等公交车回家");
		log.info(cf.join()+"先到了,我坐这趟公交回家");
		pool.shutdown();
	}

在这里插入图片描述
acceptEither
两个任务谁执行的快,就消耗那一个结果,无返回值。
这个就不举例了 和上面一样,只不过没有返回值

runAfterEither
任意一个任务执行完成,进行下一步操作(Runnable)

public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
			log.info("886公交车正在路上");
			sleep(5, TimeUnit.SECONDS);
			log.info("886公交车到了");
			return "886公交车";
		}, pool);

		CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
			log.info("996公交车正在路上");
			sleep(3, TimeUnit.SECONDS);
			log.info("996公交车到了");
			return "996公交车";
		}, pool);
		cf.runAfterEither(cf1,()->{
			log.info("哪辆公交先到了,我就坐那辆回家");
		});
		log.info("我在等公交车回家");
		pool.shutdown();
	}

异常处理
exceptionally

public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(10);
		CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
			log.info("886公交车正在路上");
			sleep(5, TimeUnit.SECONDS);
			log.info("886公交车到了");
			return "886公交车";
		}, pool).applyToEither(CompletableFuture.supplyAsync(() -> {
			log.info("996公交车正在路上");
			sleep(3, TimeUnit.SECONDS);
			log.info("996公交车到了");
			return "996公交车";
		},pool),car->{
			if (car.contains("996"))
			throw new RuntimeException(car+"撞树了");
			else
				return car;
		}).exceptionally(e -> {
			log.info(e.getMessage());
			log.info("重新打出租车");
			sleep(2, TimeUnit.SECONDS);
			log.info("出租车到了");
			return "出租车";
		});
		log.info("我等公交车回家");
		log.info(cf.join()+"到了,我坐这辆车回家");
		pool.shutdown();
	}

在这里插入图片描述
CompletableFuture常用方法总结
在这里插入图片描述

高性能队列Disruptor

简介

Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的 延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级)。基于Disruptor开发的系统单线程能支撑每秒600万订单,Disruptor实现了队列的功能并且是一个有界队列,可以用于生产者-消费者模型

juc下队列存在的问题:

  1. juc下的队列大部分采用加ReentrantLock锁方式保证线程安全。在稳定性要求特别高的系统 中,为了防止生产者速度过快,导致内存溢出,只能选择有界队列。
  2. 加锁的方式通常会严重影响性能。线程会因为竞争不到锁而被挂起,等待其他线程释放锁而唤醒,这个过程存在很大的开销,而且存在死锁的隐患。
  3. 有界队列通常采用数组实现。但是采用数组实现又会引发另外一个问题false sharing(伪共享)。

Disruptor的设计方案
Disruptor通过以下设计来解决队列速度慢的问题:

  • 环形数组结构
    为了避免垃圾回收,采用数组而非链表。同时,数组对处理器的缓存机制更加友好(空间局部性原理)。
  • 元素位置定位
    数组长度2^n,通过位运算,加快定位的速度。下标采取递增的形式。不用担心index溢出的 问题。index是long类型,即使100万QPS的处理速度,也需要30万年才能用完。
  • 无锁设计
    每个生产者或者消费者线程,会先申请可以操作的元素在数组中的位置,申请到之后,直接在该位置- 写入或者读取数据。
  • 利用缓存行填充解决了伪共享的问题
  • 实现了基于事件驱动的生产者消费者模型(观察者模式)
    消费者时刻关注着队列里有没有消息,一旦有新消息产生,消费者线程就会立刻把它消费

数据结构
使用RingBuffer来作为队列的数据结构,RingBuffer就是一个可自定义大小的环形数组。除数组外还有一个序列号(sequence),用以指向下一个可用的元素,供生产者与消费者使用。原理图如下所示:
在这里插入图片描述
Disruptor要求设置数组长度为2的n次幂。在知道索引(index)下标的情况下,存与取数组上的元素时间复杂度只有O(1),而这个index我们可以通过序列号与数组的长度取模来计算得出,index=sequence % entries.length。也可以用位运算来计算效率更高,此时array.length必须是2的幂次方,index=sequece&(entries.length-1)
当所有位置都放满了,再放下一个时,就会把0号位置覆盖掉
思考:能覆盖数据是否会导致数据丢失呢?
当需要覆盖数据时,会执行一个策略,Disruptor给提供多种策略,比较常用的:

  • BlockingWaitStrategy策略,常见且默认的等待策略,当这个队列里满了,不执行覆盖,而是阻塞等待。使用ReentrantLock+Condition实现阻塞,最节省cpu,但高并发场景下性能最差。适合CPU资源紧缺,吞吐量和延迟并不重要的场景
  • SleepingWaitStrategy策略,会在循环中不断等待数据。先进行自旋等待如果不成功,则使用Thread.yield()让出CPU,并最终使用LockSupport.parkNanos(1L)进行线程休眠,以确保不占用太多的CPU资源。因此这个策略会产生比较高的平均延时。典型的应用场景就是异步日志。
  • YieldingWaitStrategy策略,这个策略用于低延时的场合。消费者线程会不断循环监控缓冲区变化,在循环内部使用Thread.yield()让出CPU给别的线程执行时间。如果需要一个高性能的系统,并且对延时比较有严格的要求,可以考虑这种策略。
  • BusySpinWaitStrategy策略: 采用死循环,消费者线程会尽最大努力监控缓冲区的变化。对延时非常苛刻的场景使用,cpu核数必须大于消费者线程数量。推荐在线程绑定到固定的CPU的场景下使用

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的使用

引入依赖

	<!-- disruptor -->
	<dependency>
	    <groupId>com.lmax</groupId>
	    <artifactId>disruptor</artifactId>
	    <version>3.3.4</version>
    </dependency>

Disruptor构造器

public Disruptor(
        final EventFactory<T> eventFactory,
        final int ringBufferSize,
        final ThreadFactory threadFactory,
        final ProducerType producerType,
        final WaitStrategy waitStrategy)

EventFactory:创建事件(任务)的工厂类。
ringBufferSize:容器的长度。
ThreadFactory :用于创建执行任务的线程。
ProductType:生产者类型:单生产者、多生产者。
WaitStrategy:等待策略。

案例

创建事件载体和工厂

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DisruptorEvent {
	//事件存储的数据
	private Long value;
	private OrderEntity orderEntity;
}
//事件工厂
@Data
@AllArgsConstructor
public class DisruptorEventFactory implements EventFactory<DisruptorEvent> {
	@Override
	public DisruptorEvent newInstance() {
		return new DisruptorEvent();
	}
}
//订单实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderEntity {
	private Long orderId;
	private String orderName;
}

创建生产者

//事件生产者
public class EventProducer {
	RingBuffer<DisruptorEvent> ringBuffer;
	EventProducer(RingBuffer<DisruptorEvent> ringBuffer){
		this.ringBuffer = ringBuffer;
	}
	public void onData(Long id,OrderEntity entity){
		// 获取事件队列 的下一个槽
		long sequence = ringBuffer.next();
		try {
			//获取消息(事件)
			DisruptorEvent event = ringBuffer.get(sequence);
			// 往事件写入数据
			event.setValue(id);
			event.setOrderEntity(entity);
		} catch (Exception e) {
			//异常处理 TODO
			e.printStackTrace();
		} finally {
			System.out.println("生产者"+ Thread.currentThread().getName()
					+"发送订单id:"+id);
			//发布事件
			ringBuffer.publish(sequence);
		}
	}
}

创建消费者

//消费者
public class MyEventHandler implements EventHandler<DisruptorEvent>, WorkHandler<DisruptorEvent> {

	@Override
	public void onEvent(DisruptorEvent event, long l, boolean b) throws Exception {
		// TODO 消费逻辑
		System.out.println("消费者"+ Thread.currentThread().getName()
				+"获取事件:"+ event.getValue()+",name:"+event.getOrderEntity().getOrderName());
	}
	//实现WorkHandler 重写onEvent 解决消息重复消费
	@Override
	public void onEvent(DisruptorEvent event) throws Exception {
		// TODO 消费逻辑
		System.out.println("消费者"+ Thread.currentThread().getName()
				+"获取事件:"+ event.getValue()+",name:"+event.getOrderEntity().getOrderName());
	}
}

测试类

public class test {
	public static void main(String[] args) {
		//创建disruptor
		Disruptor<DisruptorEvent> disruptor = new Disruptor<>(
				new DisruptorEventFactory(),
				1024 * 1024,
				Executors.defaultThreadFactory(),
				ProducerType.MULTI, //SINGLE 单生产者 MULTI 多生产者
				new YieldingWaitStrategy()  //等待策略
		);
		//设置消费者用于处理RingBuffer的事件
		//注意:所有消费者必须要先于disruptor.start(); 调用 否则报错
//		disruptor.handleEventsWith(new MyEventHandler());
		//如果消费者是多个,只需要在调用handleEventsWith方法时将多个消费者传递进去。
		//注意:这样会出现消息重复消费情况
//		disruptor.handleEventsWith(new MyEventHandler(),new MyEventHandler());
		//解决多消费者消息重复消费  消费者要实现WorkHandler接口
		disruptor.handleEventsWithWorkerPool(new MyEventHandler(), new MyEventHandler());
		disruptor.start();
		//获取数据队列
		RingBuffer<DisruptorEvent> ringBuffer = disruptor.getRingBuffer();
		//创建生产者
//		EventProducer eventProducer = new EventProducer(ringBuffer);
		// 发送消息
//		for(long i=0;i<10;i++){
//			eventProducer.onData(i,new OrderEntity(i,"测试"+i));
//		}
		//多生产者模式
		new Thread(() -> {
			//创建生产者
			EventProducer eventProducer = new EventProducer(ringBuffer);
			// 发送消息
			for (long i = 0; i < 10; i++) {
				eventProducer.onData(i, new OrderEntity(i, "GG" + i));
			}
		}, "producer1").start();
		new Thread(() -> {
			//创建生产者
			EventProducer eventProducer = new EventProducer(ringBuffer);
			// 发送消息
			for (long i = 0; i < 10; i++) {
				eventProducer.onData(i, new OrderEntity(i, "DD" + i));
			}
		}, "producer2").start();
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值