线程池顾名思义就是存放线程的池子。要使用线程的时候直接从池子中取就可以了,这样有啥好处呢?咱们可以通过一个类比来理解。
在以前没有自来水的时候,都是到很远的地方挑水做饭,洗衣,但是每次要使用的时候都要花时间去挑水,等水挑来了可能累得已经不想煮饭洗衣。所以我们一般会在院子里面建造一个水池或者是放一个水缸,每次有空的时候把水装满,下次要用的时候,直接从水池或者是水缸中取。这样就可以在每次急于使用水的时候能及时用到,而不是还要花时间去挑水
线程池也是一样,先把一些线程创建出来放到池中,使用的时候直接取就可以了。因为创建线程和销毁线程是要耗时的。
(二)使用线程池有什么好处你是不是看过有些项目中的线程随便创建,到处在创建,Java的匿名线程的创建使用更是让这种风气盛行。
new Thread(new Runnable() {
@Override
public void run() {
//TODO:do something
}
}).start();
这种代码是不是很熟悉?其实在小型项目中这样写的确不会引起啥大问题,但是到大型项目中,这样的线程写多了会导致性能受到影响。这时线程池的作用就体现出来了,使用线程池可以减少线程在创建和销毁上耗费的时间以及系统开销。也可以解决资源不足的问题,因为创建线程也需要耗费空间的。线程池的好处总结起来就是三点:
1. 重用已经存在的线程,减少对象在创建,消亡的开销,能够优化性能
2. 可以有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多的资源竞争,避免线程阻塞
3. 线程池可以提供定时执行,定期执行,单线程,并发数控制等
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
corePoolSize:核心线程数,线程池的基本大小,会一直存活
maximumPoolSize:最大线程数:先吃池最大线程大小,当活动线程达到这个值后,后续的任务会被阻塞
keepAliveTime:空闲线程的存活时间,超过了这个时长,空闲的非核心线程就会被回收。
TimeUnit unit:他标记keepAliveTime的单位,比如是秒,毫秒啥的,比如keepAliveTime为1,unit为SECOND,就表示1秒
workQueue:任务队列,通过线程池的execute()方法提交的Runnable对象会存储在这个参数中,用于存放任务的阻塞队列
handler:当队列和最大线程数都满了之后的缓存策略。
在Android中的线程池常见的有四种:
- newCachedThreadPool:
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue(),
threadFactory);
}
线程池中只有非核心线程,最大线程数非常大。当所有线程都活动时会为新任务创建新线程,否则会使用空闲线程处理任务,但是线程池中的空闲线程只会有60秒到存活时间,过了就会被回收,它的优点就是任何任务都会被立即执行,比较适合执行大量耗时较少的任务。
- newFixedThreadPool:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
只有固定数量的核心线程,当所有的线程都在活动的时候,因为队列没有限制大小,所以新任务会等待执行,线程空闲时不会释放空工作线程,还会占用一定的系统资源,他的优点就是可以更快的响应外界的请求,因为线程一直是存活的,省去了创建线程和销毁线程的时间,当有空闲线程时新任务马上就可以得到处理
- newScheduledThreadPool:
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
核心线程数固定,非核心线程没有限制,但是空闲的线程会立即被回收,他的优点就是可以执行定时任务以及有固定周期的重复任务
- newSingleThreadExecutor:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
只有一个核心线程,所有的任务都在同一线程中按序完成,他的优点也很明显,那就是不需要处理线程同步的问题。
(五)线程池ThreadPoolExecutor的工作策略面试的时候这个点经常会被问到,ThreadPoolExecutor执行任务时会遵循如下规则:
(1)如果线程池中的数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
(2)如果线程池中的线程数量已经达到或者超过了核心线程的数量,那么任务会被插入任务队列中排队等待执行
(3)如果任务队列已经满了,无法将任务插入到任务队列中,这时候如果线程数量没有达到最大线程数,会立刻启动一个非核心线程来执行任务
(4)如果线程数量已经达到了最大线程数,那么就会拒绝执行此任务,然后ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExection方法通知调用者。
好的线程池能够很好的提高系统的整体性能,达到优化性能的目的,但是设计不好的线程池则可能会对系统有害无益。那么应该如何选择线程池呢?应该分场景来看
场景一:高并发和任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换,因为线程切换上下文会很耗时。
场景二:并发不高、任务执行时间长的业务要区分开看:
(1)假如是IO密集型的任务,也就是业务时间大多数都是集中在IO操作上,因为IO操作并不占用CPU,所以我们应该让CPU忙起来,这时候我们可以加大线程池中的线程数目,让CPU处理更多的业务
(2)假如是计算密集型任务,任务的执行时间大多数集中在计算操作上,减少线程上下文的切换,将线程池中的线程数设置得少一些,



