线程
线程在Android中是个很重要的概念,从用途来说,线程分为主线程与子线程,主线程用于处理界面相关事情,子线程用于执行耗时操作。除了Thread本身外,AysncTask,IntentService及HandleThread在安卓了都扮演着线程的角色。
AysncTask:底层封装了线程池和Handler,方便开发者在子线程中更新ui。
Handler:具有消息循环的线程,内部可以使用Handler。
IntentService:是一个服务,可执行后台任务,内部采用了HandlerThread来执行任务,任务执行完毕IntentService会自动退出。
传统线程的缺陷
1:在任务众多的情况下,系统要为每一个任务创建一个线程,而任务执行完毕后会销毁每一个线程,所以会造成线程频繁地创建与销毁。
2:多个线程频繁地创建会占用大量的资源,并且在资源竞争的时候就容易出现问题,同时这么多的线程缺乏一个统一的管理,容易造成界面的卡顿。
3:多个线程频繁地销毁,会频繁地调用GC机制,这会使性能降低,又非常耗时。
线程池
线程池概念来源于Java中的Executor,它是一个接口,真正的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池。
优点
1:重用线程池中的线程,线程在执行完任务后不会立刻销毁,而会等待另外的任务,这样就不会频繁地创建、销毁线程和调用GC。。
2:有效控制线程池的最大并发数,避免大量线程抢占资源出现的问题。
3:对多个线程进行统一地管理,可提供定时执行及指定间隔循环执行的功能。
ThreadPoolExecutor
ThreadPoolExecutor 有多个重载方法,但最终都调用了这个构造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
corePoolSize: 线程池中核心线程的数量。
maximumPoolSize:线程池中最大线程数量。
keepAliveTime:非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长。
unit:keepAliveTime这个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等。
workQueue:线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory:为线程池提供创建新线程的功能,这个我们一般使用默认即可。
**handler: 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
执行
ThreadPoolExecutor有两个方法可以供我们执行,分别是submit()和execute(),我们先来看看这两个方法到底有什么差异。
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);
}
submit()方法源码:
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
//还是通过调用execute
execute(ftask);
//最后会将包装好的Runable返回
return ftask;
}
//将Callable<T> 包装进FutureTask中
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
//可以看出FutureTask也是实现Runnable接口,因为RunableFuture本身就继承了Runnabel接口
public class FutureTask<V> implements RunnableFuture<V> {
.......
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
分析两个方法的源码得出
- submit()其实还是需要调用execute()去执行任务的,不同是submit()将包装好的任务进行了返回,他会返回一个Future对象。
- execute()方法中,不难看出addWorker()方法, 是创建线程(核心线程,非核心线程)的主要方法,而reject()方法为线程创建失败的回调。
所以,通常情况下,在不需要线程执行返回结果值时,我们使用execute 方法。 而当我们需要返回值时,则使用submit方法,他会返回一个Future对象。Future不仅仅可以获得一个结果,他还可以被取消,我们可以通过调用future的cancel()方法,取消一个Future的执行。 比如我们加入了一个线程,但是在这过程中我们又想中断它,则可通过sumbit 来实现。
简单例子
创建一个基本的线程池
//创建基本线程池
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(50));
使用线程池执行任务
for (int i = 0; i < 30; i++) {
final int finali = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
Log.e("TAG","run : "+finali+" 当前线程:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
threadPoolExecutor.execute(runnable);
}
结果是每3s打印三次日志。
ThreadPoolExecutor 执行任务时大致遵循如下流程:
- 1.如果线程池中的线程数未达到核心线程数,则会立马启用一个核心线程去执行。
- 2.如果线程池中的线程数已经达到核心线程数,且任务队列workQueue未满,则将新线程放入workQueue中等待执行。
- 3.如果线程池中的线程数已经达到核心线程数但未超过线程池规定最大值,且workQueue已满,则开启一个非核心线程来执行任务。
- 4.如果线程池中的线程数已经超过线程池规定最大值,则拒绝执行该任务,采取饱和策略,并抛出RejectedExecutionException异常。
上面例子中设置的任务队列长度为50,任务只有30个,任务队列未满,只走到第二个流程,不会开启额外的5-3=2个非核心线程,如果将任务队列设为25,则前三个任务被核心线程执行,剩下的30-3=27个任务进入队列会满超出2个,此时会开启2个非核心线程来执行剩下的两个任务,这是刚好达到线程池的最大线程数,假如还有任务,将会拒绝执行,抛出RejectedExecutionException异常。
线程池的分类
FixedThreadPool (可重用固定线程数)
//源码实现
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//创建及执行
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
//执行上述Demo的runnable
fixedThreadPool.execute(runnable);
结果:每3s打印5次任务,跟上面的基础线程池类似。
特点:参数为核心线程数,只有核心线程,无非核心线程无超时时长,并且阻塞队列无界。
适用:执行长期的任务,性能好很多
SingleThreadPool(单个核线的fixed)
//源码实现
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}//创建及执行
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//执行上述Demo的runnable
singleThreadExecutor .execute(runnable);
结果:每3s打印1次任务。
特点:只有一个核心线程,当被占用时,其他的任务需要进入队列等待,fixedThreadPool设置核心线程为1时就是SingleThreadPool。
适用:一个任务一个任务执行的场景
CachedThreadPool (按需创建)
//源码实现
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//创建及执行
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//执行上述Demo的runnable
cachedThreadPool.execute(runnable);
结果:3s后打印30次任务。
特点:没有核心线程,只有非核心线程,并且每个非核心线程空闲等待的时间为60s,采用SynchronousQueue队列。
结果分析:
- 因为没有核心线程,其他全为非核心线程,SynchronousQueue是不存储元素的,每次插入操作必须伴随一个移除操作,一个移除操作也要伴随一个插入操作。
- 当一个任务执行时,先用SynchronousQueue的offer提交任务,如果线程池中有线程空闲,则调用SynchronousQueue的poll方法来移除任务并交给线程处理;如果没有线程空闲,则开启一个新的非核心线程来处理任务。
- 由于maximumPoolSize是Integer.MAX_VALUE,无界的,所以如果线程处理任务速度小于提交任务的速度,则会不断地创建新的线程,这时需要注意不要过度创建,应采取措施调整双方速度,不然线程创建太多会影响性能。
- 从其特点可以看出,CachedThreadPool适用于有大量需要立即执行的耗时少的任务的情况。
适用:执行很多短期异步的小程序或者负载较轻的服务器
ScheduledThreadPool(定时延时执行)
//源码实现
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
特点:核心线程数量是固定的,非核心线程无穷大。当非核心线程闲置时,则会被立即回收。
ScheduledThreadPool也是四个当中唯一一个具有定时定期执行任务功能的线程池。它适合执行一些周期性任务或者延时任务。
适用:一个任务一个任务执行的场景
//创建及执行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//延迟5秒执行
scheduledExecutorService.schedule(runnable, 5, TimeUnit.SECONDS);
//延迟5s后启动,每1s执行一次 scheduledExecutorService.scheduleAtFixedRate(runnable,5,1,TimeUnit.SECONDS);
//启动后第一次延迟5s执行,后面延迟1s执行 scheduledExecutorService.scheduleWithFixedDelay(runnable,5,1,TimeUnit.SECONDS);
线程池中的任务的终止
一般线程执行完run方法之后,线程就正常结束了,线程池中的任务可以用下面的方式来实现:
利用 Future 和 Callable
步骤:
- 实现 Callable 接口
- 调用 pool.submit() 方法,返回 Future 对象
- 用 Future 对象来获取线程的状态。
private void cancelAThread() {
ExecutorService pool = Executors.newFixedThreadPool(2);
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("test");
return "true";
}
};
Future<String> f = pool.submit(callable);
System.out.println(f.isCancelled());
System.out.println(f.isDone());
f.cancel(true);
}
线程池的其他常用方法
1.shutDown() 关闭线程池,不影响已经提交的任务
2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值。
5.beforeExecute() - 任务执行前执行的方法
6.afterExecute() -任务执行结束后执行的方法
7.terminated() -线程池关闭后执行的方法
网友评论