在编程中经常会使用线程来异步处理任务,但是每个线程的创建和销毁都需要一定的开销。如果每次执行一个任务都需要开一个新线程去执行,则这些线程的创建和销毁将消耗大量的资源,并且线程都是“各自为政”的,很难对其进行控制,更何况有一堆的线程在执行。这时就需要线程池来对线程进行管理,通过线程池中线程的复用,减少创建和销毁线程的性能开销,也能控制线程池中的并发数,否则会因为大量的线程争夺CPU资源造成阻塞。
1、为什么要使用线程池
1.1不断创建线程的弊端:
- 创建了一个线程并执行,它在任务结束后GC会自动回收该线程
- 大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM
- 大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿
- 线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失
1.2使用线程池管理线程优点
- 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗
- 提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行
- 方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或OOM等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率
- 更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。
2、线程池的处理流程
3、线程池的种类
3.1 FixedThreadPool 可重用固定线程数的线程池
在Executors类中提供了创建FixedThreadPool的方法:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool的corePoolSize和maximunPoolSize都设置为创建FixedThreadPool指定的参数nThreads,也就是FixedThreadPool只有核心线程,并且数量是固定的,没有非核心线程。keepAliveTime设置为0L,意味着多余的线程会被立即终止。因为不会产生多余的线程,所以keepAliveTime是无效的参数。任务队列采用了无界的阻塞队列LinkedBlockingQueue。当线程数超过corePoolSize时,就将任务存储在任务队列中,当线程池有空闲线程时,则从任务队列中去取任务执行。
简单使用示例:
//1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("执行任务啦");
}
};
//3. 向线程池提交任务
fixedThreadPool.execute(task);
//4. 关闭线程池
fixedThreadPool.shutdown();
3.2 CachedThreadPool 根据需要创建线程的线程池
创建CachedThreadPool的代码如下所示:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
CachedThreadPool的corePoolSize为0,maximunPoolSize设置为Integer.MAX_VALUE,这就意味着CacheThreadPool没有核心线程,非核心线程是无界的。keepAliveTime设置为60L,则空闲线程等待新任务的最长时间为60s。在此用了阻塞队列SynchronousQueue,它是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。
简单使用示例:
// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task = new Runnable() {
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务
cachedThreadPool.execute(task);
// 4. 关闭线程池
cachedThreadPool.shutdown();
//当执行第二个任务时第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。
3.3 SingleThreadExecutor 使用单个工作线程的线程池
创建SingleThreadExecutor的代码如下所示:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
corePoolSize和maximumPoolSize都为1,意味着SingleThreadExecutor只有一个核心线程,其他参数都和FixedThreadPool一样。
简单使用示例:
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task = new Runnable() {
public void run() {
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务
singleThreadExecutor.execute(task);
// 4. 关闭线程池
singleThreadExecutor.shutdown();
3.4 ScheduledThreadPool 实现定时和周期性任务的线程池
创建ScheduledThreadPool的代码如下所示:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
ScheduledThreadPool继承自ThreadPoolExecutor,它主要用于给定延时之后的运行任务或者定期处理任务。ScheduledThreadPoolExectour的构造方法如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
从代码可以看出,ScheduledThreadPoolExecutor的构造方法最终调用的是ThreadPoolExecutor的构造方法。corePoolSize是传进来的固定数值,maximumPoolSize的值是Integer.MAX_VALUE。因为采用的是DelayedWorkQueue是无界的,所以maximumPoolSize这个参数是无效的。
简单使用示例:
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
// 4. 关闭线程池
scheduledThreadPool.shutdown();
学了以上四种类型的线程池,准备在Android studio 工具进行编码测试时,竟然出现红线报错提示:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。(前提是已安装阿里编码规约插件)。
也就是说,以上四种创建线程池的方式,是不符合编程规范的。
原因在于:
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
- CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
4、通过ThreadPoolExecutor创建线程池
参数作用如下所示:
- corePoolSize:核心线程数。默认情况下线程池是空的,只有任务提交时才会创建线程。如果当前运行的线程数少于corePoolSize,则会创建新线程来处理任务;如果等于或者多于corePoolSize,则不再创建。
- maximumPoolSize:线程池允许创建的最大线程数。如果任务队列满了并且线程数小于maximumPoolSize,则线程池仍旧会创建新的线程来处理任务。
- keepAliveTime:非核心线程闲置的超时时间。超过这个时间则回收
- TimeUnit:keepAliveTime参数的时间单位
- workQueue:任务队列。如果当前线程数大于corePoolSize,则将任务添加到此任务队列中。该任务队列是BlockingQueue类型的,也就是阻塞队列。
- ThreadFactory:线程工厂
- RejectedExecutionHandler:饱和策略
(1)AbortPolicy:默认策略,在需要拒绝任务时抛出RejectedExecutionException。
(2))CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
(3)DiscardPolicy:不能执行的任务,并将该任务删除。
(4)DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。
ThreadPoolExecutor创建线程池示例:
public class ThreadPoolProxy {
private volatile ThreadPoolExecutor mExecutor;
/**
* 核心线程数
*/
private int mCorePoolSize;
/**
* 最大线程数
*/
private int mMaximumPoolSize;
/**
* 保持时间
*/
private long mKeepAliveTime;
public ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long keepAliveTime) {
super();
mCorePoolSize = corePoolSize;
mMaximumPoolSize = maximumPoolSize;
mKeepAliveTime = keepAliveTime;
}
/**
* 创建唯一的线程池
*/
private ThreadPoolExecutor initThreadPoolExecutor() {
if (null == mExecutor) {
synchronized (ThreadPoolProxy.class) {
if (null == mExecutor) {
TimeUnit unit = TimeUnit.MILLISECONDS;
//(无界队列)可变的阻塞队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingDeque<>();
//默认线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();
//异常捕获器,弃任务并抛出RejectedExecutionException异常。
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
mExecutor = new ThreadPoolExecutor(
mCorePoolSize,
mMaximumPoolSize,
mKeepAliveTime,
unit,
workQueue,
threadFactory,
handler);
}
}
}
return mExecutor;
}
/**
* 执行任务
*
* @param task
*/
public void execute(Runnable task) {
initThreadPoolExecutor();
mExecutor.execute(task);
}
/**
* 移除未执行任务,已经执行的不会被移除
*
* @param task
*/
public void remove(Runnable task) {
initThreadPoolExecutor();
mExecutor.remove(task);
}
}