谈谈对 java线程池(Executors、ExecutorService、ScheduledExecutorService)和(Callable、Future、CyclicBarrier)的理解

线程池概念
线程池是实现多线程的一种用法,可以维护多个线程,优化创建和销毁线程的开销达到系统优化性能的目的。

线程池的优点
(1) 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
(2) 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;
(3) 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

Executor结构图
在这里插入图片描述
1、Executor是最基础的执行接口;
2、ExecutorService接口继承了Executor,在其上做了一些shutdown()、submit()的扩展,可以说是真正的线程池接口;
3、AbstractExecutorService抽象类实现了ExecutorService接口中的大部分方法;
TheadPoolExecutor继承了AbstractExecutorService,是线程池的具体实现;
4、ScheduledExecutorService接口继承了ExecutorService接口,提供了带"周期执行"功能ExecutorService;
5、ScheduledThreadPoolExecutor 既继承了TheadPoolExecutor线程池,也实现
6、ScheduledExecutorService接口,是带"周期执行"功能的线程池;
7、Executors是线程池的静态工厂,其提供了快捷创建线程池的静态方法。

线程池的主要工作流程
在这里插入图片描述

1、首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
2、其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
3、最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

ExecutorService 常用函数
1、execute()
以异步的方式执行一个Runnable
2、submit(Callable/Runnable)
跟execute类似,提交一个返回值的任务用于执行,Callable有Future结果返回。
3、awaitTermination()
请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。
4、invokeAll()
执行给定的任务,当所有任务完成时(或超时期满时),返回保持任务状态和结果的 Future 列表。
5、invokeAny()
执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。如果一个任务运行完毕或者抛出异常,方法会取消其它的 Callable 的执行。
6、isShutdown()
判断此执行程序是否已关闭
7、isTerminated()
判断关闭后所有任务是否都已完成
8、shutdown()
启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
9、shutdownNow()
试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
execute与submit区别:

  • 接收的参数不一样
  • submit有返回值,而execute没有
  • submit方便Exception处理
  • execute是Executor接口中唯一定义的方法;submit是ExecutorService(该接口继承Executor)中定义的方法

Executors静态工厂简单用法(阿里巴巴规范不建议这样新建线程池)
1、newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
用法:
用法:Executors.newSingleThreadExecutor()

2、newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
用法:Executors.newFixedThreadPool(int nThreads)

3、newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
用法:Executors.newCachedThreadPool()

4、newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
用法:Executors.newScheduledThreadPool(int nThreads)

不建议的原因
在这里插入图片描述

Callable、Future 介绍
Callable:跟Runnable类似,线程执行逻辑在call()函数,支持返回值。
Future:表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。Future的cancel方法可以取消任务的执行,它有一布尔参数,参数为 true 表示立即中断任务的执行,参数为 false 表示允许正在运行的任务运行完成。Future的 get 方法等待计算完成,获取计算结果。

Callable和Runnable的区别:

  • Callable定义的方法是call,而Runnable定义的方法是run。
  • Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
  • Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。

并发工具类CountDownLatch、CyclicBarrier、Semaphore介绍
1、CountDownLatch 允许一个或多个线程等待其他线程完成操作,利用它可以实现类似计数器的功能
2、CyclicBarrier 让一组线程等待至某个状态之后再全部同时执行
3、Semaphore 可以同时让多个线程同时访问共享资源,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
4、Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

拓展篇
几种排队的策略:
(1)不排队,直接提交
将任务直接交给线程处理而不保持它们,可使用SynchronousQueue
如果不存在可用于立即运行任务的线程(即线程池中的线程都在工作),则试图把任务加入缓冲队列将会失败,因此会构造一个新的线程来处理新添加的任务,并将其加入到线程池中(corePoolSize–>maximumPoolSize扩容)
Executors.newCachedThreadPool()采用的便是这种策略
(2)无界队列
可以使用LinkedBlockingQueue(基于链表的有界队列,FIFO),理论上是该队列可以对无限多的任务排队
将导致在所有corePoolSize线程都工作的情况下将新任务加入到队列中。这样,创建的线程就不会超过corePoolSize,也因此,maximumPoolSize的值也就无效了
(3)有界队列
可以使用ArrayBlockingQueue(基于数组结构的有界队列,FIFO),并指定队列的最大长度
使用有界队列可以防止资源耗尽,但也会造成超过队列大小和maximumPoolSize后,提交的任务被拒绝的问题,比较难调整和控制。

多线程同步和进程单线程区别
线程是在进程中的,一个进程可以有多个线程,单个线程不能脱离进程存在。
所以楼主的问题就是单进程中多线程为何优于多进程(每个进程对应一个线程),当同步时,看上去也都是一个线程在执行,但前者的效率将远远高于后者。因为多线程是共享进程中的数据的,共享数据使得线程之间的通信比进程间通信更快更有效。同时,很多时候,线程相对于进程属于轻量级的,更便于创建和销毁。

线程池编程实践可阅读我另一篇博客
java 线程池(Executors、ExecutorService、ScheduledExecutorService)和(Callable、Future、CyclicBarrier)实践篇

猜你喜欢

转载自blog.csdn.net/eddyjoe/article/details/88374085