Android中常见的4种线程池的理解(转)
转:https://blog.csdn.net/l540675759/article/details/62230562
转:https://blog.csdn.net/seu_calvin/article/details/52415337
线程
在了解线程池之前,先给大家介绍下线程的概念:
先看一个烧水的例子,图中看电视是主线,用户想在看电视的过程中去完成烧水这个操作,并且不耽误看电视,看了这张图,在去了解接下来的概念会更好的理解主线程与子线程的概念。
线程是什么?
从底层角度来说:
一个线程就是在进程中的一个单一的顺序控制流.而单个进程可以拥有多个并发执行的任务,每个任务都好像有自己的CPU一样,而其底层的机制就是切分CPU的时间,也就是CPU将轮流给每个任务分配其占用时间。
每个任务都觉得自己在一直占用CPU,而事实上是将CPU时间划分成片段分配给所有的任务。
在多个CPU的环境下,多线程的运作,可以极大的提供程序的运行速度,这就是线程存在的意义。
那么在Android中,线程的作用是?
首先,先了解下Android下进程和线程的概念:
这里引用Gityuan作者在知乎上的回答,关于线程和进程的概念
进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。
进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。
上面可能还是比较专业,这里简要总结下线程在Android的作用:
(1)在Android中线程分主线程和子线程,主线程也被称为UI线程,用来处理各种和界面相关的事情,
例 :界面的加载,Activity的生命周期这些都在主线程的范畴之内。
(2)由于主线程比较特殊,因为本身主线程在处理界面上,用了大部分的消耗,所以主线程不能再处理过于耗时的操作(IO操作,网络请求,大量的数据操作),否则就会造成ANR现象(程序卡死)。
什么是ANR?,这里百度上有比较全的介绍
而造成这种现象的主要原因有:
Activity响应时间超过5s
Broadcast在处理时间超过10s
Service处理时间超过20s
这大部分的原因是主线程进行过于耗时的操作,因为Activity,Broadcast,Serivce本身都是通过主线程进行承载的。
(3)此时子线程就横空出世解决了这类问题,Android建议耗时操作必须放在子线程中运行。
(4)而在Android中可以解决耗时问题的角色除了Thread之外还有AsyncTask,HandlerThread,IntentService,都可以实现此类功能,而他们的本质还是传统的线程。
为什么会有线程池?
从字面上来看,线程池是存放,和管理线程的池子。那么为什么会有线程池呢?
先看一个例子,这里我用Handler和Thread来模拟网络请求的操作:
private Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {if (msg.what == TASK_ACTION) {Log.d("收到消息", "更新UI");}return false;}});
new Thread(new Runnable() {@Overridepublic void run() {try {//模拟网络请求Thread.sleep(1000);mHandler.sendEmptyMessage(TASK_ACTION);} catch (InterruptedException e) {e.printStackTrace();}}}).start();
上面过程,只是用一个Thread来模拟正常的网络请求,然后通过Handler来回调给UI线程,通知UI线程来刷新,如果对Handler机制不太了解,
一篇不错的Handler介绍的文章
上面只是单纯的一个网络请求,那么现在需求来了,这个界面不止一个网络请求,可能存在大量的网络请求,这时候就会有问题产生:
(1)当大量的网络请求产生,就会大量的创建和销毁线程,因此可能会造成过大的性能开销。
(2)当大量的线程一起运作的时候,可能会造成资源紧张,上面也介绍过线程底层的机制就是切分CPU的时间,而大量的线程同时存在时可能造成互相抢占资源的现象发生,从而导致阻塞的现象。
基于以上背景,线程池适当的出现可以很好的解决上述的问题,而上述模拟网络请求也只是一个简单的例子,而现实情况下,会有好多种情况和上述相似,比如在数据库操作大数据,多线程下载,在使用Thread的同时都会出现上述情况。
什么是线程池?
Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor,ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。
线程池的优点:
线程池的出现,恰恰就是解决上面类似问题的痛点,而线程池的优点有:
(1)复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
(2)能够有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
(3)能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
线程池ThreadPoolExecutor的构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory)
Executor作为一个接口,它的具体实现就是ThreadPoolExecutor。
Android中的线程池都是直接或间接通过配置ThreadPoolExecutor来实现不同特性的线程池。
上面代码是创建一个基本的线程池需要的参数,让我们通过图来简要的描述下:
由上图可以简要的描述出创建一个基本的线程池需要的参数,以及各个参数的含义,下面将详细说明各个参数的具体含义。
那么ThreadPoolExecutor执行任务时的心路历程是什么样的呢?(以下用currentSize表示线程池中当前线程数量)
(1)当currentSize<corePoolSize时,没什么好说的,直接启动一个核心线程并执行任务。
(2)当currentSize>=corePoolSize、并且workQueue未满时,添加进来的任务会被安排到workQueue中等待执行。
(3)当workQueue已满,但是currentSize<maximumPoolSize时,会立即开启一个非核心线程来执行任务。
(4)当currentSize>=corePoolSize、workQueue已满、并且currentSize>maximumPoolSize时,调用handler默认抛出RejectExecutionExpection异常。
CorePoolSize
线程的核心线程数。
默认情况下,核心线程数会在线程中一直存活,即使它们处于闲置状态。
如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么核心线程就会存在超时策略,这个时间间隔有keepAliveTime所决定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被停止。
maximumPoolSize
线程池所能容纳的最大线程数。
当活动线程数达到这个数值后,后续的新任务将会被阻塞。
keepAliveTime
非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收,当ThreadPoolExector的allowCoreThreadTimeOut属性设置为True时,keepAliveTime同样会作用于核心线程。
unit
用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。
TimeUnit.NANOSECONDS 纳秒
TimeUnit.MICROSECONDS 微秒
TimeUnit.MILLISECONDS 毫秒
TimeUnit.SECONDS 秒
TimeUnit.MINUTES 分钟
TimeUnit.HOURS 小时
TimeUnit.DAYS 天
workQueue
线程池中的任务队列,通过线程池execute方法提交的Runnable对象会存储在这个参数中。
这个任务队列是BlockQueue类型,属于阻塞队列,就是当队列为空的时候,此时取出任务的操作会被阻塞,等待任务加入队列中不为空的时候,才能进行取出操作,而在满队列的时候,添加操作同样被阻塞。
如果有想了解的可以参考下这篇文章:
Java多线程-工具篇-BlockingQueue
threadFactory
线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法,newThread(Runnable r),用来创建线程。
ThreadFactory factory =new ThreadFactory() {//线程安全的Integer操作类private final AtomicInteger mCount =new AtomicInteger(1);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "new Thread #" + mCount.getAndIncrement());}};
线程池的分类
Android中最常见的四类具有不同功能特性的线程池:
1.FixedThreadPool(一堆人排队上公厕)
FixThreadPool就像一堆人排队上公厕一样,可以无数多人排队,但是厕所位置就那么多,而且没人上时,厕所也不会被拆迁.
//特点:
//核心线程数和最大线程数相同.
//无超时时间public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
(1)这是一种数量固定的线程池,当线程处于空闲的时候,并不会被回收,除非线程池被关闭.
(2)当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来.
(3)由于线程不会回收,FixThreadPool会更快地响应外界请求,很容易理解,就好像有人突然想上厕所,公厕不是现用现建的。
(4)通过构造方法可以看出,FixedThreadPool只有核心线程,并且超时时间为0(即无超时时间),所以不会被回收.
2. SingleThreadPool(公厕里只有一个坑位)
【前方高能,笔者脑洞】可以把SingleThreadPool简单的理解为FixThreadPool的参数被手动设置为1的情况,即Executors.newFixThreadPool(1).execute(r)。所以SingleThreadPool可以理解为公厕里只有一个坑位,先来先上。为什么只有一个坑位呢,因为这个公厕是收费的,收费的大爷上年纪了,只能管理一个坑位,多了就管不过来了(线程同步问题)。
public static ExecutorService newSingleThreadExecutor() {return Executors.newSingleThreadExecutor();}//特点://线程中只有一个核心线程//并且无超时时间public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行.
SingleThreadExecutor的意义在于统一外界所有任务到一个线程,这使得这些任务之间不需要处理线程同步的问题.
3.CacheThreadPool(一堆人去一家很大的咖啡馆喝咖啡)
CachedThreadPool就像是一堆人去一个很大的咖啡馆喝咖啡,里面服务员也很多,随时去,随时都可以喝到咖啡。但是为了响应国家的“光盘行动”,一个人喝剩下的咖啡会被保留60秒,供新来的客人使用,哈哈哈哈哈,好恶心啊。如果你运气好,没有剩下的咖啡,你会得到一杯新咖啡。但是以前客人剩下的咖啡超过60秒,就变质了,会被服务员回收掉。
//无核心线程,并且最大线程数为int的最大值.
//超时时间为60s
//队列为SynchronousQueue同步阻塞队列,队列中没有任何容量.只有在有需求的情况下,队列中才可以试着添加任务.public static ExecutorService newCacheThreadPool(){return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
(1)它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE(也就相当于线程池的线程数量可以无限大).
(2)当线程池中所有线程都处于活动的状态时,线程池会创建新的线程来处理新任务,否则就会复用空闲线程来处理.
(3)值得注意的是,这个线程池中储存任务的队列是SynchronousQueue队列,这个队列可以理解为无法储存的队列,只有在可以取出的情况下,才会向其内添加任务。
从整个CacheThreadPool的特性来看:
(1)比较适合执行大量的耗时较少的任务.(喝咖啡人挺多的,喝的时间也不长)。
(2)当整个线程都处于闲置状态时,线程池中的线程都会超时而被停止,这时候的CacheThreadPool几乎不占任何系统资源的.
4.ScheduledThreadPool(4个里面唯一一个有延迟执行和周期重复执行的线程池)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSzie) {return new ScheduledThreadPoolExecutor(corePoolSzie);}//核心线程数是固定的,非核心线程无限大,并且非核心线程数有10s的空闲存活时间public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue());}
它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收.
ScheduThreadPool这类线程池主要用于执行定时任务和具有固定周期的重复任务.
而DelayedWorkQueue这个队列就是包装过的DelayedQueue,这个类的特点是在存入时会有一个Delay对象一起存入,代表需要过多少时间才能取出,相当于一个延时队列.
Android中常见的4种线程池的理解(转)相关推荐
- Android开发——Android中常见的4种线程池(保证你能看懂并理解)
0.前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52415337 使用线程池可以给我们带来很多好处,首先通过线程池中线程的重用 ...
- Java常见的5种线程池
在开发过程中我们常常需要使用到多线程来提高我们代码处理某些任务的效率,最基本的两种创建多线程的方式分别是继承Thread类和实现Runnable接口.但是创建线程和销毁线程的系统开销比较大,而且过多的 ...
- 对Java中常见的四种I/O模型理解
对Java中常见的四种I/O模型理解 1.1 知识科普 1.1.1 同步阻塞I/O(Blocking I/O) 1.1.1.1 阻塞与非阻塞 1.1.1.2 同步与异步 1.1.1.3 同步阻塞I/O ...
- Java中常用的四种线程池
在Java中使用线程池,可以用ThreadPoolExecutor的构造函数直接创建出线程池实例,在Executors类中,为我们提供了常用线程池的创建方法. 接下来我们就来了解常用的四种: ne ...
- 常见的6种线程池及简单使用
jdk8线程池一共有6种,分别是 CachedThreadPool(cached线程池,当有任务进来的时候如果有空闲的线程则直接利用,如果无空闲线程,则新创建一个线程出来使用), ScheduleTh ...
- Android中网络请求创建单个线程池的方法
创建单个线程池的方法 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; impor ...
- Android中常见的五种布局及特点
- Android中Callable、Future、FutureTask的概念以及几种线程池的使用
学习线程池必备知识: 在开始介绍线程池之前,先来介绍下Callable和Future的概念,众所周知,Android中实现多线程的方式有两种,实现Runnable接口或者继承一个Thread,但是这两 ...
- Android AsyncTask两种线程池分析和总结
转自:http://bbs.51cto.com/thread-1114378-1-1.html Android AsyncTask两种线程池分析和总结 (一) 前言 在android Async ...
最新文章
- 实习生离职,HR恼羞成怒:我要全行业封杀你
- 聚类小分子数据集(基于RDKit的Python脚本)
- Visual Studio 2017常用快捷键(小白入)
- 第十一天2017/04/25(1、二叉树)
- 异常处理_Maven之web项目java.lang.LinkageError
- 快速高效 | iOS身份证识别
- 解析MySQL基础架构及一条SQL语句的执行流程和流转
- 今日恐慌与贪婪指数为91 贪婪程度有所缓解
- java方法被编译器调用_我异常了,快来捕获我,Java异常简述
- Hadoop2.8集群安装详细教程
- SVN客户端的安装与使用----第一章
- Android apk 系统签名
- 【转载】html中自定义字体
- umd文件结构深度解剖
- 布法罗纽约州立大学计算机排名,2019年QS世界大学排名纽约州立大学布法罗分校排名第313...
- Docker加速器 DaoCloud
- html页面悬浮提示框,js实现页面悬浮框
- Java 基础篇:第十九章:多线程
- uni-app使用多彩色图标,阿里图库
- vivo手机可以升级鸿蒙系统,什么手机可以刷鸿蒙系统?vivo、OPPO、三星手机刷鸿蒙系统教程...