android 线程池 ThreadPoolExecutor

来源:互联网 发布:如何设置网络共享权限 编辑:程序博客网 时间:2024/05/24 04:51

android 线程池 ThreadPoolExecutor

线程池作用:
1、提高资源利用率。由于线程池中的线程使可以重复利用的,所以达到了循环利用的目的,
2、提高响应速度。由于线程的创建也是需要开销的,线程池重复利用已经创建好的线程对象提高响应速度、节省开销
3、使用线程池则可以对线程进行统一的分配、管理、监控。线程属于稀缺资源,如果无限制的创建,不仅会消耗大量的资源还会大大降低系统的稳定性。。

多线程的应用场景:
1、常见的浏览器、Web服务,web处理请求,各种专用服务器(如游戏服务器)
2、servlet多线程
3、FTP下载,多线程操作文件
4、数据库用到的多线程,数据库的数据分析,数据迁移
5、分布式计算
6、tomcat,tomcat内部采用多线程,上百个客户端访问同一个WEB应用,tomcat接入后就是把后续的处理扔给一个新的线程来处理,这个新的线程最后调用我们的servlet程序,比如doGet或者dpPost方法
7、后台任务:如定时向大量(100W以上)的用户发送邮件;定期更新配置文件、任务调度(如quartz),一些监控用于定期信息采集
8、动作业处理:比如定期备份日志、定期备份数据库
9、异步处理:如发微博、记录日志
10、页面异步处理:比如大批量数据的核对工作
11、多步骤的任务处理,可根据步骤特征选用不同个数和特征的线程来协作处理,多任务的分割,由一个主线程分割给多个线程完成

线程池的工作原理:
1、线程池刚创建时,池里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们
2、调用execute()/submit()方法添加一个任务
3、当一个线程完成任务时,它会从队列中取下一个任务来执行
4、当一个线程空闲并超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

Executor框架
包括Executor,ExecutorService,Callable等基础接口和Executors,ThreadPoolExecutor等实现类

Executor框架的最核心的类是ThreadPoolExecutor,它是线程池的实现类,创建ThreadPoolExecutor一般使用Executors工厂模式创建,Executors类提供了一系列工厂方法用于创先线程池:public static ExecutorService newFixedThreadPool(int nThreads)创建固定数目线程的线程池,最多创建nThreads个线程,传入的任务数大于nThreads时不会创建新的线程,而是阻塞等待有空闲线程执行。public static ExecutorService newCachedThreadPool()创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60秒钟未被使用的线程。public static ExecutorService newSingleThreadExecutor()创建一个单线程的Executor。public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类

java.util.concurrent.ThreadPoolExecutor类中包含最全参数的构造函数:

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

线程池可以配置参数:
corePoolSize :
核心池的大小,核心线程会一直存活,即使没有任务需要执行。当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
如果调用了prestartAllCoreThreads()或者prestartCoreThread()方法,
会直接预先创建corePoolSize的线程,否则当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,
就会把到达的任务放到缓存队列当中
maximumPoolSize:
线程池最大线程数(包括corePoolSize),表示在线程池中最多能创建多少个线程,如果运行中的线程超过了这个数字,那么相当于线程池已满,
新来的任务会使用RejectedExecutionHandler 进行处理;
keepAliveTime:
表示线程没有任务执行时最多保持多久时间会终止,然后线程池的数目维持在corePoolSize大小;
如果允许核心线程超时allowCoreThreadTimeout=true,则线程池的线程数量可能最后为0
线程池回收线程只会发生在当前线程池中线程数量大于corePoolSize参数的时候,当线程池中线程数量小于等于corePoolSize参数的时候,回收过程就会停止)
unit:
参数keepAliveTime的时间单位;
workQueue:用于保存等待执行的任务的阻塞队列,阻塞队列:
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
DelayQueue: 阻塞的是其内部元素,DelayQueue中的元素必须实现 java.util.concurrent.Delayed接口,该接口只有一个方法就是long getDelay(TimeUnit unit),
返回值就是队列元素被释放前的保持时间,如果返回0或者一个负值,就意味着该元素已经到期需要被释放,此时DelayedQueue会通过其take()方法释放此对象,
DelayQueue可应用于定时关闭连接、缓存对象,超时处理等各种场景;
LinkedBlockingQueue:一个基于链表结构的阻塞队列,初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。
默认大小为Integer.MAX_VALUE容量的无界阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列
threadFactory:
线程工厂,主要用来创建线程,比如可以指定线程的名字, ThreadPoolExecutor会使用一个默认的ThreadFactory:DefaultThreadFactory
handler:
饱和策略(饱和策略指的就是线程池已满情况下任务的处理策略)使用的条件:
当线程数已经达到maxPoolSize,并且workQueue是有界队列并且已满(无界队列永远不会满)),会拒绝新任务
当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。
如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
具体的饱和策略:
ThreadPoolExecutor.AbortPolicy(默认) 处理程序遭到拒绝将抛出运行时RejectedExecutionException。
ThreadPoolExecutor.CallerRunsPolicy,调用者所在线程来运行该任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
ThreadPoolExecutor.DiscardPolicy, 删除不能执行的任务。
ThreadPoolExecutor.DiscardOldestPolicy,删除位于工作队列头部的任务,执行当前任务(如果再次失败,则重复此过程)
可以自己实现处理策略类,继承RejectedExecutionHandler接口即可,该接口只有一个方法:
void rejectedExecution(Runnable r, ThreadPoolExecutorexecutor);
参数默认值

corePoolSize=1workQueue的大小queueCapacity=Integer.MAX_VALUEmaximumPoolSize=Integer.MAX_VALUEkeepAliveTime=60sallowCoreThreadTimeout=false,核心线程超时关闭RejectedExecutionHandler handler=AbortPolicy()

ThreadPoolExecutor执行顺序:当提交一个新任务到线程池时,线程池的处理流程
当线程数小于核心线程数时,创建线程。
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
当线程数大于等于核心线程数,且任务队列已满
1、若线程数小于最大线程数,创建线程
2、若线程数等于最大线程数,抛出异常,拒绝任务
假设corePoolSize为3,队列大小为10,maximumPoolSize为6,则整个池的大小为10+6, 那么当加入20个任务时:
执行顺序:
首先执行任务1、2、3(corePoolSize 核心线程),然后任务4~13被放入队列。队列满后,任务14、15、16会被马上执行,而任务17~20则会抛出异常。
最终顺序:
1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13
(并不是先加入任务就一定会先执行:阻塞队列保存等待执行的任务,当线程数大于等于核心线程数,且任务队列已满,若此时线程池中线程数小于最大线程数,则线程池会创建新线程来处理任务)

ThreadPoolExecutor线程池的一些设置:
allowCoreThreadTimeOut():线程池将对包括“核心线程”在内的,没有任务分配的任何线程,在等待keepAliveTime时间后全部进行回收,则线程池的线程数量可能最后为0
prestartAllCoreThreads(): 可以在线程池创建,但还没有接收到任何任务的情况下,先行创建符合corePoolSize参数值的线程数:

向线程池提交任务
提交任务有execute()和submit()两个方法:
1、接收参数不同
execute()的参数是Runnable,
submit()参数可以是Runnable,也可以是Cable。
2、返回值不同
execute()没有返回值,
submit()有返回值Future。通过Future可以获取各个线程的完成情况,是否有异常,还能试图取消任务的执行

线程池的关闭
调用线程池的shutdown或shutdownNow方法来关闭线程池:
shutdown()是正常结束线程池,已经添加进去正在执行的线程正常执行,没添加的线程不会再添加。
shutdownNow()则是强制中断线程池里的线程,但是因为是通过interuppt()来执行的,所以会有局限性,另外该方法会返回未执行的任务。
通常调shutdown来正常关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow

线程池的监控
通过线程池提供的参数进行监控
taskCount:线程池需要执行的任务数量。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
largestPoolSize: 是一个动态变量,是记录Poll曾经达到的最高值,也就是 largestPoolSize<= maximumPoolSize
getPoolSize(): 获取当前线程池的线程数量,如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
getActiveCount(): 获取线程池中活动线程的数量
getCompleteCount(): 获取线程池中完成的任务数。

通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。
如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t){ }

配置线程池
根据任务特性,合理的配置线程池:
任务的性质:
CPU密集型任务:任务配置尽可能小的线程,参考配置NCPU+1个线程的线程池,Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
IO密集型任务:由于线程并不是一直在执行任务,则配置尽可能多的线程,参考配置2*NCPU
混合型任务:如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。
具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整

任务的优先级:高,中和低。
优先级不同的任务:可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

任务的执行时间:长,中和短。执行时间不同的任务:
可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。

任务的依赖性:是否依赖其他系统资源,如数据库连接。依赖数据库连接池的任务:
因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。

任务性质不同的任务可以用不同规模的线程池分开处理(不同规模的线程池跑不同类型的任务)。

有界队列,有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点(建议使用)。
无界队列,线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用。

设置示例:
需要根据几个值来决定
tasks :每秒的任务数,假设为500~1000,taskcost:每个任务花费时间,假设为0.1s,responsetime:系统允许容忍的最大响应时间,假设为1s
计算可得:
corePoolSize = tasks/(1/taskcost) = tasks*taskcout (1/taskcost = 10 — 1个线程1秒执行的任务数, 1秒总任务数/1个线程1秒执行的任务数 = 所需线程数)
corePoolSize = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50,根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可
queueCapacity = (coreSizePool/taskcost)*responsetime
queueCapacity = 80/0.1*1 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行。切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
maxPoolSize = (1000-80)/10 = 92(最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数

实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器cpu load已经满了,则需要通过升级硬件(呵呵)和优化代码,降低taskcost来处理。

原创粉丝点击