1.创建线程池
1.1 线程池相关类
安卓线程池相关的接口与类的关系图如下:
其中:
(1)Executor、ExecutorService和ScheduledExecutorService为接口,之间是继承关系
(2)ThreadPoolExecutor 是最常用的实现类
(3)Executors是一个工厂工具类,提供了返回值类型为ExecutorService、ScheduledExecutorService的线程池对象,以及ThreadFactory类型的对象
最常用的线程池创建方法为:
(1)用Executors中的静态方法创建,例如:
ExecutorService executorService = Executors.newFixedThreadPool(2);
(2)直接new一个线程池对象,例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(2));
1.2 Executors
该工具类中提供了如下创建线程池的方法,按返回值类型分为两大类:
1.ExecutorService:
(1) newFixedThreadPool() //线程池中线程数量在创建时指定
(2) newSingleThreadExecutor() //单个线程的线程池
(3) newCachedThreadPool() //缓存线程池,线程数量不固定,接到新任务且没有空闲线程时创建新线程执行任务,线程空闲60s时关闭。
(4) …
2.ScheduledExecutorService:
(1) newScheduledThreadPool() //创建固定大小的线程,可以延迟或定时的执行任务
(2) newSingleThreadScheduledExecutor() //单个线程的线程池,可以延迟或定时的执行任务
(3) …
1.3 ThreadPoolExecutor
该类包含三种创建线程池的构造方法:
(1)
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(2));
(2)
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(2), Executors.defaultThreadFactory());
(3)
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(2),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
他们综合起来一共有七个参数:
1.int corePoolSize, 核心线程数,当任务提交时,如果线程池没有空闲线程,且当前线程数小于核心线程数,就创建一个线程来执行该任务
2.int maximumPoolSize, 最大线程数,当阻塞队列已满,如果当前线程数小于最大线程数,则创建新线程执行任务
3.long keepAliveTime, 线程空闲时间,当前线程数大于核心线程数或 allowCoreThreadTimeOut(true)开启线程超时关闭开关,则空闲超出这个时间后关闭线程
4.TimeUnit unit, 时间单位
5.BlockingQueue workQueue, 阻塞队列,
6.ThreadFactory threadFactory, 生成线程的工厂
7.RejectedExecutionHandler handler 当线程数已达最大线程数,又收到新任务,就采取这个策略处理。这里的处理策略有四种:
(1)ThreadPoolExecutor.AbortPolicy 默认策略,直接丢弃任务并抛出 RejectedExecutionException 异常
(2)ThreadPoolExecutor.CallerRunsPolicy 由调用者线程处理该任务
(3)ThreadPoolExecutor.DiscardOldestPolicy 丢弃阻塞队列头的任务,然后重试执行该任务
(4)ThreadPoolExecutor.DiscardPolicy 只抛弃任务,不做任何操作
注意:使用该方法创建线程池后,线程池维护线程的策略为:
1.4 ThreadPoolTaskExecutor
这个类是spring包下提供的线程池类,不是原生安卓库。
该类将ThreadPoolExecutor进行了封装实现的,底层仍调用ThreadPoolExecutor的方法。
这个类可以用xml配置创建线程池所需的参数,参数的含义与ThreadPoolExecutor类似。创建线程的决定策略也和ThreadPoolExecutor相同,在此不再详述。
2.阻塞队列
在介绍阻塞队列之前,首先要了解ReentrantLock和Condition这两个类。
他们之间的关系图如下:
2.1 ReentrantLock
ReentrantLock重入锁,是实现Lock接口的一个类,在编程中经常用到。
他支持重入性,能够对共享资源重复加锁,即当前线程获取锁后,可以再次获取该锁,计数加一。释放该锁一次,计数减一,当计数等于0时,表示该线程释放了该锁,其他线程才能获取该锁。
java关键字synchronized隐式支持重入性,通过同样的计数方式实现重入。不同的是,ReentrantLock不仅支持重入,还支持公平锁和非公平锁两种方式。
ReentrantLock提供了两种构造方法:
(1)默认构造方法:
默认构造非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
(2)传入true构造公平锁,传入false构造非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.1.1 FairSync和NonfairSync
从类图可以看出,FairSync和NonfairSync均实现了Sync的lock()
并提供了tryAcquire()方法请求锁。其中NonfairSync的tryAcquire()通过调用Sync的nonfairTryAcquire方法实现。
均继承了Sync的tryRelease()来释放锁
ReentrantLock持有私有成员Sync,所以ReentrantLock对外的接口均通过调用sync对象的方法实现对锁的控制。通过构造公平或非公平的ReentrantLock对象,实现方法的多态。还能调用AbstractQueuedSynchronizer继承来的方法实现底层调用。
2.1.2 获取锁
下面分析一下两种锁获取时的源码:
(1)NonfairSync
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //状态等于0时其他线程才能获取锁
if (compareAndSetState(0, acquires)) {//如果当前同步状态为0时,原子性地将状态设为acquires
setExclusiveOwnerThread(current);//将运行线程设为当前线程,成功获取锁
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //如果持有该锁的线程还是当前线程,说明需要重入锁
int nextc = c + acquires; //锁计数加一
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc); //更新锁计数
return true;
}
return false;//获取锁失败
}
(2)FairSync
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && //当任务链表中当前任务没有前置节点(即当前任务是链表头的任务时),才分配锁
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false; //当前任务不是链表头任务,则获取锁失败
}
hasQueuedPredecessors()的判断逻辑是是否公平获取锁的唯一区别
2.1.3 释放锁
不管是否公平获取锁,释放锁调用的方法相同
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //锁计数减一
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true; //计数为0,锁完全释放,返回true
setExclusiveOwnerThread(null);
}
setState(c); //计数不为0,锁未被释放,更新计数,返回false
return free;
}
2.1.4 公平锁和非公平锁的优缺点
(1)公平锁通过对当前任务是否为链表头任务的判断,保证了先请求的线程先获取到锁,保证线程执行顺序。
但判断需要时间,性能下降。
(2)非公平锁不需要做判断,提供了最大的吞吐量。
但有可能有的线程永远无法获取到锁,造成“饥饿”现象。
2.2 Condition
Condition是一个接口,抽象类AbstractQueuedSynchronizer的内部类ConditionObject实现了这个接口
Condition的实例化过程如下:
(1)由new ReentrantLock()获取ReentrantLock对象
(2)用ReentrantLock对象调用newCondition()方法,因为ReentrantLock对象持有Sync私有对象,通过该对象调用Sync的newCondition()方法
(3)由于Sync继承了AbstractQueuedSynchronizer抽象类,该类包含了ConditionObject内部类,所以new ConditionObject()返回一个ConditionObject对象,该对象可以向上自动类型转换为Condition对象
至此才得到Condition对象
2.3 阻塞队列
阻塞队列与普通队列的最大不同点,在于阻塞队列能够阻塞添加和阻塞删除:
(1)阻塞添加: 当阻塞队列元素已满时,队列会阻塞加入元素的线程,直队列元素不满时才重新唤醒线程执行元素加入操作。
(2)阻塞删除: 在队列元素为空时,删除队列元素的线程将被阻塞,直到队列不为空再执行删除操作
创建线程池时,需要的阻塞队列种类包括:
Queue | 底层结构 | 容量 | 阻塞策略 | ReentrantLock数 | Condition数 |
---|---|---|---|---|---|
ArrayBlockingQueue | 数组 | 容量构造时指定 | 先进先出 | 1,插入移除互斥 | 2 |
LinkedBlockingDeque | 双向链表 | 默认Integer.MAX_VALUE | 链表头尾均可插入和移除 | 1,插入移除互斥 | 2 |
LinkedBlockingQueue | 单链表 | 默认Integer.MAX_VALUE | 先进先出 | 2,插入和移除各一把锁,可同时执行 | 2 |
DelayQueue | 优先队列 | - | 元素是有序的,必须实现Delayed接口(继承Comparable接口),最小先出队,出队前延迟getDelay()返回的时间 | 1,插入移除互斥 | 1 |
PriorityBlockingQueue | 数组 | 默认11,最大Integer.MAX_VALUE - 8 | 元素可排序,最小的元素先出队 | 1,插入移除互斥 | 1 |
SynchronousQueue | 无缓存结构 | 默认1 | 出队阻塞直至元素入队,入队阻塞到元素出队 | 无 | 无 |
注意:
阻塞队列通过ReentrantLock实现锁
利用Condition实现线程的阻塞与唤醒
采用数组作为底层结构时,插入或删除元素不会产生或销毁任何额外的对象
采用链表作为底层结构时,插入或删除元素会产生Node对象,大批量处理数据时对系统内存和GC有影响
采用两把锁可以让插入和移除同时执行,提供最大的吞吐量
2.4 阻塞队列的入队与出队
阻塞队列提供的方法有:
(1)入队:
add(E e) : 添加成功返回true,失败抛IllegalStateException异常
offer(E e) : 成功返回 true,如果此队列已满,则在到达指定的等待时间之前等待可用的空间,该方法可中断
put(E e) :将元素插入此队列的尾部,如果该队列已满,则阻塞
(2)出队
remove(Object o) :移除指定元素,成功返回true,失败返回false
poll() : 获取并移除此队列的头元素,若不能获取,在指定的等待时间前一直等到获取元素,超过时间后结束,该方法可中断
take():获取并移除此队列头元素,若没有元素则一直阻塞。直到有元素将唤醒等待线程执行移除操作
(3)获取元素
element():获取但不移除此队列的头元素,没有则抛出异常NoSuchElementException
peek():获取但不移除此队列的头;如果此队列为空,则返回 null。
可见,主要实现阻塞功能的方法为put(E e)方法和take()方法
3.调用线程池
创建线程池之后,向线程池提交异步任务的方法有两个:execute和submit,他们均能提交任务给线程池执行。提交的任务分为两种:Runnable和Callable。
3.1 Runnable和Callable
他们都是一个接口,在提交任务时,任务类需要实现这一接口。
Runnable任务需要覆写run()方法,Callable任务需要覆写call()方法
两者的区别在于:
run()无返回值,因此Runnable任务是无返回值的任务
call()有返回值,因此Callable任务是有返回值的,通常与Future配合使用。
3.2 execute()
execute()只能提交Runnable任务,无返回值。
由于run()无返回值,execute()也无返回值。
提交任务后,新的线程就和主线程脱离了关系,当然可以设置一些变量来获取到线程的运行结果。
当线程的执行过程中抛出了异常通常来说主线程也无法获取到异常,只有通过ThreadFactory主动设置线程的异常处理类才能感知到提交的线程中的异常。
例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());//初始化线程池
executor.execute(new Runnable() {
@Override
public void run() { //无返回值
try {
Log.i(TAG, "run; " + Thread.currentThread().getId()); //输出当前线程ID
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
3.3 submit()
submit()既可以提交Runnable任务,也可以提交Callable任务
(1)提交Runnable任务:
虽然run()无返回值,submit()仍然会返回一个Future对象,存储任务执行结果。
当任务执行成功时,该Future对象get()返回null
当任务出现异常时,get()返回异常信息。这是Runnable任务获取异常信息的一种方式。
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
Future future = executor.submit(new Runnable() {
@Override
public void run() {
try {
Log.i(TAG, "run; " + Thread.currentThread().getId());
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
try {
Log.i(TAG, "future.get():" + future.get() + "0");
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
输出为:
future.get()返回为null
(2)提交Callable任务:
Callable任务指定了String类型,所以call()的返回值为String,submit返回一个Future
任务执行成功时,future.get()获取到call()的返回结果,即"call"字符串
任务出现异常时,future.get()获取到异常信息
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Log.i(TAG, "call; " + Thread.currentThread().getId());
Thread.sleep(400);
return "call";
}
});
try {
Log.i(TAG, "future.get():" + future.get() + "0");
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
输出为:
总结来看:
(1)execute()只能提交Runnable任务,无返回值,不便于与主线程进行交互和捕获异常;
(2)submit()既可以提交Runnable任务,也可以提交Callable任务,均返回Future对象,Runnable任务的Future能捕获异常,不能获得返回值,Callable任务的Future能捕获异常,也能获得返回值。
3.4 Future和FutureTask
上面用 Future来接收任务的返回结果,实际上还可以用FutureTask。
他们的关系为:
Future是一个顶层接口,提供了如下方法:
- V get() :获取异步执行的结果,如果未执行完成,此方法会阻塞直到异步计算完成。
- V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果未执行完成,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出TimeoutException异常。
- boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
- boolean isCanceller() :如果任务完成前被取消,则返回true。
- boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(…)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(…)方法将返回false。mayInterruptRunning参数指定是否尝试中断执行中的线程。
RunnableFuture接口继承了Future和Runnable,FutureTask实现了RunnableFuture接口,因此FutureTask实际上是Future的实现类,它才能直接实例化对象。
下面是用FutureTask接收线程执行结果的例子:
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
Log.i(TAG, "call; " + Thread.currentThread().getId());
Thread.sleep(400);
return "call";
}
}); //实例化FutureTask对象
executor.submit(futureTask);//提交任务
try {
Log.i(TAG, "futureTask.get():" + futureTask.get() + "0"); //获取执行结果
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
因为FutureTask也实现了Runnable的run()方法,所以它可以看作一个Runnable任务,可以用execute或者submit方法提交任务。这和上一小节的结论一致。
FutureTask和Future获取任务结果的用法相同,均能获取返回值和捕获异常。
FutureTask提供了两种构造方法,用Callable和Runnable都能构造他:
(1)
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
(2)
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
用Runnable 构造时,result表示任务执行结果。如果不需要获取Runnable任务的执行结果,可以这样构造:
Future<?> f = new FutureTask<Void>(new Runnable() {
@Override
public void run() {
}
}, null);
3.5 get()方法的超时机制
FutureTask和Future都能获取异步任务的执行结果,都用get()方法获取。
实际上get()方法有两种用法:
(1)futureTask.get(); //无超时机制,异步任务未完成时一直阻塞当前线程
(2)futureTask.get(1000, TimeUnit.MILLISECONDS); //有超时机制,如果阻塞时间超过设定的timeout时间,该方法将抛出TimeoutException异常。
完成的实例代码为:
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());//初始化线程池
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
Log.i(TAG, "call; " + Thread.currentThread().getId());
Thread.sleep(4000); //该任务需要4s完成
return "call";
}
});
executor.submit(futureTask);
try {
Thread.sleep(1000);//主线程任务需要1s完成
futureTask.get(2000, TimeUnit.MILLISECONDS);//等待异步任务2s,超过2s时抛TimeoutException
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
futureTask.cancel(true);//在超时时可以取消任务,如果不取消,即使超时,异步线程仍会继续执行到任务完成
}
这里异步任务和主任务并行执行1s,此时主任务进入等待状态,异步任务继续执行。等待2s后主线程捕获TimeoutException异常,主线程尝试取消异步任务后结束,异步任务收到取消命令结束。如果不取消,异步任务还需执行1s,才能结束。
4.线程池的关闭
shutdown()和shutdownNow()均能关闭线程池,他们之间的异同为:
功能 | 如何阻止新线程 | 返回值 | 对已提交任务影响 | |
---|---|---|---|---|
shutdown() | 阻止新线程提交,正在执行的线程继续执行,空闲的线程进行interrupt(),阻塞队列中的任务继续往线程池中添加直至为空 | 将状态设为SHUTDOWN,若提交将抛出RejectedExecutionException | void | 无 |
shutdownNow() | 阻止新线程提交,试图对池中所有线程进行interrupt(),不再处理阻塞队列中的线程,而是将其作为返回值返回 | 将状态设为STOP,若提交将抛出RejectedExecutionException | Runnable类型的List | 有 |
4.1shutdown()
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN); //改变状态,阻止新任务提交
interruptIdleWorkers(); //interrupt空闲线程
onShutdown(); // 表示线程池将要关闭
} finally {
mainLock.unlock();
}
tryTerminate(); //关闭线程池
}
4.2 shutdownNow()
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);//改变状态,阻止新任务提交
interruptWorkers(); //interrupt所有线程
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();//关闭线程池
return tasks; //返回阻塞队列中的任务列表
}