实现自定义异步流

Java 9并发编程指南 目录

响应式流(http://www.reactive-streams.org/)提供了具有非阻塞压力的异步流处理机制。

响应式流基于以下三个要素:

  • 信息发布者
  • 多个信息订阅者
  • 发布者与消费者之间的订阅

Java 9提供三个接口,Flow.Publisher、Flow.Subscriber和Flow.Subscription,以及工具类SubmissionPublisher,用来实现响应式流应用。

本节将学习如何只使用这三个接口实现自定义的响应式流应用。考虑到实现三个元素之间的预期行为,发布者将只向提出请求的订阅者发送元素,并且以并发形式进行此操作。但是通过修改这些方法的实现,可以很容易地修改这种行为。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤实现范例:

  1. 创建名为News的类,此类实现实现从发布者发送到订阅者的元素,包含两个名为title和content的私有String属性,名为date的Date属性,还包括获取和设置这些属性值的方法。此类代码很简单,不在这里列出。

  2. 创建名为Consumer的类,指定其实现News类参数化的Subscriber接口,包含两个私有属性:名为subscription的Subscription对象和名为name的String属性。实现类构造函数,初始化name属性:

    public class Consumer  implements Subscriber<News>{
    	private Subscription subscription;
    	private String name;
    	public Consumer(String name) {
    		this.name=name;
    	}
    
  3. 实现onComplete()方法,当发布者不发送任何附加元素时,调用此方法。本范例中,只输出一条信息到控制台:

    	@Override
    	public void onComplete() {
    		System.out.printf("%s - %s: Consumer - Completed\n", name, Thread.currentThread().getName());
    	}
    
  4. 实现onError()方法,当有错误发生时,发布者调用此方法。本范例中,只输出一条信息到控制台:

    	@Override
    	public void onError(Throwable exception) {
    		System.out.printf("%s - %s: Consumer - Error: %s\n", name, Thread.currentThread().getName(), exception.getMessage());
    	}
    
  5. 然后实现onNext()方法。此方法将News对象作为参数接收,当发布者向订阅者发送条目时,调用此对象。本范例中,输出News对象的属性值到控制台,并且使用Subscription对象的request()方法请求附加条目:

    	@Override
    	public void onNext(News item) {
    		System.out.printf("%s - %s: Consumer - News\n", name, Thread.currentThread().getName());
    		System.out.printf("%s - %s: Title: %s\n", name, Thread.currentThread().getName(), item.getTitle());
    		System.out.printf("%s - %s: Content: %s\n", name, Thread.currentThread().getName(), item.getContent());
    		System.out.printf("%s - %s: Date: %s\n", name, Thread.currentThread().getName(), item.getDate());
    		subscription.request(1);
    	}
    
  6. 最后,实现onSubscription()方法,发布者将调用此方法,是唤醒Subscriber的首个方法。它在发布者和订阅者之间接收Subscription。本范例中, 存储Subscription对象,通过订阅者使用request()方法请求待处理的首个条目:

    	@Override
    	public void onSubscribe(Subscription subscription) {
    		this.subscription = subscription;
    		subscription.request(1);
    		System.out.printf("%s: Consumer - Subscription\n", Thread.currentThread().getName());
    	}
    }
    
  7. 实现名为MySubscription的类,指定其实现Subscription接口。包含名为canceled的私有Boolean属性和名为requested的私有整型属性:

    public class MySubscription implements Subscription{
    	private boolean canceled=false;
    	private long requested=0;
    
  8. 实现Subscription接口提供的cancel()方法,取消发布者与订阅者之间的通信。本范例中设置canceled属性值为true:

    	@Override
    	public void cancel() {
    		canceled=true;
    	}
    
  9. 实现Subscription接口提供的request()方法,订阅者使用此方法向发布者请求元素。它将订阅者请求的元素数量作为参数接收,本范例中,递增requested属性值:

    	@Override
    	public void request(long value) {
    		requested+=value;
    	}
    
  10. 实现isCanceled()方法获得canceled属性值,getRequested()方法获得requested属性值,以及decreaseRequested()方法递减requested属性值:

    	public boolean isCanceled() {
    		return canceled;
    	}
    	public long getRequested() {
    		return requested;
    	}
    	public void decreaseRequested() {
    		requested--;
    	}
    }
    
  11. 实现名为ConsumerData的类,发布者使用此方法存储每个订阅者的信息。包含名为consumer的私有Consumer属性和名为subscription的私有MySubscription属性,还包括获取和设置这些属性值的方法。此类代码很简单,不在这里列出。

  12. 实现名为PublisherTask的类,指定其实现Runnable接口。包含名为consumerData的私有ConsumerData属性和名为news的私有News属性。实现类构造函数,初始化这两个属性:

    public class PublisherTask  implements Runnable{
    	private ConsumerData consumerData;
    	private News news;
    	
    	public PublisherTask(ConsumerData consumerData, News news) {
    		this.consumerData = consumerData;
    		this.news = news;
    	}
    
  13. 实现run()方法,将得到ConsumerData属性的MySubscription对象。如果subscription未被取消且已经请求元素(属性值大于0),则使用onNext()发送News对象给订阅者,然后减少requested属性值:

    	@Override
    	public void run() {
    		MySubscription subscription = consumerData.getSubscription();
    		if (!(subscription.isCanceled() && (subscription.getRequested()> 0))) {
    			consumerData.getConsumer().onNext(news);
    			subscription.decreaseRequested();
    		}
    	}
    }
    
  14. 然后实现名为MyPublisher的类,指定其实现News类参数化的Publisher接口。此类存储ConsumerData对象的私有ConcurrentLinkedDeque和名为executor的ThreadPoolExecutor对象。实现类构造函数,初始化这两个属性:

    public class MyPublisher implements Publisher<News> {
    	private ConcurrentLinkedDeque<ConsumerData> consumers;
    	private ThreadPoolExecutor executor;
    	public MyPublisher() {
    		consumers=new ConcurrentLinkedDeque<>();
    		executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    	}
    
  15. 现在,实现subscribe()方法,此方法将接收以参数的形式接收发布者条目的Subscriber对象。接着创建MySubscription和ConsumerData对象,在ConcurrentLinkedDeque中存储ConsumerData,并且调用订阅者的onSubscribe()发送subscription对象给Subscriber对象:

    	@Override
    	public void subscribe(Subscriber<? super News> subscriber) {
    		ConsumerData consumerData=new ConsumerData();
    		consumerData.setConsumer((Consumer)subscriber);
    		MySubscription subscription=new MySubscription();
    		consumerData.setSubscription(subscription);
    		subscriber.onSubscribe(subscription);
    		consumers.add(consumerData);
    	}
    
  16. 实现publish()方法,此方法接收News参数并将其发送给符合之前解释条件的订阅者。 为了实现此操作,为每个订阅者创建PublisherTask方法,并将这些任务发送给执行器:

    	public void publish(News news) {
    		consumers.forEach( consumerData -> {
    			try {
    				executor.execute(new PublisherTask(consumerData, news));
    			} catch (Exception e) {
    				consumerData.getConsumer().onError(e);
    			}
    		});
    	}
    }
    
  17. 最后,添加main()方法,实现本范例主类。创建1个发布者和2个订阅者,并将它们订阅给发布者:

    public class Main {
    	public static void main(String[] args) {
    		MyPublisher publisher=new MyPublisher();
    		Subscriber<News> consumer1, consumer2;
    		consumer1=new Consumer("Consumer 1");
    		consumer2=new Consumer("Consumer 2");
    		publisher.subscribe(consumer1);
    		publisher.subscribe(consumer2);	
    
  18. 然后创建News对象,发送给发布者,休眠主线程1秒钟,创建另一个News对象,在此发送给发布者:

    		System.out.printf("Main: Start\n");
    		News news=new News();
    		news.setTitle("My first news");
    		news.setContent("This is the content");
    		news.setDate(new Date());
    		publisher.publish(news);
    		try {
    			TimeUnit.SECONDS.sleep(1);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		news=new News();
    		news.setTitle("My second news");
    		news.setContent("This is the content of the second news");
    		news.setDate(new Date());
    		publisher.publish(news);
    		System.out.printf("Main: End\n");
    	}
    }
    

工作原理

本范例中,使用Java 9 API提供的接口实现了发布者和订阅者之间的响应式流通信,且仅遵循响应式流规范中定义的预期行为。

包括MyPublisher类实现的发布者和Consumer类实现的订阅者,以及在发布者和MySubscription对象实现的每个订阅者之间有订阅信息。

当订阅者调用发布者的subscribe()方法时,通信循环开始。发布者在其间创建订阅信息,并使用onSubscribe()方法发送订阅信息给订阅者。订阅者必须使用发布信息的request()方法表明已经准备好处理来自发布者的更多元素。当发布者发布条目时,将此条目发送给所有使用订阅信息从发布者请求元素的订阅者。

我们添加了所有必要的元素确保以并发的方式执行此行为。

下图显示本范例在控制台输出的执行信息:

pics/08_08.jpg

扩展学习

创建实现响应式流的应用最简单的方式是使用SubsmissionPublisher类,此类实现Publisher接口,并提供必要的方法将其作为应用的发布者部分。

更多关注

  • 第六章“并行和响应式流”中的“响应式流编程”小节
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值