赞
踩
线程池是一种池化技术,是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。这样实现线程的复用,避免重复创建与销毁线程的开销和大量线程上下文切换,提高系统效率和并发度。
使用线程池的好处:
我们可以通过ThreadPoolExecutor类构造函数来创建一个线程池,然后调用threadPoolExecutor.execute(Runnable command)方法提交任务执行。
最多七个参数
public ThreadPoolExecutor(int corePoolSize,//最大核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//线程最大空闲时间
TimeUnit timeUnit,//keepAliveTime的单位
BlockingQueue<Runnable> workQueue,//任务等待队列
ThreadFactory threadFactory,//线程创建工厂
RejectedExecutionHandler handler)//拒绝策略
最大核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程超时也会回收。
线程池所允许的最大线程数。当等待队列满,就会创建非核心线程来处理,核心线程数+非核心线程数最大为maximumPoolSize。
线程闲置超时时长。如果线程闲置时间超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
keepAliveTime的单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
等待线程执行的任务阻塞队列,当核心线程数满了,再添加任务时会先放到阻塞队列中,当队列满了才会创建非核心线程来处理任务。任务提交执行的流程如下
任务出队执行的规则依赖于具体的实现类,任务队列的常用实现类有:
线程创建工厂。用于指定为线程池创建新线程的方式,threadFactory可以设置线程名称、线程组、优先级等参数。可以使用Executors工具类中的方法获取,例如Executors.defaultThreadFactory()
当达到最大线程数且队列任务已满时需要执行的拒绝策略,常见的拒绝策略如下:
接收一个Runnable对象作为参数
submit()除了可以接收Runnable对象外,还可接收Callable对象作为参数,但最后都是封装为一个FutureTask对象再调用execute()方法去执行。然后submit()会将这个FutureTask对象返回,那么在主线程就可以通过这个FutureTask对象获知任务当前的状态,并可以通过future.get()方法阻塞等待获取任务执行结果。
ThreadPoolExecutor有一个AtomicInteger类型成员变量ctl,高3位存储线程池状态,低29位存储线程数量。
线程池有如下五种状态
线程池一旦被创建,就处于RUNNING状态,并且线程池中的初始线程数为0。可调用如下方法关闭线程池
shutdown():RUNNING -> SHUTDOWN
shutdownNow():直接进入STOP状态。
ThreadPoolExecutor对象的成员变量workers保存了当前所有工作线程,每个工作线程都是封装为了一个内部类Worker的对象
private final HashSet<Worker> workers = new HashSet<Worker>();
当调用execute()方法提交任务时,若线程池还没满,则调用addWorker()方法添加一个线程并启动线程执行Worker的run()方法
run()方法中执行了runWorker()方法,在执行完firstTask后,会循环通过getTask()方法从等待队列workQueue中取任务执行,以此实现了线程复用。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
...
try {
while (task != null || (task = getTask()) != null) {
...
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
}
...
Executors工具类中有一些用来创建预定义线程池的方法
但在《阿里巴巴 Java 开发手册》中并不推荐使用,原因有两点:
线程数是线程池中的重要参数,如果设置小了则不能充分利用CPU,并且会导致大量任务阻塞等待,如果设置大了则会导致大量的线程上下文切换影响整体效率。因此,我们需要根据这个线程池要负责的任务类型来合理设置最大线程数。任务可分为以下两种
这种任务消耗的主要是 CPU 资源,几乎不需要阻塞等待获取其他资源,能够充分利用CPU,因此可以将线程数设置为 N(CPU 核心数)+1。比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
这种任务运行时需要进行大量IO操作,而线程在内核处理 I/O 的时间段内不会占用 CPU,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程。具体数量根据任务执行期间平均等待时间来定,需要的IO操作越多等待时间越长,最大线程数就可以设置得越多,以充分利用CPU资源。
例如在Tomcat中处理HTTP请求的线程池默认最大线程数为200,因为HTTP请求处理时通常真正用于计算的时间可能很少,大多数时间可能在阻塞,如等待数据库返回数据、等待硬盘读写数据等。
线程数更严谨的计算的方法应该是:最佳线程数 = N(CPU 核心数)∗(1+WT(线程等待时间)/ST(线程计算时间)),其中 WT(线程等待时间)=线程运行总时间 - ST(线程计算时间)。
参考文章:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。