不管是在工作中还是面试中.掌握线程池对我们的帮助都非常大
使用线程池的好处:减少了每个任务的调用开销,当有大量异步任务时能提高性能,更容易管理线程,比如已完成任务的数量
使用线程池的坏处:暂时没发现
会介绍到的内容:
ThreadPoolExecutor:
线程池的实现类,通过ThreadPoolExecutor的构造器参数我们可以定义很灵活且适合应用场景的线程池
Executors:
线程池工具类,比较常用的是他提供的几个内置线程池的方法: (newFixedThreadPool,newSingleThreadExecutor,newCachedThreadPool),但我们点进去就可以看到他其实也就是在ThreadPoolExecutor上再封装一层而已,将创建线程池变的更灵活更简单
重点就是看下ThreadPoolExecutor如何创建线程池的,他的所有参数定义,再回头看一下Executors创建的四种线程池就比较简单了
首先看下他的构造器:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue 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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
核心线程仅在新任务到达时才会创建和启动,当然你可以预启动核心线程, prestartCoreThread 方法启动一个核心线程,如果所有核心线程都已启动该方法返回false当提交新任务时execute(Runnable),运行的核心线程数少于设置的核心线程总数时,即使其他线程处于空闲状态也会创建一个新的线程来处理该任务,
创建线程(threadFactory):
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());
}
};
还有一个我自己定义的线程工厂,我改变了系统默认的优先级
public class TestDefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private static final String TAG = "duo_shine";
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
TestDefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
// if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.MAX_PRIORITY);
return t;
}
}
任务的排队机制(注意排队机制非一成不变,他会受队列的策略影响):
队列的三种策略:(workQueue)
拒绝策略:(handler)
拒绝策略什么情况下触发?
当Executor关闭时,以及当任务超过maximumPoolSize和队列的最大容时,execute(Runnable)将拒绝在方法中提交的新任务,会在execute中调用reject方法,该方法内部就是调用了handler.rejectedExecution(command, this);此handler是RejectedExecutionHandler类型,他提供了四种预定义的策略:
Hook methods:
我们可以通过重写这些方法来操作任务的执行环境,例如,重新初始化ThreadLocals,收集统计信息或添加日志条目,此外还可以在terminated()方法中做Executor完全关闭后需要执行的任何特殊处理
比如下面这段代码:
/**
* Created by duo_shine on 2018
*/
public class TestThreadPoolExecutor extends ThreadPoolExecutor {
private static final String TAG = "duo_shine";
public TestThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public TestThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public TestThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public TestThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
/**
* 任务执行前
* @param t
* @param r
*/
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
boolean mainThread = isMainThread();
Log.d(TAG, "beforeExecute:" + mainThread);
Log.d(TAG, "beforeExecute:任务即将开始执行 : ");
}
/**
* 任务执行后
* @param r
* @param t
*/
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
boolean mainThread = isMainThread();
Log.d(TAG, "afterExecute:" + mainThread);
Log.d(TAG, "afterExecute:任务执行结束 : ");
}
@Override
protected void terminated() {
super.terminated();
Log.d(TAG, "terminated:Executor关闭 : ");
}
public boolean isMainThread() {
return Looper.getMainLooper().getThread().getId() == Thread.currentThread().getId();
}
}
shutdown和shutdownNow的区别
1.shutdown:其中先前提交的任务将被执行,但不会接受任何新任务。注意如果已经调用shutdown后再调用execute比如这样
mTpe.execute(runnable);
mTpe.shutdown();
mTpe.execute(runnable);
会直接走拒绝策略handler这是一个需要注意的点,如果你的拒绝策略是ThreadPoolExecutor.AbortPolicy()那就直接凉凉了
2.shutdownNow:尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。注意如果我们在做一个死循环任务,那么这个线程通过interrupt是停止不了的,具体可以看我另一篇博客interrupt能停止线程吗
Note: ThreadPoolExecutor的所有构造函数中的参数不可以传负数和null,否则将直接抛出异常
番外篇:
1.核心线程设置为0的情况:
在LinkedBlockingQueue策略下只会创建一个非核心线程来执行任务,这个线程在new Worker时传入null来创建一个线程(正常创建线程使用任务来创建一个新线程)
2.核心线程数究竟开启多少合适(需要考虑手机的性能):这里参照AsyncTask源码中的写法
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
官方的意思是希望核心池中至少有2个线程和最多4个线程,更喜欢比CPU数少1,以避免饱和
Executors: Executors中提供了一些快速创建线程池的方法,我们简单学习几个
Executors.newFixedThreadPool,看下他的构造器
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
分析一波…
nThreads用做核心参数和最大线程数,队列采用无界队列,也就是说如果使用这个线程池,比如我们传入5,那么该线程池最多同时只有5个线程同时执行任务,更多的任务将被放入无界队列中等待核心线程来执行,且线程在创建后如果没有更多后续任务不会自动销毁,除非shutdown
Executors.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
分析一波…
这里更简单了,该线程池最大只能有一个线程工作,且为核心线程,和newFixedThreadPool的区别在对任务的处理上没啥区别,但注意返回值类型不同,此线程池返回值只能使用ExecutorService,不可以重新配置了,但是newFixedThreadPool可以使用ThreadPoolExecutor作为返回的对象
Executors.newCachedThreadPool:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
分析一波…
核心线程数为0,而最大线程数是无界的,我们只需要注意他使用的队列策略,SynchronousQueue在上面已经分析过了,他不会存储任务,该线程池的工作机制是如果有空闲线程则使用空闲线程执行任务,如果没有空闲线程则创建新线程,注意无核心线程所以他的非核心线程的空闲等待时间是60秒,如果60秒线程没有接收到任务就销毁以回收资源,该线程池适用于大量短时任务
简单介绍了几个系统内置的线程池创建方法,当你熟悉ThreadPoolExecutor的使用时,我们可以自己构建适合自己业务场景的线程池
本来理论我都对应着日志或代码.但由于导致篇幅太长,所以只精简下来这些,有错请指出好以改正