Android开发过程线程的使用很常见,最常见的用法应该是如下所示new一个线程。
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
}
}
new Thread().start();
这样使用确实很简单方便直观。
但如果线程的数量很多的话,这样会出现大量的线程创建和销毁,不仅浪费大量的内存,还会降低系统的运行效率。
为了能够复用线程,避免大量的内存浪费,我们可以使用线程池来创建和使用线程。
一、线程池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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
从上面代码可以看到,构造方法中做了一些非空的判断,和初始化赋值。下面来看一下各个参数的含义:
- corePoolSize: 核心线程的大小。默认情况下,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,则不再创建,会把到达的任务放到缓存队列当中。除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,初始化时候会直接创建corePoolSize个线程。
- maximumPoolSize :最大线程数,表示线程池中最多可以创建多少个线程
- keepAliveTime:当线程数大于corePoolSize时,线程空闲后,保持存活的时间。如果线程数不超过corePoolSize,则keepAliveTime不起作用。除非调用了allowCoreThreadTimeOut(boolean)方法。
- unit: keepAliveTime 的时间单位
- workQueue: 缓冲队列,用来存储等待执行的任务。有以下几种:
- LinkedBlockingQueue
- SynchronousQueue
- DelayedWorkQueue
- threadFactory:线程工厂,负责创建工厂
-
handler:饱和策略,当到达线程数上限或工作队列已满时的拒绝处理逻辑。有以下四种策略。
- AbortPolicy:不处理,直接抛出异常。
- CallerRunsPolicy:将任务分给调用线程来执行
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
- DiscardPolicy:直接丢弃,不处理
分析完构造参数的含义,现在我们开始声明一个线程池,并且简单实用一下吧。
public static int corePoolSize = 3;
public static int maxPoolSize = 5;
public static long keepAliveTime = 60;
private static int mCount = 0;
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 20; i++) {
threadPoolExecutor.execute(mRunnable);
}
}
private static Runnable mRunnable = new Runnable() {
@Override
public void run() {
mCount++;
System.out.println("name:"+Thread.currentThread().getName() + " count = " + mCount);
}
};
输入结果:
name:pool-1-thread-1 count = 1
name:pool-1-thread-2 count = 2
name:pool-1-thread-1 count = 3
name:pool-1-thread-2 count = 4
name:pool-1-thread-1 count = 5
name:pool-1-thread-2 count = 6
name:pool-1-thread-1 count = 7
name:pool-1-thread-2 count = 8
name:pool-1-thread-1 count = 9
name:pool-1-thread-2 count = 10
name:pool-1-thread-1 count = 11
name:pool-1-thread-2 count = 12
name:pool-1-thread-1 count = 13
name:pool-1-thread-2 count = 14
name:pool-1-thread-1 count = 15
name:pool-1-thread-2 count = 16
name:pool-1-thread-1 count = 17
name:pool-1-thread-2 count = 18
name:pool-1-thread-1 count = 19
name:pool-1-thread-3 count = 20
从输出结果可以看出,执行20次线程任务,使用了3个线程完成任务。不需要创建20个线程,大大节约了内存。
为了方便我们使用线程池,Java JDK提供了4中简单的创建方式。
二、线程池的四种创建方式
- Executors.newCachedThreadPool();
- Executors.newFixedThreadPool();
- Executors.newSingleThreadExecutor();
- Executors.newScheduledThreadPool();
1、newCachedThreadPool()
创建一个带缓存的线程池,如果有缓存线程则复用缓存线程,没有则创建新的线程。
构造方法如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
- 核心线程数corePoolSize = 0,
- 最大线程数为 maximumPoolSize 为 int 的最大值,相当于无限大,
- 保活时间keepAliveTime = 60秒
- 阻塞队列workQueue使用的是SynchronousQueue 同步队列
- defaultHandler是一个AbortPolicy
使用:
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
if (i < 10)
Thread.sleep(500);
service.execute(mRunnable);
}
}
当 i 小于10的时候,每次循环让线程sleep 500ms。执行效果如下:
name:pool-1-thread-1 count = 1
name:pool-1-thread-1 count = 2
name:pool-1-thread-1 count = 3
name:pool-1-thread-1 count = 4
name:pool-1-thread-1 count = 5
name:pool-1-thread-1 count = 6
name:pool-1-thread-1 count = 7
name:pool-1-thread-1 count = 8
name:pool-1-thread-1 count = 9
name:pool-1-thread-1 count = 10
name:pool-1-thread-1 count = 11
name:pool-1-thread-2 count = 12
name:pool-1-thread-1 count = 13
name:pool-1-thread-2 count = 14
name:pool-1-thread-3 count = 15
name:pool-1-thread-1 count = 17
name:pool-1-thread-2 count = 16
name:pool-1-thread-4 count = 18
name:pool-1-thread-5 count = 19
name:pool-1-thread-6 count = 20
可以看到,前面10个循环因为加了500毫秒的暂停让线程执行完成,所以再次执行则会复用已经创建好的空闲线程,并没有重新创建新的线程。后面的循环则一直创建了新的线程,因为没有空闲线程。
使用场景:
因为newCachedThreadPool方式创建的线程池核心线程数为0,最大线程数几乎无穷大。所以newCachedThreadPool适合处理需要创建大量耗时任务的请求。
2、newFixedThreadPool()
创建指定长度的线程池,线程数量超过指定长度则会在队列中等待执行。
构造方法
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
从构造方法可以看出,核心线程数corePoolSize 和最大线程数maximumPoolSize是相等的。并且保活时间是0ms。使用的是LinkedBlockingQueue缓冲队列。
使用:
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(3);
for (int i = 0; i < 20; i++) {
if (i<10)
Thread.sleep(500);
service.execute(mRunnable);
}
}
运行结果:
name:pool-1-thread-1 count = 1
name:pool-1-thread-2 count = 2
name:pool-1-thread-3 count = 3
name:pool-1-thread-1 count = 4
name:pool-1-thread-2 count = 5
name:pool-1-thread-3 count = 6
name:pool-1-thread-1 count = 7
name:pool-1-thread-2 count = 8
name:pool-1-thread-3 count = 9
name:pool-1-thread-1 count = 10
name:pool-1-thread-1 count = 12
name:pool-1-thread-3 count = 13
name:pool-1-thread-2 count = 12
name:pool-1-thread-3 count = 15
name:pool-1-thread-1 count = 14
name:pool-1-thread-1 count = 18
name:pool-1-thread-1 count = 19
name:pool-1-thread-1 count = 20
name:pool-1-thread-3 count = 17
name:pool-1-thread-2 count = 16
我们指定线程数为3,前10个循环让线程sleep 500ms,可以看出newFixedThreadPool会创建新的线程来执行任务。
使用场景:
控制线程中最大并发数,超过最大并发时,等待执行
3、newSingleThreadExecutor
创建一个线程的线程池,线程池中只有一个线程,超过一个则等待执行
构造方法:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
从构造方法可以看见 核心线程数corePoolSize和最大线程数maximumPoolSize都为1。使用FinalizableDelegatedExecutorService()方法保证线程的唯一性
使用:
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 20; i++) {
service.execute(mRunnable);
}
}
运行结果:
name:pool-1-thread-1 count = 1
name:pool-1-thread-1 count = 2
name:pool-1-thread-1 count = 3
name:pool-1-thread-1 count = 4
name:pool-1-thread-1 count = 5
name:pool-1-thread-1 count = 6
name:pool-1-thread-1 count = 7
name:pool-1-thread-1 count = 8
name:pool-1-thread-1 count = 9
name:pool-1-thread-1 count = 10
name:pool-1-thread-1 count = 11
name:pool-1-thread-1 count = 12
name:pool-1-thread-1 count = 13
name:pool-1-thread-1 count = 14
name:pool-1-thread-1 count = 15
name:pool-1-thread-1 count = 16
name:pool-1-thread-1 count = 17
name:pool-1-thread-1 count = 18
name:pool-1-thread-1 count = 19
name:pool-1-thread-1 count = 20
可以看到所有的任务都是一个线程执行的,因为就只允许创建一个线程。
使用场景:
需要频繁创建线程对象,可以使用newSingleThreadExecutor,避免内存浪费。按顺序执行
4、newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务
构造方法
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
可以看出核心线程数由参数指定,最大线程数是 int 的最大值,使用 DelayedWorkQueue延迟队列
使用:
public static void main(String[] args) {
ExecutorService service = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 20; i++) {
service.execute(mRunnable);
}
}
执行结果:
name:pool-1-thread-1 count = 1
name:pool-1-thread-2 count = 2
name:pool-1-thread-2 count = 3
name:pool-1-thread-1 count = 4
name:pool-1-thread-2 count = 5
name:pool-1-thread-2 count = 6
name:pool-1-thread-3 count = 7
name:pool-1-thread-2 count = 8
name:pool-1-thread-1 count = 10
name:pool-1-thread-2 count = 11
name:pool-1-thread-3 count = 9
name:pool-1-thread-2 count = 13
name:pool-1-thread-1 count = 12
name:pool-1-thread-2 count = 15
name:pool-1-thread-3 count = 14
name:pool-1-thread-2 count = 17
name:pool-1-thread-1 count = 16
name:pool-1-thread-2 count = 19
name:pool-1-thread-3 count = 18
name:pool-1-thread-1 count = 20
支持延时和周期使用:
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
service.scheduleAtFixedRate(mRunnable, 2, 1, TimeUnit.SECONDS);
}
延迟2秒后,每隔1秒执行一次任务。执行结果
name:pool-1-thread-1 count = 1
name:pool-1-thread-1 count = 2
name:pool-1-thread-2 count = 3
name:pool-1-thread-2 count = 4
name:pool-1-thread-2 count = 5
name:pool-1-thread-2 count = 6
name:pool-1-thread-2 count = 7
name:pool-1-thread-1 count = 8
name:pool-1-thread-1 count = 9
...
使用场景:
计时任务