线程池
线程池的特点
- 避免创建线程和销毁线程的资源消耗,创建线程需要分配内存,列入调度,线程切换时,还需要内存换页,如果CPU的缓存被清空,还需要重新从内存中读取信息
- 线程池能帮助管理线程
- 线程池能够提高相应速度,直接从线程池请求线程比创建线程的时间开销小,降低处理请求的延迟
- 能够避免无限创建线程引起的
OutOfMemoryError
线程池的创建
线程池的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
七个核心参数的作用:
- corePoolSize:线程池核心线程数最大值
- maximumPoolSize:线程池最大线程数,线程总数 = 核心线程数 + 非核心线程数
- keepAliveTime:线程池中非核心线程的空闲存活时间
- unit:线程空闲的存活时间单位
- workQueue:存放任务的阻塞队列
- threadFactory:设置创建线程的工厂
- handler:线程池的饱和策略,主要有四种类型
任务执行
执行流程
- 提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。
- 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
- 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
- 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
BlockingQueue workQueue 线程池中的任务队列
- SynchronousQueue:不储存元素的阻塞队列,当接受到任务时,直接交给线程处理,不保留任务,当没有空闲线程时,直接创建新线程处理任务。因为最大线程数可能会限制创建新线程,
maximumPoolSize
一般指定为Integer.MAX_VALUE
- LinkedBlockingQueue:基于链表实现的阻塞队列,当接受任务时,如果当前已经创建的核心线程小于核心线程数,则新建线程(核心线程)处理任务,当创建的核心线程等于核心线程数上限,进入队列等待。这个队列没有最大值限制,因此所有超过核心线程数的任务都被添加至该队列中,
maximumPoolSize
设定失效 - ArrayBlockingQueue:基于数组实现的有界阻塞队列,可以限定队列的长度,接收到任务时,如果当前已创建的线程数小于核心线程数,创建新线程(核心)执行任务,如果达到了,则任务入队等待,如果队列已满,则创建新线程(非核心)执行任务,如果总线程数到达
maximumPoolSize
,并且队列已满时,发生错误,或执行定义好的饱和策略 - DelayQueue:队列内元素(任务)必须实现Delayed接口,接收到任务时,首先先入队,只有到达了指定的延时时间,才会执行任务
拒绝策略
- AbortPolicy 抛出一个异常,组织线程池的正常工作,默认策略
- DiscardPolicy 直接丢弃任务,不进行处理
- DiscardOldestPolicy 丢弃队列中最老的任务,将当前任务继续交给线程池
- CallerRunsPolicy 交给调用者线程直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务
class MyTask implements Runnable {
private String id;
public MyTask(String id) {
this.id = id;
}
public void run() {
System.out.println(id);
}
}
public class RejectPolicy {
public static void main(String[] args) {
ExecutorService es = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
MyTask t1 = new MyTask("id:1");
MyTask t2 = new MyTask("id:2");
MyTask t3 = new MyTask("id:3");
MyTask t4 = new MyTask("id:4");
MyTask t5 = new MyTask("id:5");
MyTask t6 = new MyTask("id:6");
MyTask t7 = new MyTask("id:7");
es.execute(t1); // pool-1-thread-2: id:1
es.execute(t2); // pool-1-thread-2: id:2
es.execute(t3); // pool-1-thread-2: id:3
es.execute(t4); // pool-1-thread-2: id:4
es.execute(t5); // pool-1-thread-2: id:5
es.execute(t6); // main: id:6
es.execute(t7); // main: id:7
}
}
Java中常用线程池
FixedThreadPool
可重用固定线程数的线程池,超出的线程会在队列中等待
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
创建线程的方法
public static void main(String[] args) {
// 参数是要线程池的线程最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
}
FixedThreadPool
的corePoolSize
和maximumPoolSize
都设置为nThreads,即只有固定数量的核心线程,不存在非核心线程。keepAliveTime
为0L表示多余的线程立即终止,因为没有多余的线程,所以这个参数是无效的。FixedThreadPool
采用的任务队列是LinkedBlockingQueue
CachedThreadPool
CachedThreadPool根据需要创建线程的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
corePoolSize
是0,maximumPoolSize
是Integer.MAX_VALUE
,即该线程池没有核心线程,全部都是非核心线程,且没有上限。keepAliveTime
是60秒,空闲线程在60秒后会被自动销毁,此处用到的队列是阻塞队列SynchronousQueue
,这个队列没有缓冲区,其中最多只能存在一个任务,有新的任务则阻塞等待
SingleThreadExecutor
SingleThreadExecutor
是使用单个线程工作的线程池。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
该线程池总线程数和核心线程数都是1,所以就只有一个核心线程。该线程池采用链表阻塞队列LinkedBlockingQueue
,先进先出原则,保证任务按照入队顺序执行
ScheduledThreadPool
ScheduledThreadPool
是一个能实现定时和周期性任务的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
这里创建了ScheduledThreadPoolExecutor
,继承自ThreadPoolExecutor
,主要用于定时延时或者定期处理任务。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
corePoolSize
是传进来的固定值,maximumPoolSize
无限大,因为采用的队列DelayedWorkQueue
是无解的,所以maximumPoolSize
参数无效。该线程池执行如下:
当执行scheduleAtFixedRate
或者scheduleWithFixedDelay
方法时,会向DelayedWorkQueue
添加一个实现RunnableScheduledFuture
接口的ScheduledFutureTask
(任务的包装类),并会检查运行的线程是否达到corePoolSize
。如果没有则新建线程并启动ScheduledFutureTask
,然后去执行任务。如果运行的线程达到了corePoolSize
时,则将任务添加到DelayedWorkQueue
中。DelayedWorkQueue
会将任务进行排序,先要执行的任务会放在队列的前面。在跟此前介绍的线程池不同的是,当执行完任务后,会将ScheduledFutureTask
中的time变量改为下次要执行的时间并放回到DelayedWorkQueue
中。