Java中的线程池
一、线程池的好处
1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
二、线程池的种类
Java通过Executors提供四种线程池
1. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
三、 ThreadPoolExecutor
1. 上述四种Java中的线程池都是用ThreadPoolExecutor实现的
2. ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
3. ThreadPoolExecutor继承了AbstractExecutorService抽象类,AbstractExecutorService实现了ExecutorService接口,ExecutorService继承了Executor 接口
- corepoolsize:核心池的大小,默认情况下,在创建了线程池之后,线程池中线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中线程数达到 corepoolsize 后,就把任务放在任务缓存队列中。
- Maximumpoolsize:线程池中最多创建多少个线程。
- Keeplivetime:线程没有任务执行时,最多保存多久的时间会终止,默认情况下,当线程池中线程数>corepoolsize ,Keeplivetime 才起作用,直到线程数不大于 corepoolsize。
- workQueue:阻塞队列,用来存放等待被执行的任务
- threadFactory:线程工厂,用来创建线程。
4. ThreadPoolExecutor继承了AbstractExecutorService抽象类,AbstractExecutorService实现了ExecutorService接口,ExecutorService继承了Executor 接口
四、 线程池生命周期/状态转换过程
1. RUNNING :当线程池创建后,初识为Running状态,能接受新提交的任务,并且也能处理阻塞队列中的任务;
2. SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
3. STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
4. TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
五、 线程池的组成
1. 线程池管理器:用于创建并管理线程池
2. 工作线程:线程池中的线程
3. 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
4. 任务队列:用于存放待处理的任务,提供一种缓冲机制
六、 execute方法执行流程
1. addWorker方法的主要工作是在线程池中创建一个新的线程并执行,firstTask参数用于指定新增的线程执行的第一个任务,core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize
2. 下述添加到工作线程并执行,调用的就是addWorker()
3. submit方法能获得线程执行后的返回值,底层还是调用了execute方法
4. execute方法工作流程
- 若当前线程池中线程数<corepoolsize,则每来一个任务就创建一个线程去执行,即使现在的线程是空闲的。
- 若当前线程池中线程数>=corepoolsize,会尝试将任务添加到任务缓存队列中去,若添加成功,则任务会等待空闲线程将其取出执行,若添加失败,则尝试创建线程去执行这个任务。
- 若当前线程池中线程数>= Maximumpoolsize,则采取拒绝策略有 4 种,
1)abortpolicy 丢弃任务,抛出 RejectedExecutionException
2)discardpolicy 拒绝执行,不抛异常
3)discardoldestpolicy 丢弃任务缓存队列中最老的任务,并且尝试重新提交新的任务
4)callerrunspolicy 有反馈机制,使任务提交的速度变慢。
七、 工作线程整个工作流程
1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
2. 当调用 execute()/submit() 方法添加一个任务时,会进行上述线程数量判断过程(线程池创建线程时,会将线程封装成工作线程Worker),并执行addWorker() 添加并执行任务
3. 调用addWorker()中的runWorker()方法执行任务firstTask,firstTask执行完成之后,通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源;
4. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。