Android 线程池

使用线程池可以给我们带来很多好处,首先通过线程池中线程的重用,减少创建和销毁线程的性能开销。其次,能控制线程池中的并发数,否则会因为大量的线程争夺CPU资源造成阻塞。最后,线程池能够对线程进行管理比如使用ScheduledThreadPool来设置延迟N秒后执行任务,并且每隔M秒循环执行一次。

下面会通过介绍线程池中的真正实现者——ThreadPoolExecutor来引出Android中的4类线程池的使用以及特性分析,会加上笔者自己的理解,和自认为比较恰当的比喻,帮助理解。

 

1.凡事得靠ThreadPoolExecutor(铺垫环节,懂的直接跳过)

Executor作为一个接口,它的具体实现就是ThreadPoolExecutor

Android中的线程池都是直接或间接通过配置ThreadPoolExecutor来实现不同特性的线程池。

先介绍ThreadPoolExecutor的一个常用的构造方法。

[java]  view plain  copy
  1. /* 
  2. *@ ThreadPoolExecutor构造参数介绍 
  3. *@author SEU_Calvin 
  4. * @date 2016/09/03 
  5. */  
  6. public ThreadPoolExecutor(  
  7. //核心线程数,除非allowCoreThreadTimeOut被设置为true,否则它闲着也不会死  
  8. int corePoolSize,   
  9. //最大线程数,活动线程数量超过它,后续任务就会排队                     
  10. int maximumPoolSize,   
  11. //超时时长,作用于非核心线程(allowCoreThreadTimeOut被设置为true时也会同时作用于核心线程),闲置超时便被回收             
  12. long keepAliveTime,                            
  13. //枚举类型,设置keepAliveTime的单位,有TimeUnit.MILLISECONDS(ms)、TimeUnit. SECONDS(s)等  
  14. TimeUnit unit,  
  15. //缓冲任务队列,线程池的execute方法会将Runnable对象存储起来  
  16. BlockingQueue<Runnable> workQueue,  
  17. //线程工厂接口,只有一个new Thread(Runnable r)方法,可为线程池创建新线程  
  18. ThreadFactory threadFactory)  

ThreadPoolExecutor的各个参数所代表的特性注释中已经写的很清楚了,那么ThreadPoolExecutor执行任务时的心路历程是什么样的呢?(以下用currentSize表示线程池中当前线程数量)

1)当currentSize<corePoolSize时,没什么好说的,直接启动一个核心线程并执行任务。

2)当currentSize>=corePoolSize、并且workQueue未满时,添加进来的任务会被安排到workQueue中等待执行。

3)当workQueue已满,但是currentSize<maximumPoolSize时,会立即开启一个非核心线程来执行任务。

4)当currentSize>=corePoolSizeworkQueue已满、并且currentSize>maximumPoolSize,调用handler默认抛出RejectExecutionExpection异常。


2 Android中的四类线程池

Android中最常见的四类具有不同特性的线程池分别为FixThreadPoolCachedThreadPoolScheduleThreadPool以及SingleThreadExecutor

 

2.1     FixThreadPool(一堆人排队上公厕)

[java]  view plain  copy
  1. /* 
  2. *@FixThreadPool介绍 
  3. *@author SEU_Calvin 
  4. * @date 2016/09/03 
  5. */  
  6. public static ExecutorService newFixThreadPool(int nThreads){  
  7.     return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());  
  8. }  
  9. //使用  
  10. Executors.newFixThreadPool(5).execute(r);  

1)从配置参数来看,FixThreadPool只有核心线程,并且数量固定的,也不会被回收,所有线程都活动时,因为队列没有限制大小,新任务会等待执行。

2【前方高能,笔者脑洞】FixThreadPool其实就像一堆人排队上公厕一样,可以无数多人排队,但是厕所位置就那么多,而且没人上时,厕所也不会被拆迁,哈哈o(_)o ,很形象吧。

3)由于线程不会回收,FixThreadPool更快地响应外界请求,这也很容易理解,就好像有人突然想上厕所,公厕不是现用现建的。


2.2     SingleThreadPool(公厕里只有一个坑位)

[java]  view plain  copy
  1. /* 
  2. *@SingleThreadPool介绍 
  3. *@author SEU_Calvin 
  4. * @date 2016/09/03 
  5. */  
  6. public static ExecutorService newSingleThreadPool (int nThreads){  
  7.     return new FinalizableDelegatedExecutorService ( new ThreadPoolExecutor (110, TimeUnit. MILLISECONDS, new LinkedBlockingQueue<Runnable>()) );  
  8. }  
  9. //使用  
  10. Executors.newSingleThreadPool ().execute(r);  

1)从配置参数可以看出,SingleThreadPool只有一个核心线程,确保所有任务都在同一线程中按顺序完成。因此不需要处理线程同步的问题。

2【前方高能,笔者脑洞】可以把SingleThreadPool简单的理解为FixThreadPool的参数被手动设置为1的情况,即Executors.newFixThreadPool(1).execute(r)。所以SingleThreadPool可以理解为公厕里只有一个坑位,先来先上。为什么只有一个坑位呢,因为这个公厕是收费的,收费的大爷上年纪了,只能管理一个坑位,多了就管不过来了(线程同步问题)。


2.3     CachedThreadPool(一堆人去一家很大的咖啡馆喝咖啡)

[java]  view plain  copy
  1. /* 
  2. *@CachedThreadPool介绍 
  3. *@author SEU_Calvin 
  4. * @date 2016/09/03 
  5. */  
  6. public static ExecutorService newCachedThreadPool(int nThreads){  
  7.     return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit. SECONDS, new SynchronousQueue<Runnable>());  
  8. }  
  9. //使用  
  10. Executors.newCachedThreadPool().execute(r);  

1CachedThreadPool只有非核心线程,最大线程数非常大,所有线程都活动时,会为新任务创建新线程,否则利用空闲线程(60s空闲时间,过了就会被回收,所以线程池中有0个线程的可能)处理任务。

2)任务队列SynchronousQueue相当于一个空集合,导致任何任务都会被立即执行

3【前方高能,笔者脑洞】CachedThreadPool就像是一堆人去一个很大的咖啡馆喝咖啡,里面服务员也很多,随时去,随时都可以喝到咖啡。但是为了响应国家的“光盘行动”,一个人喝剩下的咖啡会被保留60秒,供新来的客人使用,哈哈哈哈哈,好恶心啊。如果你运气好,没有剩下的咖啡,你会得到一杯新咖啡。但是以前客人剩下的咖啡超过60秒,就变质了,会被服务员回收掉。

4)比较适合执行大量的耗时较少的任务。喝咖啡人挺多的,喝的时间也不长。


2.4    ScheduledThreadPool4个里面唯一一个有延迟执行和周期重复执行的线程池)

[java]  view plain  copy
  1. /* 
  2. *@ScheduledThreadPool介绍 
  3. *@author SEU_Calvin 
  4. * @date 2016/09/03 
  5. */  
  6. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){  
  7. return new ScheduledThreadPoolExecutor(corePoolSize);  
  8. }  
  9. public ScheduledThreadPoolExecutor(int corePoolSize){  
  10. super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedQueue ());  
  11. }  
  12. //使用,延迟1秒执行,每隔2秒执行一次Runnable r  
  13. Executors. newScheduledThreadPool (5).scheduleAtFixedRate(r, 10002000, TimeUnit.MILLISECONDS);  

1)核心线程数固定,非核心线程(闲着没活干会被立即回收)数没有限制。

2)从上面代码也可以看出,ScheduledThreadPool主要用于执行定时任务以及有固定周期的重复任务

----------------------------------------------------------

一,简述线程池:

线程池是如何工作的:一系列任务出现后,根据自己的线程池安排任务进行。

如图:

 

线程池的好处:

  1. 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
  2. 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
  3. 能对线程进行简单的管理。并提供定时执行以及指定间隔循环执行等功能。

 

线程池的具体实现为ThreadPoolExeutor,其接口为Executor

ThreadPoolExecutor提供了一系列的参数用于配置线程池。

复制代码
   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 :线程池核心线程数,默认情况下,核心线程会在线程池中一直存活,即使他们处于闲置状态。其有一个allowCoreThreadTimeOut属性如果设置为true,那么核心线程池会有超时策略。超时的时长为第三个参数  keepAliveTime 。如果超时,核心线程会被终结。
  2. maxmumPoolSize: 线程池所能容忍的最大线程数,当活动线程数达到这个数值后,后续的新任务会被阻塞。
  3. keepAliveTime:非核心线程闲置时的超时时长,超过这个时长就会被非核心线程会被回收。这个参数如同第一个参数,如果设置相关属性后也会作用于核心线程。、
  4. unit:指定keepAliveTime的参数时间单位。这是一个枚举,常用的有MILLISECONDS(毫秒)、SECONDS(秒)等
  5. workQueue:线程池的任务队列,通过execute()方法(执行方法)提交的Runable对象会存储在这个参数中。
  6. threadFactory:线程工厂,为线程池提供创建新线程的功能。

其执行任务时大致遵顼如下规则:

  1. 如果线程达到核心线程数量,那么回直接启动一个核心线程。
  2. 线程未达到核心线程的数量,任务会被插入到任务队列(workQueue)排队。
  3. 如果任务队列已满导致步骤二无法插入到任务队列,那么开启一个非核心线程执行。
  4. 如果步骤三的线程数量达到线程池规定数目(maxmumPoolSize),那么拒绝执行此任务。

二,线程池的相关类结构解析

看一下线程池的类结构图:

  

  说以下这几点:

     这是一个抽象工厂模式!

  Executor和Executors的区别:

  Executor仅仅是一个接口,其只有一个方法,execute()方法(执行方法)。

public interface Executor {


    void execute(Runnable command);
}

   而Executors是一个没有继承任何类的方法,其作为一个工厂方法类,用于创建形形色色的线程池等对象。如:FixedThreadPool、defaultThreadFactory(下面会具体讲),而这些具体的线程池方案(FixedThreadPool)亦或者自定义的线程池方案(ThreadPoolExeutor)都是来自ExecutorService接口,这个接口也是继承于Executor接口。通过类结构图可以很清楚的看到。

如,Executors生产出ExecutorService对象,源码如下

复制代码
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
复制代码

 

 说到工厂模式,ThreadPoolExeutor的参数threadFactory是一个接口,仅有newThread方法,代码如下

  

public interface ThreadFactory {

    Thread newThread(Runnable r);
}

 

其作用和executor接口类似,体现面向接口编程思想。其中例如如下两个方法,是工厂方法类Executors所生成的ThreadFactory对象。

复制代码
 public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }

       public static ThreadFactory privilegedThreadFactory() {
        return new PrivilegedThreadFactory();
    }
复制代码
 

 defaultThreadFactory是这样创建线程的,如果要自定义不妨参照一下。

  

复制代码
static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
复制代码

 

三 具体线程池的使用


说了那么多,要具体看看系统提供的线程池,主要是这四个:

  1. FixedTreadPool. fixed有稳固的含义,通过工厂方法类Executors的newFixedThreadPool方法创建。它是线程数量固定的线程池,仅有核心线程且没有超时策略,所以线程不会被回收。这意味着它能够快速的响应外界请求。

  例子:这里设置了4个runnable和2个核心线程。

复制代码
  Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Log.d("hello","sleep前");
                SystemClock.sleep(3000);
                Log.d("hello", "sleep后");
            }
        };
......

   ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(runnable);
        executorService.execute(runable1);
        executorService.execute(runable2);
        executorService.execute(runable3);
复制代码

输出如下,

复制代码
06-24 15:49:47.962 12462-12497/? D/hello: sleep1前
06-24 15:49:47.962 12462-12496/? D/hello: sleep前
06-24 15:49:50.962 12462-12497/com.example.hang.myapplication D/hello: sleep1后
06-24 15:49:50.962 12462-12497/com.example.hang.myapplication D/hello: sleep2前
06-24 15:49:50.972 12462-12496/com.example.hang.myapplication D/hello: sleep后
06-24 15:49:50.972 12462-12496/com.example.hang.myapplication D/hello: sleep3前
06-24 15:49:53.962 12462-12497/com.example.hang.myapplication D/hello: sleep2后
06-24 15:49:53.972 12462-12496/com.example.hang.myapplication D/hello: sleep3后
复制代码

可以看到,每次仅有两个线程去执行,当其中一个执行完毕后才轮到下一个。
当 核心线程数量改为 4 时,则会一次性执行完这4个runnable。

  1. CachedThreadPool.它是一种线程数量不定的线程池,只有非核心线程,可以简单理解为最大线程数是无限大的。CachedThreadPool的任务队列相当于一个空集合,这导致任何任务都会被立即执行,比较适合做一些大量的耗时较少的任务。

 例子,修改为将上述的  ExecutorService executorService = Executors.newFixedThreadPool(2);替换为

ExecutorService executorService = Executors.newCachedThreadPool();,输出如下,一次性执行4个线程,且线程顺序不定。
复制代码
06-24 15:53:08.582 16801-16873/com.example.hang.myapplication D/hello: sleep2前
06-24 15:53:08.582 16801-16872/com.example.hang.myapplication D/hello: sleep1前
06-24 15:53:08.582 16801-16871/com.example.hang.myapplication D/hello: sleep前
06-24 15:53:08.582 16801-16875/com.example.hang.myapplication D/hello: sleep3前
06-24 15:53:11.582 16801-16873/com.example.hang.myapplication D/hello: sleep2后
06-24 15:53:11.582 16801-16872/com.example.hang.myapplication D/hello: sleep1后
06-24 15:53:11.582 16801-16871/com.example.hang.myapplication D/hello: sleep后
06-24 15:53:11.582 16801-16875/com.example.hang.myapplication D/hello: sleep3后
复制代码

 

  1. ScheduledThreadPool.Scheduled有 “预定的” 意思。核心线程池的数量书固定的且非核心线程池是没有限制的,非核心线程池被闲置时会被立即回收。主要用于执行定时任务和具有固定周期的重复任务。

  例子:这里是延迟2000ms后开始执行

 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        scheduledExecutorService.schedule(runnable,2000, TimeUnit.MILLISECONDS);
        scheduledExecutorService.schedule(runable1, 2000, TimeUnit.MILLISECONDS);
        scheduledExecutorService.schedule(runable2, 2000, TimeUnit.MILLISECONDS);
        scheduledExecutorService.schedule(runable3, 2000, TimeUnit.MILLISECONDS);

  例子:延迟10ms后开始每1000ms执行一次runnable。

 scheduledExecutorService.scheduleAtFixedRate(runnable,10,1000,TimeUnit.MILLISECONDS);

 

  1. SingleThreadExecutor.只有一个核心线程,所以确保所有的任务都是在一个线程里顺序执行。把所有的任务都放到一个线程,这样有一个好处是不需要处理线程同步问题。

 这里就不做例子了,就是4个排着队依次执行,且不会乱序。

  

复制代码
06-24 16:05:02.782 32057-32125/com.example.hang.myapplication D/hello: sleep前
06-24 16:05:05.782 32057-32125/com.example.hang.myapplication D/hello: sleep后
06-24 16:05:05.782 32057-32125/com.example.hang.myapplication D/hello: sleep1前
06-24 16:05:08.782 32057-32125/com.example.hang.myapplication D/hello: sleep1后
06-24 16:05:08.782 32057-32125/com.example.hang.myapplication D/hello: sleep2前
06-24 16:05:11.782 32057-32125/com.example.hang.myapplication D/hello: sleep2后
06-24 16:05:11.782 32057-32125/com.example.hang.myapplication D/hello: sleep3前
06-24 16:05:14.782 32057-32125/com.example.hang.myapplication D/hello: sleep3后
复制代码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值