八、线程池
2019-10-25 06:48:29来源:博客园 阅读 ()
八、线程池
线程池
【1】为什么需要线程池?线程池的优点?
1、为什么要使用线程池?
因为创建和销毁线程都是需要时间的,特别是需要创建大量线程的时候,时间和资源的消耗是不可忽略的,而合理的使用线程池中已经创建的线程,可以减少创建和销毁线程而花费的时间和资源。
2、线程池的优点?
(1)降低资源消耗:通过线程的重用可以降低创建和销毁线程花费的时间和资源;
(2)提高响应速度:任务到达时,因为利用线程池中已经创建好的线程,可以不用等待线程创建而直接执行任务;
(3)提高线程的可管理性:线程池允许我们开启多个任务而不用为每个线程设置属性(便于管理);线程池根据当前在系统中运行的进程来优化线程时间片(调优);线程池可以限制创建线程的数量,如果无限制的创建线程,不仅会消耗资源,还会降低系统的稳定性;
【2】线程池的工作原理
当向线程池提交一个任务以后,线程池处理任务的流程大概如下:
1、判断核心线程池是否满?如果没有,那么创建线程并执行任务;如果满,那么进入2;
2、判断阻塞队列(BlockingQueue)是否满?如果没有,那么把任务添加到阻塞队列中;如果满,进入3;
3、判断当前线程数是否大于最大线程数(maximumPoolSize)?如果没有,那么创建线程并执行任务;如果满,那么进入饱和策略(RejectionExecutionHandler);
4、如果核心线程池的线程执行完当前任务,那么立刻从阻塞队列头取任务来执行,如果队列为空,且当前线程超过了存活时间(keepAliveTime),那么判断当前线程数是否大于核心线程池的最大数(corePoolSize)?如果是,那么销毁当前线程,如果不是则保留当前线程。因此当创建的线程数大于核心线程池的最大数(corePoolSize),在所有任务执行完毕以后,线程池最终线程数量会回到corePoolSize。
流程图如下所示:
【3】线程池的创建
ThreadPoolExecutor:是线程池中最核心的类,这里着重说一下这个类的各个构造参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
(1)corePoolSize:核心线程池大小。如果调用了prestartAllCoreThread()方法,那么线程池会提前创建并启动所有基本线程。
(2)maximumPoolSize:线程池大小
(3)keepAliveTime:线程空闲后,线程存活时间。如果任务多,任务周期短,可以调大keepAliveTime,提高线程利用率。
(4)timeUnit:存活时间的单位,有天(DAYS)、小时(HOURS)、分(MINUTES)、秒(SECONDS)、毫秒(MILLISECONDS)、微秒(MICROSECONDS)、纳秒(NANOSECONDS)
(5)runnalbleTaskQueue:阻塞队列。可以使用ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue
静态工厂方法Executors.newFixedThreadPool( )使用了LinkedBlockingQueue;
静态工厂方法Executors.newCachedThreadPool( )使用了SynchronousQueue;
(6)handler:饱和策略的句柄,当线程池满了的时候,任务无法得到处理,这时候需要饱和策略来处理无法完成的任务,饱和策略中有4种处理策略:
AbortPolicy:这是默认的策略,直接抛出异常;
CallerRunsPolicy:只是用调用者所在线程来运行任务;
DiscardOldestPolicy:丢弃队列中最老的任务,并执行当前任务;
DiscardPolicy:不处理,直接把当前任务丢弃;
当然也可以自定义饱和处理策略,需要实现RejectedExecutionHandler接口,比如记录日志或者持久化不能存储的任务等。
【4】线程池的关闭
可以通过shutdown或者shutDownNow来关闭线程池。
原理:遍历线程池中的线程,逐个调用线程的interrupt()方法来中断线程,所以不响应中断的线程可能永远无法终止
(1)shutDown:把线程池的状态设置为SHUTDOWN,然后中断所有没有正在执行任务的线程,而已经在执行任务的线程继续执行直到任务执行完毕;
(2)shutDownNow:把当前线程池状态设为STOP,尝试停止所有的正在执行或者暂停的线程,并返回等待执行的任务的列表;
在调用了shutDown或者shutDownNow后,调用isShutDown()返回true;当所有任务都关闭后,调用isTerminaed()方法返回true。(注意关闭线程池和所有线程关闭是不同的)
【5】线程池的合理配置
(1)CPU(计算)密集型则线程数配置尽可能的少,比如NCPU+1。可以通过Runtime.getRuntime().avaliableProcessors( )方法获得当前设备的CPU数量;
(2)IO密集型需要配置尽可能多的线程数,比如2*NCPU,因为IO处理时线程阻塞的时间很长,导致CPU空闲时间很长,多一点线程可以提高CPU利用率;
(3)混合型任务:如果可以拆分,最好拆分成CPU密集型任务+IO密集型任务,只要这两个拆分后的任务执行时间相差没有太大,那么拆分后的吞吐量将高于串行执行的吞吐量,如果时间相差太大,就没有必要分解;
(4)优先级不同的任务:使用PriorityQueue作为阻塞队列。(如果一直有优先级高的任务进来,可能导致优先级低的任务无法执行)
(5)执行时间不同的任务:可以交给不同规模的线程池来执行;或者使用PriorityQueue作为阻塞队列,把执行时间短的任务优先级设置高一点,让时间短的任务先执行;
(6)建议使用有界队列,这样可以保证系统的稳定性,如果队列时无界的,那么一直有任务进来就一直往阻塞队列添加节点,可能导致内存溢出。
原文链接:https://www.cnblogs.com/lee0527/p/11716254.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
下一篇:玩转Spring全家桶
- 最详细的java多线程教程来了 2020-06-08
- 系统化学习多线程(一) 2020-06-08
- 多线程:生产者消费者(管程法、信号灯法) 2020-06-01
- 如何合理地估算线程池大小? 2020-05-31
- 那些面试官必问的JAVA多线程和并发面试题及回答 2020-05-28
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash