多线程软件设计方法确实可以最大限度地发挥现代多核处理器的计算能力,提高生产系统的吞吐量和性能,但是,若不加控制和管理随意使用线程,对系统的性能反而会产生不利影响
首先,线程的创建和关闭依然需要花费时间,如果为每一个小的任务都创建一个线程,很有可能出现创建和销毁线程所占用的时间大于该线程真实工作所消耗的时间的情况;其次,线程本身也要占用内存空间的,大量的线程会抢占宝贵的内存资源,如果处理不当,可能会导致Out of Memory异常,即便没有,大量的线程回收也会给GC带来很大的压力,延长GC的停顿时间
线程池中,总有那么几个活跃线程,当你使用线程时,可以从池子随便拿一个空闲线程,当完成工作时,并不着急关闭线程,而是将这个线程退回到池子,简而言之,使用线程池后,创建线程变成了从线程池获得空闲线程,关闭线程变成了向池子归还线程
在开发中,合理使用线程池能带来3个好处:
第一,降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
第二,挺高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
第三,提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控
1.JDK线程池
在JDK并发包java.util.concurrent中,ThreadPoolExecutor通常使用工厂类Executors来创建,Executors可以创建3种类型的ThreadPoolExecutor,如下;ScheduledThreadPoolExecutor也使用工厂类Executors来创建,且可创建两种类型:
举一个简单例子,展示线程池的使用
输出,10个任务分成2批进行,并且时间相差1秒(很奇怪,毫秒为单位的话,运行差为什么不直等于1000)
分析,上述不同方式创建的线程池有完全不同的功能,但是其内部均是使用了ThreadPoolExecutor实现,先看下ThreadPoolExecutor类
其主要构造函数
- corePoolSize,核心池的大小,当提交一个任务到线程池,线程池会创建一个线程来执行任务,即使其它空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小就不再创建
- maximumPoolSize,线程池中的最大线程数,表示线程池中最多能创建多少个线程,如果队列满了,并且已经创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务,如果使用了无界队列该参数则没效果
- keepAliveTime,表示线程没有任务执行时最多保持多久时间会终止,默认,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize
- unit,参数keepAliveTime的时间单位
- workQueue,一个阻塞队列,用来存储等待执行的任务
- threadFactory,线程工厂,用来创建线程
- handler,当拒绝处理任务时的策略
由ThreadPoolExecutor类可知,其实继承了 AbstractExecutorService类的,而AbstractExecutorService类是一个抽象类,实现了ExecutorService接口:
而ExecutorService接口继承了Executor接口:
由此可见,Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor之间的关系
Executor是一个顶层接口,在它里面只声明了一个方法:
ExecutorService继承Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现ExecutorService中的所有方法
ThreadPoolExecutor继承了抽象类AbstractExecutorService,其中有几个重要方法提及一下:execute、submit提交任务,shutdown和shutdownNow关闭线程池
Executor框架主要由3大部分组成如下:
- 任务,包括被执行任务需要实现的接口:Runnable或Callable
- 任务的执行,包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口
- 异步计算的结果,包括接口Future和实现Future接口的FutureTask类
类和接口的简单介绍:
- Executor,接口,是Executor框架的基础,将任务的提交和任务的执行分离
- ThreadPoolExecutor,线程池的核心实现类,用来执行被提交的任务
- ScheduledThreadPoolExecutor,实现类,可以在给定的延迟后执行命令,或者定期执行命令
- Future接口和其实现类FutureTask,代表异步计算结果
- Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行
2.线程池状态
当创建线程池,初始时,线程池处于RUNNING 状态;如果调用shutdown方法,线程池处于SHUTDOWN状态,此时线程池不能够接收新的任务,它会等待所有任务执行完毕;如果调用shutdownNow,则线程池处于STOP 状态,此时线程池不能够接收新的任务,并会尝试终止正在执行的任务;当线程池处于SHUTDOWN或者STOP,并且所有工作线程已经销毁,任务缓存队列已经清空或者执行结束后,线程池被设置为TERMINATED
3.任务缓存队列
BlockingQueue<Runnable> workQueue可以使用以下几种:
- 直接提交的队列,SynchronousQueue,没有容量,提交的任务不会被真实保存,而总是将新任务提交给线程执行,如果没有空闲的线程,则尝试创建新的线程,如果新的线程数量已经达到最大值,则执行拒绝策略
- 有界的任务队列,ArrayBlockingQueue,若有新的任务要执行,如果线程池的实际线程数小于corePoolSize,则优先创建新的线程,若大于corePoolSize,将新任务加入等待队列。若等待队列已满无法加入,在总线程数不大于maximumPoolSize时创建新的线程执行任务,否则执行拒绝策略。队列按照先进先出算法处理任务
- 无界的任务队列,LinkedBlockingQueue,与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。队列按照先进先出算法处理任务
- 优先任务队列,PriorityBlockingQueue,可以控制任务执行的先后顺序,是一个特殊的无界队列
4.拒绝策略
JDK内置的拒绝策略如下:
- AbortPolicy,直接抛出异常,阻止系统正常工作
- DiscardPolicy,默默丢弃无法处理的任务,不予任何处理
- DiscardOldestPolicy,丢弃队列里最近的一个任务,并执行当前任务
- CallerRunsPolicy,只用调用者所在线程来运行任务
以上策略均实现了RejectedExecutionHandler接口,若以上策略仍不满足,则可自己扩展
5.对1中所介绍的线程池详解下
(1)newFixedThreadPool
其corePoolSize和maximumPoolSize都被设置为指定参数nThreads,当线程池中的线程数大于corePoolSize,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止,这里设置为0L,即多余的空闲线程会被立即终止
FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列,其容量为Integer.MAX_VALUE,有如下特点:
- 1.当线程池中的线程数达到corePoolSize,新任务在无界队列中等待,so线程池中的线程数不超过maximumPoolSize
- 2.由于1,maximumPoolSize是一个无效参数
- 3.由于1和2,keepAliveTime是一个无效参数
- 4.由于使用无界队列,运行中的FixedThreadPool不会拒绝任务
(2)newSingleThreadExecutor
(3)newCachedThreadPool
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!