线程池
简介
线程池的创建
new ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit milliseconds,
BlockingQueue<Runnable> runnableTaskQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
- corePoolSize(线程池基本大小)
这是一个判断该线程池是否要新建一个新的线程的标准值,如果当前线程池中的线程数小于corePoolSize,
则在接到新的请求任务时新建一个线程来处理,反之则把任务放到
BlockingQueue中,由线程池中空的线程从BlockingQueue中取出并处理;
- maximumPoolSize(线程池最大大小)
线程池最大的线程数,当大于该值的时候则让RejectedExecutionHandler拒绝处理;
keepAliveTime
当线程池中大于corePoolSize的时候,部分多余的空线程会等待keepAliveTime时间,如果没有请求处理超过该时间则自行销毁;BlockingQueue(任务队列)
保存等待任务执行的阻塞队列,有以下几种:- ArrayBlockingQueue:数组队列,先进先出;
- LinkedBlockingQueue:链表队列,先进先出,吞吐量大于ArrayBlockingQueue;
- SynchronousQueue:一个不存储元素的队列,每插入一个任务必须等到另一个线程调用移除操作,否则处于阻塞状态;吞吐量高于LinkedBlockingQueue,newCachedThreadPool使用的就是这个队列;
- PriorityBlockingQueue:一个优先级无限阻塞的队列;
RejectedExecutionHandler(饱和策略)
当线程池处于饱和状态下,对于提交的新任务必须要有一种策略来处理;
提交任务
execute(new Runnable(){} )
没有返回值threadsPool.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } });
commit(new callable(){}) 返回future对象
Future future = mThreadPoolExecutor.submit(new Callable() { @Override public Object call() throws Exception { return null; } }); -------------------------------------------------- try { Object o = future.get();//阻塞 直到结果准备就绪 } catch (InterruptedException e) { //中断异常 e.printStackTrace(); } catch (ExecutionException e) { //无法执行异常 e.printStackTrace(); } finally { //关闭线程池 mThreadPoolExecutor.shutdown(); } }
关闭任务
- 原理
遍历所有的线程,逐个调用线程的interrupt方法来中断线程; - shutdown
执行shutdown后,遍历线程池中所有的线程,将状态修改为SHUTDOWN状态,然后中断正在执行任务的线程; - shutdownNow
执行shutdownNow后,遍历线程池中所有的线程,将所有线程的状态修改为STOP状态,然后尝试停止所有的线程任务。
工作原理
- 工作流程示意图
源码分析
工作线程
合理配置线程池
任务特性
- 任务的性质:CPU密集型任务,IO密集型的任务,混合型任务;
- 任务的优先级:高,中,低;
- 任务的执行时间:长、中、短;
- 任务的依赖性,是否依赖其他系统资源,比如数据库连接。
配置意见
- CPU密集型建议使用线程数尽可能少的线程池,IO密集型任务由于线程并不是一直在工作,所以建议使用线程数较多的线程池;
- 优先级不同任务可以使用PriorityBlockingProcessors任务队列来处理,让优先级更高的来处理(!注意:如果一直是优先级搞的任务在处理,则优先级低的任务可能无法得到处理;
- 时间不同的任务可以交给不同规模的线程池来处理,也可以交给优先级队列,让时间短的任务先执行;
- 依赖数据库连接池的任务,由于提交SQL后需要等待返回结果,所以等待的时间越长,CPU空闲的时间越长,为了提高CPU的利用率,可以通过增大线程池的线程数。
- 建议使用有界队列,这样更加稳定安全,防止撑爆内存(如果线程一直处于阻塞状态,新的任务来的时候就会新建新的线程,直到内存不足)。
线程池的监控
部分线程池的属性参数
- taskCount:线程池需要执行的任务数量;
- completedTaskCont: 已经完成的任务数量;
- largestPoolSize: 线程池曾经创建过的最大线程数,可以看看是否大于最大线程数,是否满过;
- getPoolSize: 线程池的线程数量,线程池不销毁,池中的线程不会自动销毁;
- getAliveCount:活动状态的线程数。
通过扩展线程池进行监控
- 继承线程池;
- 重写beforeExecute,afterExecute和terminated方法。
常用线程池
FixedThreadPool 定长并发线程池
源码
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
- 特点:
可以创建固定的线程数,当线程数达到corePoolSize的时候,再添加任务就放到LinkedBlockingQueue这个无界的队列里边,等待空闲的线程来取任务执行任务,空闲线程不会被回收;
SingleThreadExecutor 顺序执行线程池
源码
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
- 特点
corePoolSize为1,既最多只能有一个线程工作,当其他任务来的时候都放到LinkedBlockingQueue任务队列里边,等待线程工作完后,再从队列里边取,逐个执行,相当于顺序执行;
CachedThreadPool “无限”容量可缓存线程池
源码
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
- 特点
- SynchronousQueue是一个没有容量的阻塞队列,每个插入必须有对应的移除操作;
- 如果没有线程去从SynchronousQueue从事移除的工作,那么就会新建一个线程来执行任务,如果一直这么下去,就可以能因为创建过多的线程耗尽CPU资源;
- 如果空闲的线程等待时间超过60秒,而且没有新的任务,会自动被回收。
- ScheduledThreadPool 定时线程池
参考资料:
- JAVA编程思想
- JDK1.6源码
- 聊聊并发——方腾飞
- Java中常见的线程池