在Android中,因为主线程的限制,执行一些耗时操作都必须在子线程中执行,使用子线程的时候多数都直接new一个,之后把执行的结果通过handler传输给主线程。
new的线程,我们没办法进行管理,只能在执行结束后去做相应的操作,假设第一个创建的线程未执行完毕,继续new多个线程,线程之间会进行竞争,可能会因为占有过多的资源而导致oom,死机或者线程被强制干掉。毕竟大量的创建和销毁都会消耗系统资源。
使用线程池的好处
.1.避免因为重复的创建和销毁而导致系统造成的开销。
.2.线程并发的管控,避免无限制的创建线程,有效的管理线程。
.3.提高系统的响应速度,避免等待线程的创建,直接放入队列进行操作。
.4.方便处理一些需要同步的操作。
.5.可控线程数量,提供定时等功能。
一、线程池介绍
- 创建
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor();
- 构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> 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;
}
- 参数介绍
int corePoolSize
核心线程数:核心线程一直存活在线程池中,有新任务直接取线程进行操作。
threadPoolExecutor.allowCoreThreadTimeOut(true);的时候会根据keepAliveTime来执行超时策略,一旦超过超时的时间,闲置的核心线程会被终止。
int maximumPoolSize
线程池中所容纳的最大线程数(核心数+非核心数),如果活动的线程达到这个数量之后,后续的任务会被阻塞。
long keepAliveTime
非核心线程的闲置超时时长,如果要控制核心线程需allowCoreThreadTimeOut为true。
TimeUnit unit
时间单位
BlockingQueue<Runnable> workQueue
阻塞队列有四种:
ArrayBlockingQueue基于数组实现的阻塞队列,先进先出原则对队列中的元素进行排序。
LinkedBlockingQueue基于链表实现的阻塞队列,先进先出原则对队列中的元素进行排序。
SynchronousQueue内部没有任何容量的阻塞队列,有任务的话会新建一个线程,超时时长为60秒。
PriorityBlockingQueue 具有优先级的无限阻塞队列。
ThreadFactory threadFactory
为线程池提供创建线程的操作,这是一个接口,内部就Thread newThread(Runnable r);
RejectedExecutionHandler handler
也是一个接口,可以理解为由ThreadPoolExecutor调用接口里的方法处理不同的情况。调用条件是当队列已满并且活动线程已经是限定的最大值或者是无法执行成功的任务才会调。默认情况为AbortPolicy,就是直接抛出异常RejectedExecutionException。
{可选情况分类
CallerRunsPolicy 只用调用者所在线程来运行任务。
AbortPolicy 直接抛出RejectedExecutionException异常。
DiscardPolicy 丢弃掉该任务,不进行处理。
DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务。
} - 调用
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(3,5,20,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
Log.e("MainActivity", "execute方式");
}
});
线程池执行流程
1.如果线程数量没有达到核心的线程数量,启动一个核心线程来执行任务。
2.如果线程池中的线程数量已经超过核心线程数,这时候任务就会被插入到任务队列中排队等待执行。
3.如果任务队列已满,任务无法插入到任务队列中。这个时候如果线程池中的线程数量没有达到线程池所设定的最大值,那么这时候就会启动一个非核心线程来执行任务。
4.如果线程池中的数量达到了所规定的最大值,那么就会拒绝执行此任务,这时候就会调用RejectedExecutionHandler中的rejectedExecution方法来通知调用者。
二、 四种线程池类型
都是直接或间接配置ThreadPoolExecutor 实现的常见线程池。
- newFixedThreadPool
ExecutorService service = Executors.newFixedThreadPool(3);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
设置的参数就是所能容纳的最大线程数,设置的线程为核心线程。线程池空闲状态的时候,线程不会被回收,如果所有线程都有任务执行,新任务会处于等待状态,直到有线程空闲出来。
由于newFixedThreadPool只有核心线程,并且这些线程都不会被回收,也就是 它能够更快速的响应外界请求 。从下面的newFixedThreadPool方法的实现可以看出,newFixedThreadPool只有核心线程,并且不存在超时机制,采用LinkedBlockingQueue,所以对于任务队列的大小也是没有限制。
- newCachedThreadPool
ExecutorService service = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newCachedThreadPool方法在这里我们可以看出它的核心线程数为0,线程最大数量为Integer.MAX_VALUE,这个值是一个很大的数,也可以理解为任意大、
当线程池有任务的时候,就会创建一个线程来执行任务,如果任务执行完毕后,线程闲置时间超过60秒的话就会被回收掉,所以说在60秒后线程池中不存在任何线程,这个时候是不占有任何资源的。
- newScheduledThreadPool
ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
它的核心线程数是固定的,对于非核心线程几乎可以说是没有限制的,并且当非核心线程处于限制状态的时候就会立即被回收。
创建一个可定时执行或周期执行任务的线程池:
ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
service.schedule(new Runnable() {
public void run() {
System.out.println("延迟三秒操作"+Thread.currentThread().getName());
}
}, 3, TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延迟三秒后每隔2秒操作"+Thread.currentThread().getName());
}
}, 3, 2, TimeUnit.SECONDS);
schedule(Runnable command, long delay, TimeUnit unit)
:延迟一定时间后执行Runnable任务;
schedule(Callable callable, long delay, TimeUnit unit)
:延迟一定时间后执行Callable任务;
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
:延迟一定时间后,以间隔period时间的频率周期性地执行任务;
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit)
::与scheduleAtFixedRate()方法很类似,但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的。
- newSingleThreadExecutor
ExecutorService service = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newSingleThreadExecutor将所有的任务统一到一个线程中执行,所以在这个任务执行之间我
们不需要处理线程同步的问题。
总结
1.使用线程池需要看需求,如果是io密集任务,可以多设置线程来提高cpu的利用率,如果是cpu密集任务就是cpu个数+1的线程。
2.在需要统一管理线程的情况下,可以使用。
3.想避免过多的开销,可以使用。
4.想规定线程执行队列,可以使用。