Android线程优化之线程池的详解

使用线程来进行异步任务,这对于大多数人而言还是轻车熟路的,而且使用起来比较简单,只需要通过new Thread() 之后 start 即可,当任务完成之后则会销毁,但是这里有一个弊端,就是如果一个高度任务,比如说定时处理异步事件,如果你每次执行都new Thread的话,这将导致线程频繁的创建与销毁,这样会导致占用大量的资源,这已经就十分的不友好了,而且所有创建的子线程都没有一个统一的管理,如果要优化,也应该从这里下手了,所以,我们,我们就更应该来了解一下线程池的使用了,使用线程池可以复用创建的子线程,不会反复创建和销毁,并且使用起来也不算太难。


ThreadPoolExecutor

我们要理解ThreadPoolExecutor,因为Android中其他几个线程池都是他延伸出来的,相当于他就是个爸爸,所以你要先了解ThreadPoolExecutor的前世今生,首先来看下他的构造函数的几个参数:

  • int corePoolSize

  • 核心线程数量

  • int maximumPoolSize

  • 最大线程数量

  • long keepAliveTime,

  • 超时时长

  • TimeUnit unit,

  • 时间枚举

  • BlockingQueue<Runnable> workQueue,

  • 任务队列

  • ThreadFactory threadFactory

  • 线程工厂接口

  • RejectedExecutionHandler handler

  • 拒绝执行的Handler


这里要注意,正常情况下,corePoolSize是一直存在的,并且当corePoolSize >= maximumPoolSize的时候,线程会被阻塞等待任务完成,keepAliveTime触发的时候非核心线程都会被回收掉。


ThreadPoolExecutor还是有很多可讲的,但是我选择的还是着重讲解Android中的四个线程池,网上对ThreadPoolExecutor的例子太多了,也比较杂,大家可以去看看。


Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor,其中所实现的四个线程池每一个的作用都是不一样的,我们一起来看下:


FixThreadPool

FixThreadPool通过如下代码实现:


ExecutorService service = Executors.newFixedThreadPool(5);

Runnable r = new Runnable() {

   @Override

   public void run() {

       Log.i(TAG, "newFixedThreadPool");

   }

};

service.execute(r);


队列线程池,因为核心线程池是固定的,所以不管你如何execute,都会一个个来执行完成,因为直接创建使用,没有回收,所以他的优势是响应速度很快,效率更高。


这里我们可以模拟一下:


ExecutorService service = Executors.newFixedThreadPool(2);


for (int i = 0; i < 10; i++) {

   final int finalI = i;

   service.execute(new Runnable() {

       @Override

       public void run() {

           try {

               Thread.sleep(2000);

               Log.i(TAG, "newFixedThreadPool:" + finalI );

           } catch (InterruptedException e) {

               e.printStackTrace();

           }

       }

   });

}


这里指定核心线程数为2,然后执行10个任务,得到的结果:

       640?wx_fmt=png      

可以看出,首先他是无序的,其次他每隔两秒钟打印两个,也就验证了刚才的说法,他线程数满了之后需要等待,就是排队打饭一样。


SingleThreadPool

顾名思义,这个都不需要指定核心线程数,代码如下:


ExecutorService service = Executors.newSingleThreadExecutor();

Runnable r = new Runnable() {

   @Override

   public void run() {

       Log.i(TAG, "newSingleThreadExecutor");

   }

};

service.execute(r);


这个其实和newFixedThreadPool(1)是一样的,SingleThreadPool主要还是为了线程同步而来的,newFixedThreadPool可以同时执行多任务,所以肯定不是同步的,如果什么场景需要线程同步,那SingleThreadPool再合适不过了。


如果我们把上面的例子改成SingleThreadPool:


ExecutorService service = Executors.newSingleThreadExecutor();


for (int i = 0; i < 10; i++) {

   final int finalI = i;

   service.execute(new Runnable() {

       @Override

       public void run() {

           try {

               Thread.sleep(2000);

               Log.i(TAG, "newSingleThreadExecutor:" + finalI );

           } catch (InterruptedException e) {

               e.printStackTrace();

           }

       }

   });

}


可以得到的结论:首先他是有序的,其次他每隔两秒则打印一次,而Log的输出也刚好验证了我的结论:

      640?wx_fmt=png      

CachedThreadPool

CachedThreadPool还是比较多使用的,他不需要指定线程数,因为他的线程数很大,但是不存在核心线程,这也就意味着经常被回收,他的回收思路是,执行任务,任务结束后,保留60s,在60s来了新任务则继续使用刚才的线程,如果60s内无任务则回收线程,有点类似吃饭,你去盛饭没关系,但是你吃完走了我就要收盘子,间隙就在60s


ExecutorService service = Executors.newCachedThreadPool();

Runnable r = new Runnable() {

   @Override

   public void run() {

       Log.i(TAG, "newCachedThreadPool");

   }

};

service.execute(r);


我们继续改造我们的示例代码:


ExecutorService service = Executors.newCachedThreadPool();


for (int i = 0; i < 10; i++) {

   final int finalI = i;

   service.execute(new Runnable() {

       @Override

       public void run() {

           try {

               Thread.sleep(2000);

               Log.i(TAG, "newCachedThreadPool:" + finalI );

           } catch (InterruptedException e) {

               e.printStackTrace();

           }

       }

   });

}


这段代码在前面两个线程池都使用到了,就是用来告诉你他们的区别,我们使用newCachedThreadPool执行后得到的结果:

      640?wx_fmt=png      

看Log,我们得到的结论是:首先他也是无序的,并且他因为子线程够多,所以sleep两秒后直接全部打印出来了。


ScheduledThreadPool

这是一个可操作性较强的线程池,也是唯一一个可循环的线程池,便于一些重复性的工作使用


ScheduledExecutorService service = Executors. newScheduledThreadPool (5);

Runnable r = new Runnable() {

   @Override

   public void run() {

       Log.i(TAG, "newScheduledThreadPool");

   }

};

Log.i(TAG,"start");

service.scheduleAtFixedRate(r, 1000, 2000, TimeUnit.MILLISECONDS);


scheduleAtFixedRate中有四个参数,第一个是任务,第二个是延时时长,第三个是循环时长,第四个是单位,也就是说,当启动scheduleAtFixedRate之后,在1s后才开始执行这个间隔2s的任务不断循环,可以这样理解,启动任务后,这里延时1s,然后才开始执行任务,以后每隔2s重复执行


我们拿着示例继续改造:


ScheduledExecutorService service = Executors.newScheduledThreadPool(2);


for (int i = 0; i < 10; i++) {

   final int finalI = i;

   service.scheduleAtFixedRate(new Runnable() {

       @Override

       public void run() {

           try {

               Thread.sleep(2000);

               Log.i(TAG, "newCachedThreadPool:" + finalI );

           } catch (InterruptedException e) {

               e.printStackTrace();

           }

       }

   },1000,2000,TimeUnit.MILLISECONDS);

}


这段代码比上面的复杂一些,我们看打印:

      640?wx_fmt=png      

这里得到的是什么结论呢?比较多,首先,他是无序的,其次他间隔2s无限重复,并且启动这个县城需要3s,也就是延迟的1s + sleep的3s。


到这里我相信大家还是比较清晰的认知线程池,但是线程池是结合实战的,不然的话也都是纸上谈兵,但是一般线程池也只是替换new Thread的操作,所以示例的话,大家可以自行寻找一下,后续的实战项目文章,我再带领大家学习线程池。


大家有兴趣可以加入我的星球,任何问题都能得到解答,还有专门的视频辅佐,快速进阶。


640?wx_fmt=jpeg



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值