线程咱们一定都用过,项目傍边一些比较耗时的操作,比方网络请求,IO操作,咱们都会把这类操作放在子线程中进行,因为假如放在主线程中,就会多少造成一些页面卡顿,影响性能,不过是不是放在子线程中就好了呢,咱们看看下面这段代码
很简略的一段代码,创立了一个Thread,然后把耗时作业放在里边进行就好了,假如项目傍边只有一两处出现这样的代码,倒也影响不大,可是现在的项目傍边,耗时的操作一大堆,比方文件读取,数据库的读取,sp操作,或许需求频频从某个服务器获取数据显示在屏幕上,比方k线图等,像这些操作假如咱们都去经过创立新的线程去履行它们,那么对性能以及内存的开支是很大的,所以咱们在平时开发过程傍边应该养成习惯,不要去创立新的线程而是经过运用线程池去履行自己的使命
线程池
为什么要运用线程池呢?线程池总结一下有以下几点优势
- 下降资源耗费:经过复用之前创立过的线程资源,下降线程创立与销毁带来的性能与内存的开支
- 提高响应速度:无需等候线程创立,直接能够履行使命
- 提高线程可办理性:运用线程池能够对线程资源一致调优,分配,办理
- 运用更多扩展功用:运用线程池能够进行一些推迟或许周期性作业
而咱们创立线程池的方法有以下几种
- Executors.newFixedThreadPool:创立一个固定巨细的线程池
- Executors.newCachedThreadPool:创立一个可缓存的线程池
- Executors.newSingleThreadExecutor:创立单个线程数的线程池
- Executors.newScheduledThreadPool:创立一个能够履行推迟使命的线程池
- Executors.newSingleThreadScheduledExecutor:创立一个单线程的能够履行推迟使命的线程池
- Executors.newWorkStealingPool:创立一个抢占式履行的线程池
- ThreadPoolExecutor:最原始的创立方法,以上六种方法的内部也是经过这个创立的线程池
虽然咱们发起运用线程池,可是有这么多的创立方法,咱们假如不在项目傍边做一下办理的话,那么各式各样的线程池都有可能被运用到,因为每种创立方法关于线程的办理方法都不相同,假如不合理创立的话,很可能会出现问题,所以咱们需求有一个一致创立线程池的当地
一致办理线程
首要咱们先创立一个线程池,运用Executors.newCachedThreadPool()去创立一个 ExecutorService,至于为什么选择newCachedThreadPool(),咱们看下它的源码
从上面一大段英文注释中咱们能知道,这是一个可缓存的线程池,而且corePoolSize为0阐明这个线程池没有一直存活的线程,假如线程池中没有可用线程,会重新创立新线程,而线程池中假如有可用线程,那么这个线程会被再利用,一个线程假如60秒内没有被运用,那么将会从行列中移除并销毁,所以个人感觉关于并发要求不是特别高的移动端,从性能视点来讲运用这样的一个线程池是比较合适的,当然详细设计方案以业务性质来决议,现在咱们能够将项目傍边的线程放在咱们的线程池里边运转了,再增加一个履行线程的函数
经过这个函数就能够有效的避免项目傍边随意创立线程的现象发生,让项目傍边的线程能够井然有序的运转,可是这还没完事,咱们知道Runnable在使命履行完结之后是没有回来成果的,因为Runnable接口中的run方法的回来类型是个void,但实践开发傍边,咱们的确有需求,在履行一些比方查询数据库,读取文件之类的操作中,需求获取使命的履行成果,之前都是经过在线程傍边手动增加一个handler将需求的数据传递出来,再专业一点运用RxJava或许Flow,但不管什么方法,这些都会造成代码耦合,咱们还有更简略的方法
Callable和Future
这两个类是在java1.5之后推出来的,意图便是处理线程履行完结之后没有回来成果的问题,咱们先来对比下Runnable与Callable
相比较于Runnable,Callable接口里边也有一个call的方法,这个方法是有回来值的,而且能够抛出反常,所以今后当咱们需求获取使命的履行成果的时候,咱们还能够运用Callable替代Runnable,那么怎样运用并获取回来值呢?当然是运用咱们现已创立好的ExecutorService,它里边供给了一个函数去履行Callable
运用submit函数就能够履行咱们的Callable,回来值是一个Future,而怎样去获取真实的回来成果,就在Future里边,咱们看下
运用get方法就能够获取线程的履行成果,咱们现在就来试试Callable和Future,在PoolManager里边再增加一个函数,用来履行Callable
咱们这儿有个简略的使命,便是回来一段文字,然后将这段文字显示在界面上,那么第一步,先在布局文件里边增加一个按钮
然后点击这个按钮,将使命里边回来的文字显示在按钮上,代码如下
得到的作用如下
在这边特地把履行成果放在界面上而不是用日志打印出来的原因可能咱们现已发现了,Callable在回来履行成果的同时,也帮咱们把线程切回到了主线程,所以咱们不必在特地去切换线程更新ui界面了
周期性使命
一般的单个使命咱们讲完了,可是在项目傍边往往会存在一些比较特殊的使命,可能需求你去周期性的去履行,举个常见的例子,在证券类的app里制作k线图的时候,并不需求将服务器吐出来的数据统统拿出来制作ui,这样对性能的开支是很大的,咱们正确的做法是将数据都先存放在一个buffer里边,然后定时的去buffer里边拿最新数据就好,那这样一个定时刷新的功用如安在咱们的线程池里边去完成呢,这儿就要用到刚刚提到的另一种创立线程池的方法
这个函数创立的是一个ScheduledExecutorService对象,可周期性的履行使命,入参的corepoolSize表明可并发的线程数,现在咱们在PoolManager里边增加上这个ScheduledExecutorService
而怎样去履行使命,咱们运用ScheduledExecutorService里边的scheduleAtFixedRate函数,咱们先看下这个函数都有哪些入参
不必去看注释咱们就能知道怎样运用这个函数,command便是履行的使命,第二,第三个参数别离表明推迟履行的时间以及使命履行的周期时间,第四个参数是时间的单位,在看回来值是一个ScheduleFuture,既然也是个Future,那是不是也能够经过它去获取使命履行的成果呢?答案是拿不到的,一个原因是command是一个Runnable而不是Callable,不会回来使命的履行成果,另外咱们从注释上就能了解,这个ScheduleFuture只是用来当周期使命中有使命被取消了,或许被反常终止的时候,抛出反常用的,那ScheduledExecutorService一定有入参是Callable的函数的吧,找了找发现并没有,那只有一个方法了,咱们在command里边去履行一个Callable使命,再将使命的履行成果回调出来就好了,代码设计如下
咱们创立了一个函数叫executeScheduledJob,也有四个入参,job是一个Callable,用来履行咱们的使命,callback是一个回调,用来将使命履行成果回调到上层去处理,后边两个刚刚现已介绍过了,这儿设置了默认值,可自定义,现在咱们就来完成一个简略的读秒功用,点击刚刚那个按钮,按钮初始值是1,然后每秒钟加一,代码完成如下
这边创立了一个CounterViewModel用来履行计数器的逻辑,dataState是一个StateFlow而且设置了初始值1,在onCallback里边接纳到了使命履行成果并发送至上层展示,上层的代码逻辑如下
现在这个计时器功用完结了,咱们来履行下代码看看作用怎样
咱们这边运用StateFlow发送数据还有个优点,当接纳的数据中有些数据需求过滤掉的时候,咱们还能够运用StateFlow供给的操作符完成,比方这边咱们只想展示奇数,那么代码能够改成如下所示
运用filter操作符将偶数的值过滤掉了,咱们再看看作用
总结
咱们的这个线程办理东西到这儿现已完结了,不是很杂乱,可是项目傍边存不存在这样一个东西显着会对全体开发功率,代码的可读性,保护本钱,以及一个app的性能视点来讲都会有个很大的提升与改善,后边假如还做了其他优化作业,也会拿出来分享。