读书笔记,以下内容来自:《实战Java高并发程序设计》和《Java编程的逻辑》
概述
大体结构如下:
分为三部分。
第一部分叙述什么是线程池以及Java中对线程池的支持。
第二部分探讨线程池的内部实现。
第三部分简析线程池的可拓展性。
下面是本系列的第一部分
线程复用
多线程的软件设计方法确实可以最大限度地发挥多核处理器的计算能力。但是若不加控制和管理地随意使用线程,对系统的性能反而会产生不利的影响。
- 首先,线程是一种轻量级的工具,但其创建和关闭仍然需要花费时间,如果每一个小的任务都创建一个线程,极有可能会出现创建和销毁线程所占用的时间大于该线程真实工作所消耗的时间的情况。
- 其次,线程本身也需要占用内存空间,大量的线程会抢占宝贵的内存资源,给GC的回收带来很大的压力,如果处理不当,还有可能导致OutOfMemory。
因此,对线程的使用要掌握一个度。
什么是线程池
为了避免系统频繁地创建和销毁线程,可以让创建的线程进行复用。
类似于数据库连接池,在线程池中,也总有几个活跃的线程。当你需要使用线程时,可以从池子中随便拿一个空闲线程,当完成工作时,并不急着关闭线程,而是将其退回到线程池,以便其他用户使用。
简而言之,使用线程池之后,创建线程,变成了从线程池中取一个空闲线程;回收线程,变成了将线程归还线程池中。
线程池一般用于执行多个不相关联的耗时任务,没有多线程的情况下,任务顺序执行,使用了多线程的话,可以让多个不相关联的任务同时执行。
JDK对线程池的支持
为了更好地控制多线程,JDK提供了一套Executor框架,其本质就是一个线程池。
Java并发包中的线程池的实现类是ThreadPoolExecutor,它继承自AbstractExecutorService,实现了ExecutorService。
构造方法
先来看ThreadPoolExecutor的构造方法,它的构造方法有很多,但主要是:
public ThreadPoolExecutot(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其中,threadFactory和handler分别表示创建线程的工厂和任务拒绝策略,一般而言是不需要的。
参数介绍
- corePoolSize: 核心线程个数,但是,并不是一开始就创建这么多线程。实际上,线程池刚创建时,并不会创建任何线程。
- maximumPoolSize: 指定了线程池的最大线程个数
- keepAliveTime:除了核心线程之外的空闲线程在线程池中的存活时间,目的是为了释放多余的线程资源。也就是说,一个非核心线程,在空闲等待新任务时,会有一个最长等待时间,如果超过这段时间,仍然没有新任务,就会被终止。该值如果为0,则表示所有线程都不会超时终止。
- unit: keepAliveTime的时间单位
- workQueue: 任务队列,存放因线程数目不足而暂时无法处理的任务
- handle:任务拒绝策略。
它们都是ThreadPoolExecutor的public静态内部类,都实现了RejectedExecutionHandler接口。
工厂方法
ThreadPoolExecutor表示一个线程池,而Executors类则作为线程池工厂,从中可以获取具有特定功能的线程池。
产生线程池的工厂方法:
- newFixedThreadPool( )方法:返回一个固定数量的线程池。当有一个新任务提交时,线程池中若有空闲线程,则立即执行;如果没有空闲线程,则将其放入到一个任务队列中,待有线程空闲,便处理在任务队列中的线程。
- newSingleThreadExecutor( )方法:返回一个只有一个线程的线程池。若有多于一个的任务被提交到线程池,则会将任务放入任务队列中,待线程空闲,按照先入先出的顺序执行
- newCachedThreadPool( )方法:返回一个可根据实际情况调整线程数量的线程池。该线程池中线程的数量不确定。如果有空闲线程可以复用,就优先使用可复用的线程。否则创建新的线程处理任务。
- newSingleThreadScheduledExecutors( )方法:返回一个ScheduledExecutorsService对象,线程池的大小为1。ScheduledExecutorService接口在ExecutorService接口的基础上拓展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或周期性地执行某个任务。
- newScheduledThreadPool( )方法:同上,但是其线程池大小可以指定。
计划任务
与其他几个线程池不同,ScheduledExecutorService并不一定会立即安排执行任务,它其实是起到了计划任务的作用。
其中,sheduleAtFixedRate( )和scheduleWithFixedDelay( )会对任务进行周期性的调度。但是二者有些微的区别。
- 对于FixedRate而言,任务调度的频率是一定的。它是以上一个任务的结束时间为起点,之后的period时间,调度下一次任务。即,本次任务开始执行的时间和下一次任务开始执行的时间,间隔period。
- FixedDelay则是在上一个任务结束后,经过delay时间进行任务调度。本次任务执行完后,经过delay时间,再执行下一次任务。
- 若周期太短,对FixedRate。就会在本次任务结束后,直接调用下一次任务;但FixedDelay则依旧会间隔固定的时间,再执行下一次。
注意
如果任务本身出了异常,那么后续的所有执行都会被中断。也就是说,如果想保持任务的连续稳定执行,就需要做好异常处理。