一、为什么使用线程池
1.线程池吃好处
(1).对多个线程进行统一管理,避免资源竞争中出现问题
(2).(重点)对线程进行复用,线程在执行完任务后不会立刻销毁,而会等待另外的任务,这样就不会频繁的创建、销毁线程和调用GC
2.线程池适用的场景
(1)在项目中频繁的开启线程,需要多线程去处理不同的任务
(2)需要监控线程的运行状态
3.线程池运行规则
(1)如果线程池中的数量未达到核心线程的数量,则会直接启动一个核心线程来执行任务
(2)如果线程池中的数量已经达到或超过核心线程数,则任务会被插入到任务队列中等待执行
(3)如果(2)中的任务无法插入到任我队列中,由于任务队列已满,这时候如果线程数量未达到线程池规定的最大值,则会启动一个非核心线程来执行
(4)如果(3)中线程数量已达到线程池最大值,则会拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法通知调用者
二、线程池的实现ThreadPoolExecutor
1.构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory)
参数的含义:
corePoolSize: 线程池中核心线程的数量。
maximumPoolSize: 线程池中最大线程数量。
keepAliveTime: 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长。
unit: keepAliveTime这个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等。
workQueue: 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory: 为线程池提供创建新线程的功能,这个我们一般使用默认即可。
handler: 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
2.两个执行的方法
(1)execute()方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获得当前线程的生命周期对应的二进制状态码
int c = ctl.get();
//判断当前线程数量是否小于核心线程数量,如果小于就直接创建核心线程执行任务,创建成功直接跳出,失败则接着往下走.
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//判断线程池是否为RUNNING状态,并且将任务添加至队列中.
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//审核下线程池的状态,如果不是RUNNING状态,直接移除队列中
if (! isRunning(recheck) && remove(command))
reject(command);
//如果当前线程数量为0,则单独创建线程,而不指定任务.
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果不满足上述条件,尝试创建一个非核心线程来执行任务,如果创建失败,调用reject()方法.
else if (!addWorker(command, false))
reject(command);
}
(2)submit()方法
public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task);
//还是通过调用execute
execute(ftask);
//最后会将包装好的Runable返回
return ftask;
}
//将Callable 包装进FutureTask中
protected RunnableFuture newTaskFor(Callable callable) {
return new FutureTask(callable);
}
//可以看出FutureTask也是实现Runnable接口,因为RunableFuture本身就继承了Runnabel接口
public class FutureTask implements RunnableFuture {
.......
}
public interface RunnableFuture extends Runnable, Future {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
(3)结论
从上面两个方法的源码我们可以分析出几个结论,
submit()其实还是需要调用execute()去执行任务的,不同是submit()将包装好的任务进行了返回,他会返回一个Future对象。
从execute()方法中,不难看出addWorker()方法, 是创建线程(核心线程,非核心线程)的主要方法,而reject()方法为线程创建失败的回调。
所以,通常情况下,在不需要线程执行返回结果值时,我们使用execute 方法。 而当我们需要返回值时,则使用submit方法,他会返回一个Future对象。Future不仅仅可以获得一个结果,他还可以被取消,我们可以通过调用future的cancel()方法,取消一个Future的执行。 比如我们加入了一个线程,但是在这过程中我们又想中断它,则可通过sumbit 来实现。
三、Android 中常用的几种线程池
1.FixedThreadPool (可重用固定线程数)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
特点:参数为核心线程数,只有核心线程,无非核心线程,并且阻塞队列无界
创建
//创建fixed线程池
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
使用
/**
* fixed线程池
*/
mFixedPoolThread.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for(int i = 0;i<30;i++){
final int finali = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.d("Thread", "run: "+finali);
Log.d("当前线程:",Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
fixedThreadPool.execute(runnable);
}
}
});
结果为每2s打印5次任务,跟上面的基础线程池类似
2.CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
CachedThreadPool中是没有核心线程的,但是它的最大线程数却为Integer.MAX_VALUE,另外,CachedThreadPool是有线程超时机制的,它的超时时间为60秒。
由于最大线程数为无限大,所以每当添加一个新任务进来的时候,如果线程池中有空闲的线程,则由该空闲的线程执行新任务;如果没有空闲线程,则创建新线程来执行任务。
根据CachedThreadPool的特点,在有大量耗时短的任务请求时,可使用CachedThreadPool,因为当CachedThreadPool中没有新任务的时候,它里边所有的线程都会因为60秒超时而被终止
创建
//创建Cached线程池
final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
使用
/**
* cached线程池
*/
mCachedPoolThread.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for(int i = 0;i<30;i++){
final int finali = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.d("Thread", "run: "+finali);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
cachedThreadPool.execute(runnable);
}
}
});
结果:过2s后直接打印30个任务
3.SingleThreadPool(单个核线的fixed)
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
它的核心线程数量是固定的,但是非核心线程无穷大。当非核心线程闲置时,则会被立即回收。
ScheduledThreadPool也是四个当中唯一一个具有定时定期执行任务功能的线程池。它适合执行一些周期性任务或者延时任务。
创建
//创建Single线程池
final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
使用
/**
* single线程池
*/
mSinglePoolExecute.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for(int i = 0;i<30;i++){
final int finali = i;x
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.d("Thread", "run: "+finali);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
singleThreadExecutor.execute(runnable);
}
}
});
结果:每2s打印一个任务,由于只有一个核心线程,当被占用时,其他的任务需要进入队列等待。
四、终止线程池中的某个线程
一般线程执行完run方法之后,线程就正常结束了,因此有如下几种方式来实现:
1.利用 Future 和 Callable
步骤:
实现 Callable 接口
调用 pool.submit() 方法,返回 Future 对象
用 Future 对象来获取线程的状态。
private void cancelAThread() {
ExecutorService pool = Executors.newFixedThreadPool(2);
Callable callable = new Callable() {
@Override
public String call() throws Exception {
System.out.println("test");
return "true";
}
};
Future f = pool.submit(callable);
System.out.println(f.isCancelled());
System.out.println(f.isDone());
f.cancel(true);
}
2.利用 volatile 关键字,设置退出flag, 用于终止线程
public class ThreadSafe extends Thread {
public volatile boolean isCancel = false;
public void run() {
while (!isCancel){
//TODO method();
}
}
}
3.interrupt()方法终止线程,并捕获异常
public class ThreadSafe extends Thread {
@Override
public void run() {
while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
try{
//TODO method();
//阻塞过程捕获中断异常来退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕获到异常之后,执行break跳出循环。
}
}
}
}
参考: https://blog.csdn.net/qq_22393017/article/details/79356115
https://www.jianshu.com/p/7b2da1d94b42