首页 > 编程语言 >9.Java异步编程

9.Java异步编程

时间:2022-12-31 16:12:12浏览次数:39  
标签:异步 Java 编程 任务 实例 线程 Executor ExecutorService

一.Java Executor框架

 Runnable接口和Callable接口都是对任务的抽象。java.util.concurrent.Executor接口则是对任务执行的抽象。

 Executor接口功能有限,①只能为客户端代码执行任务,无法将任务的处理结果返回给客户端代码。②Executor接口实现类内部往往会维护一些工作者线程,当不再需要一个Executor实例时,往往需要将该实例内部维护的工作者线程停掉以释放相应的资源,而Executor接口没有定义相应的方法。ExecutorService接口继承自Executor接口,它解决了上述问题。

1-1 实用工具类Executors

 Executors提供了一些能够返回ExecutorService实例的快捷方法,使得可以在不必手动创建ThreadPoolExecutor实例的情况下使用线程池。

表1-1 Executors提供的能够返回ExecutorService实例的快捷方法
方法 适用条件及注意事项
public static ExecutorService newCachedThreadPool()

适用于 ①大量 ②耗时较短 ③提交频繁 的任务。

如果提交的任务耗时较长,可能导致线程池中工作者线程数量无限制地增加,最后导致过多的上下文切换,从而使得整个系统变慢

public static ExecutorService

newCachedThreadPool(ThreadFactory threadFactory)

public static ExecutorService newFixedThreadPool(int nThreads)

核心线程池大小等于最大线程池大小,因此该线程池中的工作者线程永远不会超时,必须在不需要该线程池的时候主动将其关闭。

public static ExecutorService

newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

public static ExecutorService newSingleThreadExecutor()

适用于单(多)生产者——单消费者模式。

该方法的返回值无法被转换为ThreadPoolExecutor类型

public static ExecutorService

newSingleThreadExecutor(ThreadFactory threadFactory)

1.Executors.newCachedThreadPool()

 该方法的返回值相当于:

new ThreadPoolExecutor(
        0, // 核心线程池大小为0
        Integer.MAX_VALUE, // 最大线程池大小不受限
        60L, // 工作者线程最大空闲时间(KeepAliveTime)为60秒
        TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>() // 内部以SynchronousQueue为工作者队列
);
  • 该线程池中的所有工作者线程在空闲了指定的时间后都可以被自动清理掉。
  • 由于该线程池的核心线程池大小为0,因此提交给该线程池执行的第一个任务会导致该线程池中的第一个工作者线程被创建并启动。后续继续给该线程池提交任务的时候,由于当前线程池大小已经超过了核心线程池大小(0),因此ThreadPoolExecutor此时会尝试将任务缓存到工作者队列中(即调用非阻塞的workderQueue.offer方法)。
  • SynchronousQueue内部并不维护用于存储队列元素的实际存储空间。一个线程(生产者线程)在执行SynchronousQueue.offer(E)的时候,如果没有其他线程(消费者线程)因执行SynchronousQueue.take()而被暂停,那么SynchronousQueue.offer(E)调用会直接返回fasle,即如对列失败
  • 在线程池所有工作者线程都在执行任务(无空闲工作者线程)的情况下,给其提交任务会导致该任务无法被缓存成功。而ThreadPoolExecutor在①任务缓存失败且②线程池当前大小未达到最大线程池大小的情况下会创建并启动新的工作者线程。
  • 在极端情况下,给该线程池没提交一个任务都会导致一个新的工作者线程被创建并启动,这会导致系统中线程过多。

2.Executor.newFixedThreadPool()

 该方法的返回值相当于:

new ThreadPoolExecutor(
        nThreads, // 核心线程池大小
        nThreads, // 最大线程池大小
        0, // 此时最大空闲时间没意义
        TimeUnit.MILLISECONDS,
        new LinkedBlockingDeque<Runnable>()
);
  • 空闲工作者线程不会被清理。
  • 线程池大小一旦达到其核心线程池大小就既不会增大也不会减小工作者线程。
  • 不需要此线程池实例时,必须主动将其关闭。

3.Executor.newSingleThreadPool()

 该方法的返回值相当于Executor.newFixedThreadPool(1)所返回的线程池。但该线程池实例并非ThreadPoolExecutor实例,而是一个封装了ThreadPoolExecutor实例且对外仅暴露ExecutorService接口所定义方法的一个ExecutorService实例。

 该线程池确保了在任意时刻只有一个任务会被执行,这就形成类似锁将原本并发的操作改为串行操作的效果。

 该线程池适合用来执行访问了非线程安全对象而又不希望引入锁的任务。

1-2 异步任务的批量执行:CompletionService

 如果需要一次性提交一批异步任务并获取这些任务的处理结果的话,那么仅使用Future接口(ExecutorService.submit(Callable<V> task))会较为繁琐。java.util.concurrent.CompletionService接口能够较便利地批量提交异步任务并获取这些任务的处理结果。

 CompletionService接口定义的一个submit方法可用于提交异步任务,该方法的签名与ThreadPoolExecutor的一个submit方法相同:Future<V> submit(Callable<V> task)。如果需批量提交异步任务,则通过多次调用此submit方法,此时通常并不关心该方法的返回值,若要求批量获取提交的异步任务的处理结果,可以使用CompletionService接口中的

Future<V> take() throws InterruptedException

 该方法与BlockingQueue.take()相似,是一个阻塞方法,其返回值是一个已经执行结束的异步任务对应的Future实例,该实例就是提交相应任务时submit(Callable<V>)调用的返回值。如果take()被调用时没有已经执行结束的异步任务,那么take()的执行线程就会被暂停,直到有异步任务执行结束。因此,我们批量提交了多少个异步任务,则多少次连续调用CompletionService.take()便可以获取这些任务的处理结果。但不保证获取处理结果的顺序与任务提交的顺序一致(与任务完成先后的顺序一致)。

 CompletionServie也定义了两个非阻塞方法用于获取异步任务处理结果:

Future<V> poll()
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException

 这两个方法与BlockingQueue的poll方法相似,他们的返回值是已经结束任务的异步任务对应的Future实例(如果没拿到则为null)。

 Java标准库提供的CompletionService的实现类为ExecutorCompletionService,ExecutorCompletionService其中一个构造器是:

ExecutorCompletionService(Executor executor,
                BlockingQueue <Future<V>> completionQueue);

由此可见,ExecutorCompletionService相当于Executor实例与BlockingQueue实例的一个融合体。其中Executor负责接收并执行异步任务,而BlockingQueue实例则用于存储已经执行完毕的异步任务对应的Future实例。ExecutorCompletionService会为其客户端提交的每个异步任务(Callable实例或者Runnable实例创建一个相应的Future实例,通过该实例其客户端代码便可以在任务处理完成后获取到相应异步任务的处理结果(如果任务未处理完成,通过不为null的Future拿不到处理结果)。ExecutorCompletionService每执行完一个异步任务,就将该任务对应的Future实例存入其内部维护的BlockingQueue实例之中,其客户端代码则可以通过ExecutorCompletionService.take()调用来获取这个Future实例。

标签:异步,Java,编程,任务,实例,线程,Executor,ExecutorService
From: https://www.cnblogs.com/certainTao/p/17011515.html

相关文章

  • How to Iterate HashMap in Java?
    https://www.geeksforgeeks.org/how-to-iterate-hashmap-in-java/ HashMap isapartofJava’scollectionprovidingthebasicimplementationoftheMapinterfa......
  • JAVA零基础小白上手教程day08-JAVAOOP面向对象
    day08-JAVAOOP课程目标1.【理解】什么是接口2.【掌握】接口的定义格式3.【掌握】接口的使用4.【理解】接口的成员特点5.【理解】类和接口抽象类和接口之间的关......
  • How to Maintain Insertion Order of the Elements in Java HashMap?
    https://www.geeksforgeeks.org/how-to-maintain-insertion-order-of-the-elements-in-java-hashmap/ Whenelementsgetfromthe HashMap duetohashingtheorder......
  • 生成JavaDoc文档
    javadoc在Dos命令中生成java文档打开.java所在文件目录目录上cmd\..\..javadoc-encodingUTF-8-charsetUTF-8Demo01.java生成index.html文件javadoc在IDEA生......
  • 第八章《Java高级语法》第8节:Java可变参数
    如果需要定义一个2个数相加的方法,可以在定义方法时为方法设置2个参数,同理,如果要定义一个3个数相加的方法,就给方法设置3个参数,但是如果想定义一个任意多数字相加的方法该怎么......
  • 第八章《Java高级语法》第11节:泛型
    ​泛型也JDK1.5引入的一种技术,它改变了核心API中的许多类和方法。使用泛型,可以建立类型安全模式来处理各种数据的类、接口和方法。使用泛型,一旦定义了一个算法,就可以独立于......
  • 第八章《Java高级语法》第12节:Lambda表达式
    ​Lambda表达式是JDK8的一个新特性,它可以定义大部分的匿名内部类,从而让程序员能写出更优雅的Java代码,尤其在集合的各种操作中可以极大地优化代码结构。8.12.1认识Lambda......
  • 音视频:JavaCV 视频切片(MPEG-TS)(HLS)
    需要进行简单的音视频编程,如果不是特别数据C/C++,那么JavaCV应该是比较好的选择,下面记录一下使用JavaCV视频切片(MPEG-TS)(HLS)的方法。注意:存放HLS切片的目录必须存在(不会自......
  • 第八章《Java高级语法》第7节:枚举
    ​枚举是JDK1.5中新增加的一种数据类型,它最大的特点就是枚举数据类型的取值范围由程序员自己规定,本小节将会讲解枚举的用法以及实现枚举的原理。8.7.1枚举的概念及定义方式......
  • day04_java基础
    day04_java基础课程目标1.【掌握】IDEA的基本使用2.【理解】什么是数组3.【掌握】数组的定义及初始化4.【理解】数组的内存图6.【理解】数组常见的问题7.......