线程池原理分析
一、概述
众所周知Android中可以使用线程池来管理线程,有非常多的框架一旦涉及到多线程都会优先采用线程池来处理线程,例如:EventBus、Retrofit2、RxJava、OkHttp等框架。那么Android为什么能使用线程池呢?其实这源于Java的java.util.cuncurrent包,从jdk1.5之后开始提供线程池。所以Android中的线程池来源于Java。
Android的线程池的概念来源于Java中的Executor,Executor是一个接口其真正的实现类是ThreadPoolExecutor,Java定义好的常见的四大线程池:CachedThreadPool,FixedThreadPool,SingleThreadPool,ScheduleThreadPool,都是直接或者简介利用ThreadPoolExecutor来实现的。
那么为什么要用线程池呢?
在操作系统中,CPU调度的最小单位是线程,而CPU调度线程是通过时间片轮转的方式来进行的。线程是一种有限的系统资源,频繁的创建和销毁会带来相当大的开销。而线程池除了可以避免上述情况以外还可以管理线程的最大并发数以及执行一些定时任务。
优点:
1.重用线程池中的线程,避免线程重复的创建和销毁带来的系统开销。
2.可以控制线程池的最大并发数,避免大量的线程同时抢占CPU资源而造成阻塞现象
3.可以简单的管理线程池中的线程,例如:定时任务、循环定时任务等。
二、线程池原理
在介绍线程池的原理之前先回过头开看一下上面提到的ThreadPoolExecutor,其提供了一系列的参数供开发者配置自己想要的线程池。
1 2 3 4 5 6 7 8 9 | public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this (corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); } |
参数介绍:
1.corePoolSize:线程池的核心线程数,默认情况下核心线程在线程池中会一直存活,即使他们处于空闲状态。如果用户主动设置了ThreadPoolExecutor.allowCoreThreadTimeOut=true,那么闲置的核心线程在等待新任务的到来时会有一个超时策略,超时时间由第三个参数keepAliveTime来指定。一旦闲置时间超过了keepAliveTime指定的时间,则核心线程会被销毁。
2.maximumPoolSize:线程池容纳的最大线程数数量,当活动的线程数达到这个数值后,后续的新任务将会被阻塞
3.keepAliveTime,非核心线程的超时时长,超过这个时间非核心线程就会被回收,如果ThreadPoolExecutor设置了allowCoreThreadTimeOut=true,则这个属性也就被应用到核心线程上。
4.unit,用于指定keepAliveTime的时间单位,可以设置毫秒、秒、分钟等值
5.workQueue,线程池中的任务队列,通过线程池的execute(runnable)方法,提交的Runnable对象会被存入这个队列中
6.threadFactory,为线程池提供创建新线程的功能
重要:ThreadPoolExecutor的执行规则如下:
1.如果线程池中的线程数量未达到核心线程的数量,则直接启用核心线程执行任务
2.如果线程池中的线程数量达到或者超过了核心线程的数量,则将新线程入队列等待执行。
3.如果线程池中的线程数量超过了核心线程的数量,且任务队列已满。此时如果线程池中的线程数量未超过线程池允许的最大线程数量,则直接开启一个非核心线程执行任务
4.如果线程池中的线程数量超过了核心线程数量,且任务队列已满,且线程池中的线程数量已经超过线程池允许的最大线程数,则拒绝此任务
下面说一下Java为我们封装好的四种线程池,例如:EventBus中就使用到了CachedThreadPool
1.FixedThreadPool
FixedThreadPool只有核心线程,线程数量固定,且核心线程在空闲的时候也不会被销毁。当所有的核心线程都处于活动状态时,新进来的任务会处于等待状态,知道线程池中的核心线程空闲出来再执行。由于核心线程即便处于空闲状态也不会被回收,这意味着此线程池能够更加快速的响应外接的请求。
其创建方式如下:
1 2 3 4 5 6 7 8 9 10 | /** * 设置核心线程数为100 */ ExecutorService newFixedThreadPool = Executors.newFixedThreadPool( 100 ); newFixedThreadPool.execute( new Runnable() { @Override public void run() { //执行任务 } }); |
2.CachedThreadPool
CachedThreadPool是一种线程数量不固定的线程池,其线程数量设置的值为:Integer.MAX_VALUE,基本上可以任务线程数可以无限的大。其只有非核心线程,且有超时机制,超时时长为60秒。当线程池中的线程都处于活动状态时,线程池会创建新的线程来出来新的任务。这种线程池的优点是:当线程池中的线程都处于空闲状态时,所有线程都会超时,这意味着在这种状态下线程池是不占用任何系统资源的,所以特备适合执行大量的且耗时较小的任务。
创建方式如下:
1 2 3 4 5 6 7 | ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); newCachedThreadPool.execute( new Runnable() { @Override public void run() { } }); |
3.ScheduledThreadPool
ScheduledThreadPool其核心线程数固定,非核心线程数没有限制,且当非核心线程空闲时会被立即回收。它比较适合执行定时任务和具有固定周期的重复任务。
1 2 3 4 5 6 7 8 9 10 11 | /** * 核心线程数100 * 延迟一秒执行 */ ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool( 100 ); scheduledThreadPool.schedule( new Runnable() { @Override public void run() { } }, 1000 , TimeUnit.SECONDS); |
4.SingleThreadPool
SingleThreadPool内部只有一个核心线程,且没有非核心线程。当线程池中的任务处于活动状态时,新任务会被插入队列等待,知道上一个任务完成。其实际意义在于同步所有的任务到一个线程,使这些任务之间不需要处理线程同步的问题。
实例代码:
1 2 3 4 5 6 7 | ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); singleThreadPool.execute( new Runnable() { @Override public void run() { } }); |
再补充一点Android中的线程形式:
1.HandlerThread:继承了Thread,其内部可以直接使用handler,因为在run方法中对looper进行了初始化Looper.prepare()和looper.loop(),源码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized ( this ) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = - 1 ; } |
2.IntentService:是一个抽象类,其继承了Service,且使用时必须派生子类。由于其封装了HandlerThread,所以它比较适合执行耗时任务。因为除了不需要重新开启一个线程外,由于它是service,天生的优先级就比较高,不容易被系统杀死。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super (looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } public IntentService(String name) { super (); mName = name; } public void setIntentRedelivery( boolean enabled) { mRedelivery = enabled; } @Override public void onCreate() { super .onCreate(); HandlerThread thread = new HandlerThread( "IntentService[" + mName + "]" ); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } |
3.AsyncTask(现在基本上用不到这个类了,不说了)
4.new Thread(){}.start或者new Thread(runnable).start
5.使用线程池的execute(runnable);来开启一个线程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库