Android线程池ThreadPoolExecutor详解

Android线程池ThreadPoolExecutor详解,第1张

       传统的多线程是通过继承Thread类及实现Runnable接口来实现的,每次创建及销毁线程都会消耗资源、响应速度慢,且线程缺乏统一管理,容易出现阻塞的情况,针对以上缺点,线程池就出现了。

       线程池是一个创建使用线程并能保存使用过的线程以达到复用的对象,简单的说就是一块缓存了一定数量线程的区域。

       1复用线程:线程执行完不会立刻退出,继续执行其他线程;

       2管理线程:统一分配、管理、控制最大并发数;

       1降低因频繁创建&销毁线程带来的性能开销,复用缓存在线程池中的线程;

       2提高线程执行效率&响应速度,复用线程:响应速度;管理线程:优化线程执行顺序,避免大量线程抢占资源导致阻塞现象;

       3提高对线程的管理度;

       线程池的使用也比较简单,流程如下:

       接下来通过源码来介绍一下ThreadPoolExecutor内部实现及工作原理。

       线程池的最终实现类是ThreadPoolExecutor,通过实现可以一步一步的看到,父接口为Executor:

       其他的继承及实现关系就不一一列举了,直接通过以下图来看一下:

       从构造方法开始看:

       通过以上可以看到,在创建ThreadPoolExecutor时,对传入的参数是有要求的:corePoolSize不能小于0;maximumPoolSize需要大于0,且需要大于等于corePoolSize;keepAliveTime大于0;workQueue、threadFactory都不能为null。

       在创建完后就需要执行Runnable了,看以下execute()方法:

       在execute()内部主要执行的逻辑如下:

       分析点1:如果当前线程数未超过核心线程数,则将runnable作为参数执行addWorker(),true表示核心线程,false表示非核心线程;

       分析点2:核心线程满了,如果线程池处于运行状态则往workQueue队列中添加任务,接下来判断是否需要拒绝或者执行addWorker();

       分析点3:以上都不满足时 [corePoolSize=0且没有运行的线程,或workQueue已经满了] ,执行addWorker()添加runnable,失败则执行拒绝策略;

        总结一下:线程池对线程创建的管理,流程图如下:

       在执行addWorker时,主要做了以下两件事:

       分析点1:将runnable作为参数创建Worker对象w,然后获取w内部的变量thread;

       分析点2:调用start()来启动thread;

       在addWorker()内部会将runnable作为参数传给Worker,然后从Worker内部读取变量thread,看一下Worker类的实现:

       Worker实现了Runnable接口,在Worker内部,进行了赋值及创建 *** 作,先将execute()时传入的runnable赋值给内部变量firstTask,然后通过ThreadFactorynewThread(this)创建Thread,上面讲到在addWorker内部执行tstart()后,会执行到Worker内部的run()方法,接着会执行runWorker(this),一起看一下:

       前面可以看到,runWorker是执行在子线程内部,主要执行了三件事:

       分析1:获取当前线程,当执行shutdown()时需要将线程interrupt(),接下来从Worker内部取到firstTask,即execute传入的runnable,接下来会执行;

       分析2:while循环,task不空直接执行;否则执行getTask()去获取,不为空直接执行;

       分析3:对有效的task执行run(),由于是在子线程中执行,因此直接run()即可,不需要start();

       前面看到,在while内部有执行getTask(),一起看一下:

       getTask()是从workQueue内部获取接下来需要执行的runnable,内部主要做了两件事:

       分析1:先获取到当前正在执行工作的线程数量wc,通过判断allowCoreThreadTimeOut[在创建ThreadPoolExecutor时可以进行设置]及wc > corePoolSize来确定timed值;

       分析2:通过timed值来决定执行poll()或者take(),如果WorkQueue中有未执行的线程时,两者作用是相同的,立刻返回线程;如果WorkQueue中没有线程时,poll()有超时返回,take()会一直阻塞;如果allowCoreThreadTimeOut为true,则核心线程在超时时间没有使用的话,是需要退出的;wc > corePoolSize时,非核心线程在超时时间没有使用的话,是需要退出的;

       allowCoreThreadTimeOut是可以通过以下方式进行设置的:

       如果没有进行设置,那么corePoolSize数量的核心线程会一直存在。

        总结一下:ThreadPoolExecutor内部的核心线程如何确保一直存在,不退出?

       上面分析已经回答了这个问题,每个线程在执行时会执行runWorker(),而在runWorker()内部有while()循环会判断getTask(),在getTask()内部会对当前执行的线程数量及allowCoreThreadTimeOut进行实时判断,如果工作数量大于corePoolSize且workQueue中没有未执行的线程时,会执行poll()超时退出;如果工作数量不大于corePoolSize且workQueue中没有未执行的线程时,会执行take()进行阻塞,确保有corePoolSize数量的线程阻塞在runWorker()内部的while()循环不退出。

       如果需要关闭线程池,需要如何 *** 作呢,看一下shutdown()方法:

       以上可以看到,关闭线程池的原理:a 遍历线程池中的所有工作线程;b 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)

       也可调用shutdownNow()来关闭线程池,二者区别:

       shutdown():设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程;

       shutdownNow():设置线程池的状态为STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表;

       使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow();

        总结一下:ThreadPoolExecutor在执行execute()及shutdown()时的调用关系,流程图如下:

       线程池可以通过Executors来进行不同类型的创建,具体分为四种不同的类型,如下:

       可缓存线程池:不固定线程数量,且支持最大为IntegerMAX_VALUE的线程数量:

       1、线程数无限制

       2、有空闲线程则复用空闲线程,若无空闲线程则新建线程

       3、一定程度上减少频繁创建/销毁线程,减少系统开销

       固定线程数量的线程池:定长线程池

       1、可控制线程最大并发数(同时执行的线程数)

       2、超出的线程会在队列中等待。

       单线程化的线程池:可以理解为线程数量为1的FixedThreadPool

       1、有且仅有一个工作线程执行任务

       2、所有任务按照指定顺序执行,即遵循队列的入队出队规则

       定时以指定周期循环执行任务

       一般来说,等待队列 BlockingQueue 有: ArrayBlockingQueue 、 LinkedBlockingQueue 与 SynchronousQueue 。

       假设向线程池提交任务时,核心线程都被占用的情况下:

        ArrayBlockingQueue :基于数组的阻塞队列,初始化需要指定固定大小。

       当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以最终可能出现后提交的任务先执行,而先提交的任务一直在等待。

        LinkedBlockingQueue :基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。

       当指定大小后,行为就和 ArrayBlockingQueue一致。而如果未指定大小,则会使用默认的 IntegerMAX_VALUE 作为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。

        SynchronousQueue :无容量的队列。

       使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合IntegerMAX_VALUE就实现了真正的无等待。

       但是需要注意的是, 进程的内存是存在限制的,而每一个线程都需要分配一定的内存。所以线程并不能无限个。

多线程作为Android开发中相对而言较为高阶的知识,其中用到相关的知识点是非常的多,所以在我们需要进行设计或者写多线程的代码就必须要进行相对谨慎的处理,这样就由必要对其要有着比较系统化的认知

我们一般将Android应用分成为两种:主线程和工作线程;主线程主要是用来进行初始化UI,而工作线程主要是进行耗时 *** 作,例如读取数据库,网络连接等

Android系统是以进程为单位来对应用程序资源进行限制,这个问题的可以解释为:一个进程最多能够开几个线程?最好能开几个?但实则这个是没有上限这一说,主要是因为资源的限制

Android中关于主线程的理解:Android的主线程是UI线程,在Android中,四大组件运行在主线程中,在主线程中做耗时 *** 作会导致程序出现卡顿甚至出现ANR异常,一个

在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。多线程处理一个常见的例子就是用户界面。

线程总的来就是进程的一个实体,是CPU进行分派和调度的基本单位,拥有着比进程更小且能够独立运行的基本单位,线程本身基本上是不拥有系统资源,仅拥有一点在运行过程中必须拥有的资源,但它可与同属一个进程中的其他进程进行共享其所拥有的所有资源

线程状态有些地方将之分为5中状态,而且在Java Jdk中线程被其定义为6中状态,我们可以对其进行类比

普遍定义的5中状态:新建,就绪,运行,阻塞, 死亡

Java Jdk 定义状态

线程阻塞是指在某一时刻的某一个线程在进行运行一段代码的情况下,突然另一个线程也要进行运行,但在运行过程中,那个线程执行完全运行之前,另一个线程是不可能获取到CPU的执行权,就会导致线路阻塞的出现

死锁也称之为抱死,意思就是说一个进程锁定了另外一个进程所需要的页或表是,但第二个进程同时又锁定了第一个进程所需的一页,这样就会出现死锁现象

简要介绍实现线程的三种方式:继承Thread,实现runnable,实现callable。这里有一点需要注意的是,实现callable是与线程池相关联的而callable很重要的一个特性是其带有返回值。当我们只需实现单线程时实现runnable更加利于线程程序的拓展

在线程开启之前进行调用 threadsetDaemon(true); 将thread设定成当前线程中的守护线程 使用案例

线程让步yield方法让当前线程释放CPU资源,让其他线程抢占

这种具体某个对象锁 wait & notify 方法与Condition 的 await以及signal方法类似; 全面这种方法的阻塞等待都可以是释放锁,而且在唤醒后,这种线程都是能够获取锁资源的,而这个门栓就跟阀门类似

在 Android 应用开发中,建议将读取本地 JSON 数据放在其他线程中执行,以避免卡顿或 ANR 等问题,提高用户体验。

在主线程中进行耗时 *** 作会导致应用响应变慢,因为主线程负责处理 UI 事件和更新 UI 界面,如果阻塞,就会导致应用无法响应用户 *** 作,出现 ANR(Application Not Responding)现象。

因此,可以将读取本地 JSON 数据的 *** 作放到 AsyncTask 异步任务中执行。也可以使用 Handler、Thread 或 RxJava 等方式进行异步 *** 作。这样可以保证应用能够快速响应用户 *** 作,提升用户体验。

如果你Thread A获取数据是给另外一个Thread B使用的,那为什么不以Thread B为主,甚至直接让Thread B来请求数据数据呢。疑问:Thread A 请求数据,Thread B在那专门等待?为什么要这么设计?如果你非要这么干,那也是Thread A把数据处理好了,在启动Thread B,数据可以直接传递过去,thread没有process之间通信那么麻烦,直接共享

找Bug应该要有条理,我们首先不能确定"每次都去执行while(value2 != 1)以外的代码",因为程序执行的很快,可能收发很快,循环每妙执行多少次不确定,而且你的阻塞时间还是0,最好循环内外加上输出判断是不是每次都执行循环体外的代码。

再就是你创建的是两个循环,开头

fd = HardwareControleropenSerialPort("/dev/ttyUSB0", buaterate, 8, 1);

每次读取完数据都会给fd重新赋值, 不确定打开串口是否有应答数据,如果有的话

打开串口->应答数据->有数据,执行第二个循环->读数据->重新第一个循环

这样可能会一直有数据,可把fd = HardwareControleropenSerialPort("/dev/ttyUSB0", buaterate, 8, 1);放到第一个循环外面。

这些只是推测,需要你根据自己代码慢慢调试。

以上就是关于Android线程池ThreadPoolExecutor详解全部的内容,包括:Android线程池ThreadPoolExecutor详解、Android开发之路-多线程、android读取本地json需要在其他线程吗等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9496056.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存