因为线程的创建和销毁都会伴随着系统的开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率。而且线程并发数量过多,抢占系统资源从而导致阻塞。所以需要对线程进行简单的管理,比如:延时执行,定时循环执行,这就需要线程池来进行管理。
线程池有以下好处:
Java1.5中引入的Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。
当向线程池提交一个任务时,线程池会按照以下的流程执行:
ThreadPoolExecutor执行execute方法分下面4种情况。
当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。默认情况下的策略为AbortPolicy。
可以通过setRejectedExecutionHandler来修改。线程池框架提供了4中饱和策略。
可以通过ThreadPoolExecutor来创建一个线程池,并且这个框架提供了四种不同类型的线程池,分别是:
①FixedThreadPool():
固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待。使用LinkedBlockingQuene作为阻塞队列,不过当线程池没有可执行任务时,也不会释放线程。
适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景。
②CachedThreadPool():
可以缓存的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列,和FixedThreadPool创建的线程池不同,CachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。
适用于执行很多的短期异步任务的小程序。
③SingleThreadExecutor();
单线程线程池,按顺序来执行线程任务里面的核心线程数和线程数都是1,内部使用LinkedBlockingQueue作为阻塞队列。每次只能处理一个任务,所以后面所有的任务都被阻塞在工作队列中,只能一个个任务执行。
适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
④ScheduledThreadPool():
初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。
适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景
ThreadPoolExecutor构造函数
这是四种线程池类型都是通过ThreadPoolExecutor框架来创建的,所以有必要了解一下ThreadPoolExecutor的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
创建一个线程池需要多个参数,分别是:
①int corePoolSize
该线程池中核心线程数最大值,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
②int maximumPoolSize
该线程池中线程总数最大值,如果当前阻塞队列满了,且继续提交任务,并且当前线程数小于maximumPoolSize,则创建新的线程执行任务。
③long keepAliveTime
该线程池中非核心线程闲置超时时长,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;
④TimeUnit unit
keepAliveTime的单位,TimeUnit是一个枚举类型。单位可以选择:天、小时、分钟、毫秒、微妙、纳秒等。
⑤BlockingQueue
用于保存等待执行的任务的阻塞队列。可以一下选择:
⑥RejectedExecutionHandler
饱和策略,共有四种饱和策略,上面已经提到过了,也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。
可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。
①execute()
用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
②submit()
用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线
程一段时间后立即返回,这时候有可能任务没有执行完。
可以通过调用线程池的shutdown()或shutdownNow()方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别:
当所有的任务都已关闭后,才表示线程池关闭成功,至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法
如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性: