您的位置:首页 > 移动开发 > Android开发

从api理解java/android线程池

2017-07-06 09:59 316 查看

一、开发中异步任务和多线程

1.一贯做法

android日常开发中经常会遇到异步任务和多线程,而我们一贯的做法是,new Thread().start()+Handler,要么就是AysncTask,虽然简单快捷,但是会有很多弊端。

2.弊端

a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导
致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。

3.引入线程池

相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵
塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
从某位博客大大那里借鉴来的

二、Java/Android线程池及用法

java的线程池框架在“java.util.concurrent”包中,打开api会发现,其中的内容是相当可观的。。。
 

1.顶级接口Executor

其中线程池的顶级接口是“Executor”,从严格意义上讲Executor并不是线程池,其内部只提供一个方法:
 
Executor接口并不是严格的要求execute()方法异步执行,实现类完全可以在调用线程执行command任务,例如:
public classMyExcutor
implementsExecutor {
    @Override
    public voidexecute(Runnable runnable) {
        //在调用线程执行该任务
        runnable.run();
        //新开线程,异步执行该任务
        newThread(runnable).start();
    }
}

2.线程池接口ExecutorService和ScheduledExecutorService

为什么要讲到这两个接口?
可以毫不夸张的说,这两个接口是线程池接口的祖宗级别,已经具有线程池的模型规范,各式各样的线程池类都是实现了这两个类,并进行扩展的。

1. ExecutorService

ExecutorService继承Executor接口,并进行了扩展,可以对执行的任务进行管理,如跟踪执行结果、添加、取消、关闭等功能,基本提供了实现了线程池所需的规范。可以通过实现扩展此接口达到想要的线程池功能。
重要的方法:
void

shutdown() :终止前允许之前已提交的任务正常执行

List<Runnable>

shutdownNow():拒绝添加任务,并试着中断正在执行的任务和等待执行的任务,返回未执行的任务列表

T Futrue<T>

submit(Callable<T> task):向线程池添加一个任务,并返回Futrue句柄,用于跟踪和控制任务task,注:此方法是异步执行,但是也可以阻塞执行:
submit.get().

Futrue<?>

submit(Runnable task):向线程池添加一个任务,并返回Futrue句柄,用于

跟踪和控制任务task,同上

T List<Future<T>>

invokeAll(Collection<? extends Callable<T>> tasks):执行task集合中的所有任务,并返回执行结果,会阻塞调用线程。

 

2. SheduledExecutorService

  ● public interface ScheduledExecutorService extends ExecutorService
SheduleExecutorService继承了ExecutorSerrvice接口,并定义延时和定期执行给定任务的规范,供实现类使用并扩展。
重要方法:
<V> ScheduledFuture<V>
schedule(Callable<V> callable,
long delay,TimeUnit unit)
创建并执行在给定延迟后启用的ScheduledFuture。 
ScheduledFuture<?>
schedule(Runnable command, long delay,TimeUnit unit)
创建并执行在给定延迟后启用的单次操作。
ScheduledFuture<?>
scheduleAtFixedRate(Runnable command,
long initialDelay, long period,TimeUnit unit)
创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay之后开始,然后是initialDelay+period ,然后是initialDelay + 2 * period ,等等。
 

3. 真正的线程池实现类ThreadPoolExecutor(常用)

 

1.继承关系

 
public class ThreadPoolExecutor extends
AbstractExecutorService
ThreadPoolExecutor类继承自抽象类AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口,但是对于ExecutorService接口的扩展并不多,仅仅多了2个方法,有兴趣可以自己研究一下,在这里不在讲解。
A. 使用ThreadPoolExecutor需要进行一系列的复杂配置,api文档中强烈推荐Executors类进行配置不同功能的ThreadPoolExecutor线程池(Executors是一个java提供的线程池工具类,后续会讲到)。如果Executors不能满足你的需求,api中也提供了手动配置指南,必须严格的按照官方指南配置,api文档中有详细配置指南。
B. ThreadPoolExecutor解决两个问题:
   1. 对于执行大量异步任务时,通过减少任务的调用开销而改善性能。
   2. 提供任务管理和任务统计功能。
C. 使用线程池,首先需要理解构造方法中的几个参数的含义:
 
2.ThreadPoolExecutor最简单的构造函数:

 

ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)
corePoolSize:核心池大小,即使线程池空闲,一会保留的线程数,除非主动调用
allowCoreThreadTimeOut()方法(如果超过定义的空闲时间,核心线程也会被销毁)。
maximumPoolSize:线程池允许创建的最大线程数。如果添加的任务超过核心线程,
则创建新的非核心线程,但不不会超过maximumPoolSize
keepAliveTime:当前线程数大于核心线程数的时候,如果线程空闲时间超过

keepAliveTime,则终止线程,知道线程数小于核心线程数

  unit:keepAliveTime的时间单位

workQueue:用于保存任务的队列,仅保存execute方法提交的Runnable任务(workQueue在源码中声明为:private BlockQueue<Runnbale> workQueue) 。
BlockQueue是一个队列接口,常用的实现子类有:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue
ArrayBlockingQueue:是一个经典的有界队列缓冲区(容量固定,不可扩充)
LinkedBlockingQueue:是无边界的缓冲队列,内次插入节点都是动态创建新节点,直到耗尽系统资源。
此处有个小问题:线程池中可通过submit()和execute()提交任务,workQueue队列仅仅保存execute方法提交Runnbale任务,而submit可提交Runnbale和Callable两种类型的任务,难道线程池不会讲Callable任务添加进队列?那岂不是submit提交任务无法管理了?
答案是否定的,通过源码会发现submit()内部也是调用的execute()方法
public
<T> Future<T> submit(Callable<T> var1) {
    if(var1 ==null) {
        throw newNullPointerException();
    }else
{
  // 此行代码将Callable转为了RunnbaleFuture
        RunnableFuture var2 =
this.newTaskFor(var1);
        //内部调用了execute方法
  this.execute(var2);
        return var2;
    }
}
从源码可看出,submit内部调用了execute方法,并将Callable接口转换为Runnble接口(RunnableFuture实现了Runnble接口)。

知识小扩展:其实ThreadPoolExecutor(线程池)为了管理任务,内部统一将任务包装成Callable的形式,以便可以拿到Future,进行取结果、或终止、或监听状态。那么这将设计一个问题,怎么将Runnable包装成Callable?我们都知道,java/android实现多任务的并发执行,并和用户有良好交互的方法有3种,Thread、Runnable、Callable,在此不深入讲解,有兴趣可以研究遗留的一个问题,能够更清晰的认识Thread、Runnbale、Callable三者的区别和用法。

3.自定义线程池用到的构造函数

ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

此构造函数,比上文讲解的多了2个参数,ThreadFactory和RjectedExecutionHandler

ThreadFactory:线程池创建执行任务的线程用的,如果自定义可以,加入一些自己的参数,如线程id,优先级等。

RjectedExecutionHandler:一个处理添加任务过量的机制,java提供了4个实现子类:

ThreadPoolExecutor.AbortPolicy:拒绝添加任务,并抛出异



ThreadPoolExecutor.CallerRunsPolicy:拒绝添加任务,并在

调用线程执行任务

ThreadPoolExecutor.DiscardOldestPolicy:拒绝最旧未处理

任务,然后重新添加

ThreadPoolExecutor.DiscardPolicy:拒绝添加任务,并不做

  处理  

4.常用方法介绍

void
allowsCoreThreadTimeOut(boolean value) 
允许核心线程超时,如果超出配置的线程空闲时间,核心线程也将被停止 
void
execute(Runnable command) 
提交任务 
protected void
finalize() 
当这个执行器不再被引用并且没有线程时,调用 shutdown 。
int
getActiveCount() 
返回正在执行任务的线程的大概数量。
long
getCompletedTaskCount() 
返回完成执行的任务的大致总数。
int
getCorePoolSize() 
返回核心线程数。
long
getKeepAliveTime(TimeUnit unit) 
返回线程保持活动时间,这是超过核心池大小的线程在终止之前可能保持空闲的时间量。
int
getMaximumPoolSize() 
返回允许的最大线程数。
int
getPoolSize() 
返回池中当前的线程数。
BlockingQueue<Runnable>
getQueue() 
返回此执行程序使用的任务队列。拿到此队列尽量不要做其他的不必要操作,否则会影响线程池运行。
long
getTaskCount() 
返回计划执行的任务的大概总数。
ThreadFactory
getThreadFactory() 
返回用于创建新线程的线程工厂。配置线程池是传入的Threadfactory 
boolean
isShutdown() 
如果此执行者已关闭,则返回 true 。
boolean
isTerminated() 
如果所有任务在关闭后完成,则返回 true 。
boolean
isTerminating() 
如果此执行者在
shutdown()或 shutdownNow()之后
终止 ,但尚未完全终止,则返回true。
int
prestartAllCoreThreads() 
启动所有核心线程,导致他们等待工作。
注:默认有任务添加时,才会创建线程,如果没有任务,是不开启线程的,此方法强制开启配置数量的线程
boolean
prestartCoreThread() 
启动核心线程,使其无法等待工作。
void
purge() 
尝试从工作队列中删除已取消的所有Future任务。
boolean
remove(Runnable task) 
如果此任务存在,则从执行程序的内部队列中删除此任务,从而导致该任务尚未运行。
void
setCorePoolSize(int corePoolSize) 
设置核心线程数。
void
setKeepAliveTime(long time,TimeUnit unit) 
设置线程在终止之前可能保持空闲的时间限制。
void
setMaximumPoolSize(int maximumPoolSize) 
设置允许的最大线程数。
void
setThreadFactory(ThreadFactory threadFactory) 
设置用于创建新线程的线程工厂。
void
shutdown() 
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
List<Runnable>
shutdownNow() 
尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。



4. java提供的线程池工具类Executors

自定义线程池(手动配置),还是一件比较麻烦的事情,java提供了一个工具类Excutors(上文也有提及,Executors就是一个提供配置好的线程池的工具类,都是通过直接或间接的配置ThreadPoolExecutor实现的),其中提供了4种不同功能的线程池(基本能满足日常开发所需),并且java官方力荐使用此工具类,并不鼓励自定义线程池。
1. Executors.newFixThreadPool
源码:

public staticExecutorService newFixedThreadPool(intvar0)
{
    return newThreadPoolExecutor(var0,var0,0L,
TimeUnit.MILLISECONDS, newLinkedBlockingQueue());
}
从源码可以看到,FixThreadPool的核心池和最大池一样,也就是说,它只有核心线程池,而且核心线程也不会被销毁,会一直保持,知道shutdownNow()方法

2. Executors.newSigleThreadPool
源码:
public staticExecutorService newSingleThreadExecutor() {
    return newExecutors.FinalizableDelegatedExecutorService(newThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
newLinkedBlockingQueue()));                           
}
顾名思义,SingleThreadPool内部维持一个核心线程,确保任务都在同一线程执行,不会出现并发操作。
3. Executors.newCacheThreadPool
源码:
public staticExecutorService newCachedThreadPool() {
    return newThreadPoolExecutor(0,2147483647,60L,TimeUnit.SECONDS,
newSynchronousQueue());
}
CacheThreadPool没有核心线程,最大线程池为Integer.MAX_VALUE(非常大。。。),当所有线程都执行任务时,会为新任务开启新线程,如果有空闲线程,则复用。而且线程空闲60s,会被销毁。
4. Executors.newSheduledThreadPool
源码:
public staticScheduledExecutorService newScheduledThreadPool(intvar0)
{
    return newScheduledThreadPoolExecutor(var0);
}
 
public
ScheduledThreadPoolExecutor(intvar1) {
    super(var1,2147483647,0L,TimeUnit.NANOSECONDS,
newScheduledThreadPoolExecutor.DelayedWorkQueue());
}
 
ScheduledThreadPool支持任务延迟执行和周期重复执行,核心线程固定,非核心线程Integer.MAX_VALUE,如果非核心线程没有任务执行,会被立即销毁
 

三、总结

线程池顶级接口Executor,其子接口ExecutorService 和 ScheduledExcutorService初建线程池模型,由他们的子类ThreadPoolExecutor 和 ScheduledThreadPoolExecutor具体实现线程池功能,开发中可以根据需求配置不同功能的线程池。为了方便开发使用,java提供了Executors工具类,提供4种不同功能的线程池,并且推荐使用。

本次技术分享,主要是带大家了解线程池常用类之间的关系,方便日后更深入的学习线程池。

本文到此结束,有什么不足或错误的地方,希望能批评指正,谢谢!

致谢:这篇文章大部分是来自研究api,也有部分是参考别人的博文,在此表示感谢!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息