在Java里,线程池的运用场景很多,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。
目录
线程池的好处
-
降低资源消耗。通过重复利用已创建的线程降低了创建线程和销毁线程的资源消耗
-
提高了响应速度。当有任务时,任务可以不用等线程的创建就可以执行
- 提高了线程的可管理性。
线程池的工作流程
当你向线程池提交了一个任务后,线程池判断核心线程池里的线程是否都在执行任务,如果不是,则创建一个新的线程去执行这个任务。如果核心线程都在执行任务再判断工作队列是否已满,如果没满,就把任务存储在工作队列里。如果工作队列已满,就判断线程池的线程是否已满,如果满了则执行饱和拒绝策略,如果没满则创建新的线程去完成任务。
线程池的组成元素
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
当你需要创建一个线程池的时候需要上面的几个参数
corePoolSize:核心线程池的大小也可以理解为线程池的基本大小,当你提交一个任务给线程池的时候,线程池就会创建一个线程来执行任务,即使其他空闲的核心线程能够执行任务也会去创建新的核心线程去执行,等到完成预热即需要执行的任务数大于线程池基本大小的时候就不会再创建了。
maximumPoolSize:线程池所允许创建的最大线程数,如果工作队列满了,并且创建的线程数小于最大线程数,线程池就会再创建新的线程执行任务,如果工作队列是无界(LinkedBlockingQueue),那么这个参数就没有什么实际效果了。
keepAliveTime:线程池的工作线程空闲后,保持存活的时间,当任务比较多且每个任务的执行时间比较短的时候,可以将时间调大,以此提高线程的利用率。
TimeUnit :线程存活时间的单位。
workQueue:工作队列,用来保存等待执行的任务的阻塞队列。
- ArrayBlockingQueue:这是一个基于数组结构的有界阻塞队列,按FIFO方式对元素进行排序,
- LinkedBlockingQueue:这是一个基于链表结构的阻塞队列,无界。
- SynchronousQueue:这是一个不存储元素的阻塞队列,相当于每洗一个碗就要将其烘干,不会洗完后放在碗架上再去烘干
- PriorityBlockingQueue:这是一个带有优先级的无限阻塞队列。
ThreadFactory :设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置名字
RejectedExecutionHandler:饱和拒绝策略,当队列与线程池都满了,就说明线程池处于一种饱和状态,就需要采用一种策略来处理提交的新任务。一般默认是AbortPolicy。
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者所在的线程执行任务。
- DiscardOldestPolicy:丢弃队列中最老的一个任务,然后执行当前任务。
- DiscardPolicy:不处理,直接丢掉。
向线程池提交任务
向线程池提交任务有两个方法,execute()与submit();
execute()方法用于提交无返回值的任务,所以你无法去判断这个任务是否被线程池执行成功
submit()方法用于提交有返回值的任务,线程池会返回一个future类型的对象。可以用这个对象去判断任务是否执行成功。可以通过future的get()方法来获取返回的值。
关闭线程池
关闭线程池有两种方式,shutdown()与shutdownNow()去关闭线程池。它们都是通过遍历线程池中的工作线程,然后调用线程的interrupt()方法来中断线程,所以无法响应中断的线程将会无法终止。shutdownNow是先将线程池的状态设置为STOP,然后再去尝试停止每一个正在执行或者暂停的线程,并且返回等待任务的列表。而shutdown是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行的的线程。
Java自带的四种线程池
Java里推荐里4种线程池,分别是:FixedThreadPool、SingleThreadExecutor、CachedThreadPool、ScheduledThreadPoolExecutor。
-
FixedThreadPool被称为可重用固定线程数的线程池,它的corePoolSize与maximumPoolSize均被设置为创建线程池时指定的参数nThreads.当线程池的线程数大于corePoolSize时,keepAliveTime被设置为0,这意味着有多余的空闲线程将会被禁止。
执行过程:如果当前的运行线程数<corePoolSize,就创建新线程来执行任务,当前运行的线程数=corePoolSize时,就将任务加入LinkedBlockingQueue,核心线程完成正在执行的任务后,会在循环中,不断地在LinkedBlockingQueue里去获取任务来执行。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
当线程池中的线程数达到corePoolSize时,新的任务就会在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。因为,此处的工作队列是无界队列,所以maximumPoolSize与keepAliveTime将会被无效化,而且运行中的FixedThreadPool在不调用shutdown()与shutdownNow()时,将不会拒绝任务。
- SingleThreadExecutor是使用单个工作线程的Executor,它的corePoolSize与maximumPoolSize均被设置为1,它也是使用LinkedBlockingQueue作为工作队列。
执行过程:如果当前运行的线程的数量<corePoolSize,就创建新线程来执行任务,前运行的线程数=corePoolSize时,就将任务加入LinkedBlockingQueue,核心线程完成正在执行的任务后,会在循环中,不断地在LinkedBlockingQueue里去获取任务来执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- CachedThreadPool是一个会根据需要来创建新线程的线程池,它的corePoolSize被设置为0,maximumPoolSize设置为Integer.MAX_VALUE,可以理解为maximumPool是无界的,它的工作队列是没有容量的SynchronousQueue,这意味着主线程提交任务的速度高于maximumPool中线程的处理速度时,它会不断地去创建新的线程,在极端情况下,它会因为创建过多的线程而耗尽CPU的资源
执行过程:首先执行SynchronousQueue.offer(Runnable task),如果当前的maximumPool中有空闲的线程正在执行SynchronousQueue.poll方法,那么主线程执行的offer操作与空闲线程的poll操作配对成功,主线程把任务交给空闲线程去执行,如果maximumPool为空,或者当前没有空闲线程的时候,CachedThreadPool会创建一个新的线程执行任务,当这个新线程创建完毕后,会执行SynchronousQueue.poll方法,这个poll操作会让空闲线程去等待主线程的offer操作,如果60s之内,主线程给了offer操作,就会配对成功,如果没给的话,这个空闲线程会被终止。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- ScheduledThreadPoolExecutor主要用来在给定的延迟之后运行任务,或者定期的执行任务。它与前面的三个线程池不太一样,前面三个线程池是通过ThreadPoolExecutor工厂类来创建的,而ScheduledThreadPoolExecutor是通过Executor工厂类来创建的,它适用于需要多个后台线程来执行周期性任务,它里面的队列是DelayQueue,DelayQueue是由优先级队列实现的无界阻塞队列,支持延迟获取元素,因为工作队列是无序的,所以它的maximumPoolSize值为无效的。
执行过程:从DelayQueue中获取已经到期的ScheduledFutureTask,再执行这个任务,执行完毕后将这个任务的time改为下一次要执行的时间,再放回DelayQueue