线程池,就是一个线程的池子,里面有若干线程,它们的目的就是执行提交给线程池的任务,执行完一个任务后不会退出,而是继续等待或执行新任务。
线程池主要由两个概念组成,一个是任务队列,另一个是工作者线程,工作者线程主体就是一个循环,循环从队列中接受任务并执行,任务队列保存待执行的任务。
线程池的概念类似于生活中的一些排队场景,比如在火车站排队购票、在医院排队挂号、在银行排队办理业务等,一般都由若干个窗口提供服务,这些服务窗口类似于工作者线程,而队列的概念是类似排队的队伍。
ThreadPoolExecutor有多个构造方法,都需要一些参数,主要构造方法有:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
线程池的大小主要与四个参数有关:
unit:是keepAliveTime参数的时间单位,参数为TimeUnit的枚举,常见的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECOND(秒) 等。
一般情况下,有新任务到来的时候,如果当前线程个数小于corePoolSize,就会直接创建一个新线程来执行该任务,即使其他线程现在也是空闲的,也会创建新线程。
ThreadPoolExecutor要求的队列类型是阻塞队列BlockingQueue,它们都可以用作线程池的队列,比如:
如果队列有界,且maximumPoolSize有限,则当队列排满,线程个数也达到了maximumPoolSize,这时新任务来了,如何处理呢?此时,会触发线程池的任务拒绝策略。需要强调下,拒绝策略只有在队列有界,且maximumPoolSize有限的情况下才会触发。
默认情况下,提交任务的方法如execute/submit/invokeAll等会抛出异常,类型为RejectedExecutionException。不过,拒绝策略是可以自定义的,ThreadPoolExecutor实现了四种处理方式:
类Executors提供了一些静态工厂方法,可以方便的创建一些预配置的线程池,主要方法有:
public static ExecutorService newSingleThreadExecutor() {
return new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
只使用一个线程,使用无界队列LinkedBlockingQueue,线程创建后不会超时终止,该线程顺序执行所有任务。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
使用固定数目的n个线程,使用无界队列LinkedBlockingQueue,线程创建后不会超时终止。和newSingleThreadExecutor一样,由于是无界队列,如果排队任务过多,可能会消耗非常大的内存。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
它的corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE,keepAliveTime是60秒,队列为SynchronousQueue。含义是,当新任务到来时,如果正好有空闲线程在等待任务,则其中一个空闲线程接受该任务,否则就总是创建一个新线程,创建的总线程个数不受限制。对任一空闲线程,如果60秒内没有新任务,就终止。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPool的核心线程数量是固定的,由传入的corePoolSize参数决定,非核心线程数量可以无限大。非核心线程闲置回收的超时时间为10秒( DEFAULT_KEEPALIVE_MILLIS的值为10L)。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方
式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。Executors 返回的线程池对象的弊端如下:
//正例
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();//返回可用处理器的Java虚拟机的数量
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue taskQueue = new LinkedBlockingQueue();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES*2,
KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
taskQueue, new BackgroundThreadFactory(),
new DefaultRejectedExecutionHandler());
//反例
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//获取当前的cpu核心数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//线程池核心容量
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//线程池最大容量
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//过剩的空闲线程的存活时间
private static final int KEEP_ALIVE = 1;
//ThreadFactory 线程工厂,通过工厂方法newThread来获取新线程
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
//原子整数,可以在超高并发下正常工作
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//静态阻塞式队列,用来存放待执行的任务,初始容量:128个
private static final BlockingQueue sPoolWorkQueue =
new LinkedBlockingQueue(128);
/**
* 静态并发线程池,可以用来并行执行任务,尽管从3.0开始,AsyncTask默认是串行执行任务
* 但是我们仍然能构造出并行的AsyncTask
*
* CORE_POOL_SIZE 核心线程数
* MAXIMUM_POOL_SIZE 最大线程数量
* KEEP_ALIVE 1s闲置回收
* TimeUnit.SECONDS 时间单位
* sPoolWorkQueue 异步任务队列
* sThreadFactory 线程工厂
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory,
new ThreadPoolExecutor.DiscardOldestPolicy());
ThreadPoolExecutor实现了生产者/消费者模式,工作者线程就是消费者,任务提交者就是生产者,线程池自己维护任务队列。当我们碰到类似生产者/消费者问题时,应该优先考虑直接使用线程池,而非重新发明轮子,自己管理和维护消费者线程及任务队列。
计算机程序的思维逻辑 (78) - 线程池
Android 线程池的类型、区别以及为何要用线程池
必须要理清的Java线程池
【Java】线程池ThreadPoolExecutor实现原理
阿里Anddroid开发手册