线程池的好处
1.频繁的创建和销毁线程,会带来性能的问题。线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失。
2.线程池方便管理线程,定时执行线程,间隔执行线程。线程池能控制线程的最大并发数量,避免大量抢占资源导致的阻塞现象。
ThreadPoorExecutor构造方法
threadPoolExecutor = new ThreadPoolExecutor(3,5,1, TimeUnit.SECONDS,new LinkedBlockingDeque(128));
for (int i = 0; i < 30; i++) {
final int finalI = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
Log.e("ThreadPoolExecutor", "rune: " + finalI);
Log.e("ThreadPoolExecutor", Thread.currentThread().getName());
}
};
threadPoolExecutor.execute(runnable);
}
corePoolSize:核心线程数,线程池正常情况下保持的线程数,大户人家“长工”的数量。
maximumPoolSize:最大线程数,当线程池繁忙时最多可以拥有的线程数,大户人家“长工”+“短工”的总数量。
keepAliveTime:空闲线程存活时间,没有活之后“短工”可以生存的最大时间。
TimeUnit:时间单位,配合参数 3 一起使用,用于描述参数 3 的时间单位。
BlockingQueue:线程池的任务队列,用于保存线程池待执行任务的容器。
ThreadFactory:线程工厂,用于创建线程池中线程的工厂方法,通过它可以设置线程的命名规则、优先级和线程类型。
RejectedExecutionHandler:拒绝策略,当任务量超过线程池可以保存的最大任务数时,执行的策略。
第一个参数corePoolSize 线程池中核心线程的数量,第二个参数maximumPoolSize 线程池中最大线程数量,
第三个参数keepAliveTime 非核心线程的超时时长,表示线程没有任务执行时最多保持多久时间会终止,第四个参数workQueue 线程池中的任务队列。
参数7:RejectedExecutionHandler
拒绝策略:当线程池的任务超出线程池队列可以存储的最大值之后,执行的策略。
默认的拒绝策略有以下 4 种:
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。
线程池的默认策略是 AbortPolicy 拒绝并抛出异常。
参数5:BlockingQueue
阻塞队列:线程池存放任务的队列,用来存储线程池的所有待执行任务。
它可以设置以下几个值:
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
比较常用的是 LinkedBlockingQueue,线程池的排队策略和 BlockingQueue 息息相关。
ThreadPoorExecutor执行任务的顺序
(corePoolSize -> workQueue -> maximumPoolSize)
- 当未超过核心线程数时,就直接创建一个核心线程去执行任务。
- 当超过核心线程数,就将任务加入到workQueue的任务队列中等待
- 当任务队列中任务添满时候,在不超过最大线程数的情况下启动线程去处理任务
- 当线程数量超过最大线程数时,RejectedExecutionHandler对象通知调用者
Android中常用几种线程线程池
因为Java doc不建议直接使用ThreadPoorExecutor,Java提供了以下几种方式使用线程池,具体实现来看,它们底层实际上也是调用了 ThreadPoolExecutor,只不过参数都已配置好了。
一般以下的封装都不合适才会自己封装
一般需要根据任务的类型来配置线程池大小: 如果是 CPU 密集型任务,就需要尽量压榨 CPU,参考值可以设为 NCPU+1 ,
如果是 IO 密集型任务,参考值可以设置为 2*NCPU 当然,这只是一个参考值,
具体的设置还需要根据实际情况进行调整,比如可以 先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来 进行适当调整。
1 FixedThreadPool
核心线程数和最大线程数相同,一个无限的任务队列,就是说线程池一直有固定的线程数处理务。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 30; i++) {
final int finalI = i;
Runnable runnable = new Runnable(){
@Override
public void run() {
SystemClock.sleep(3000);
Log.e("fixedThreadPool", "run: "+ finalI);
Log.e("fixedThreadPool", Thread.currentThread().getName());
}
};
fixedThreadPool.execute(runnable);
2 CachedThreadPool
没有核心线程,最大线程数为2^31-1,线程空闲60秒后被回收,任务队列SynchronousQueue是一个不存储的,所以这个线程的特点是只要任务一来,马上就有线程去执行。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 30; i++) {
final int finalI = i;
Runnable runnable = new Runnable(){
@Override
public void run() {
SystemClock.sleep(2000);
Log.e("cachedThreadPool", "run: "+ finalI);
Log.e("cachedThreadPool", Thread.currentThread().getName()); }
};
cachedThreadPool.execute(runnable);
SystemClock.sleep(1000);
3 SingleThreadPool
线程池中只有一个核心线程,按顺序执行队列中的任务
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
4 ScheduledThreadPool
该线程最大的特点就可以延迟执行
//第一次延迟initialDelay秒,以后每次延迟delay秒执行一个任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable() {
@Override
public void run() {
Log.d("google_lenve_fb", "run: ----");
}
};
scheduledExecutorService.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS);
线程池其他常用功能
1.shutDown() 关闭线程池,不影响已经提交的任务
2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值