Android线程池的使用及demo

一、为什么要使用线程池:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

二、线程池的分类(四类)

  • newCachedThreadPool 缓存型线程池: 如果池中没有线程可用,它将创建 一个线程并添加到线程池中, 线程池中尚未使用的60秒线程 将会终止并从线程池中移出,因此, 对于长期保持足够的空闲池不会消耗任何资源。

  • newFixedThreadPool 固定数目的线程池 线程池中的数量初始化后(n个线程), 线程数量n不变,激活线程的最大数为n, 当需要的线程大于最大线程数量, 则其它线程在队列中排队等候,直到线程可用, 线程池的线程将一直存在,除非调用shutdown

  • newScheduledThreadPool 调度型线程池 创建一个线程池后,线程数量是固定, 并一直存在线程池中,它可指定线程延时、定时 周期性的执行

  • newSingleThreadExecutor 单线程池 创建线程池后,线程池有且只有一个线程,其它线程 在队列中排队等候。 与其他等效 newFixedThreadPool(1) 所返回的执行保证无需重新配置使用额外的线程。

三、线程池构造方法

ThreadPoolExecutor (int corePoolSize,
                int maximumPoolSize,
                long keepAliveTime,
                TimeUnit unit,
                BlockingQueue<Runnable> workQueue,
                ThreadFactory threadFactory,
                RejectedExecutionHandler handler)

corePoolSize  线程池中的核心线程数

  • 当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;
  • 如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
  • 如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

 maximumPoolSize 线程池中允许的最大线程数

  • 如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize

keepAliveTime 线程空闲时的存活时间,

  • 即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于corePoolSize时才有用

unit

  • keepAliveTime的时间单位

workQueue

workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。

一般来说,我们应该尽量使用有界队列,因为使用无界队列作为工作队列会对线程池带来如下影响。

1)当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。

2)由于1,使用无界队列时maximumPoolSize将是一个无效参数。

3)由于1和2,使用无界队列时keepAliveTime将是一个无效参数。

4)更重要的,使用无界queue可能会耗尽系统资源,有界队列则有助于防止资源耗尽,同时即使使用有界队列,也要尽量控制队列的大小在一个合适的范围。

threadFactory 创建线程的工厂

  • 通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名,当然还可以更加自由的对线程做更多的设置,比如设置所有的线程为守护线程。

RejectedExecutionHandler线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

(1)AbortPolicy:直接抛出异常,默认策略;

(2)CallerRunsPolicy:用调用者所在的线程来执行任务;

(3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

(4)DiscardPolicy:直接丢弃任务;

当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

四、线程池原理

1.线程池的工作机制

当一个任务通过execute(Runnable)方法添加到线程池时:

  1. 线程数量小于corePoolSize,创建新线程来执行任务(注意,执行这一步骤需要获取全局锁);
  2. 线程数量大于等于 corePoolSize,存在空闲线程,使用空闲线程执行新任务;
  3. 线程数量大于等于 corePoolSize,不存在空闲线程,新任务被添加到等待队列,添加成功则等待空闲线程,添加失败BlockingQueue(队列已满):①线程数量小于maximumPoolSize,新建线程执行新任务; ②线程数量等于maximumPoolSize,拒绝此任务,并调用RejectedExecutionHandler.rejectedExecution()方法 

2.提交任务

  • execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
  • submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

3.关闭线程池

可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。

4.合理地配置线程池

要想合理地配置线程池,就必须首先分析任务特性

要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。

  • 任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
  • 任务的优先级:高、中和低。
  • 任务的执行时间:长、中和短。
  • 任务的依赖性:是否依赖其他系统资源,如数据库连接。

性质不同的任务可以用不同规模的线程池分开处理。

CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。

混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。

执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。

建议使用有界队列。有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点儿,比如几千。

如果当时我们设置成无界队列,那么线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题。

五、线程池的使用


1、newCachedThreadPool

第一步:初始化线程池

    //创建线程池
     executorService = Executors.newCachedThreadPool(threadFactory);

源码分析 缓存型线程池特性

   public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

从源码中我们发现缓存型线程池核心线程数是0, 非核心线程数是整型的最大值, 非核心线程的空闲线程等待时间是60s 单位是秒,存储非核心线程的队列SynchronousQueue

第二步:创建任务(线程)

//任务
 private Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            try {
                Log.e("TAG","pb1:"+Thread.currentThread().getName());
                while(pb1.getProgress()< pb1.getMax()){
                    Thread.sleep(100);
                    pb1.setProgress(pb1.getProgress()+5);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

第三步: 提交任务

executorService.execute(runnable1);
2、newFixedThreadPool

第一步:初始化线程池

//初始化
 fixedExecutorService = Executors.newFixedThreadPool(corePoolSize,threadFactory);
源码分析 newFixedThreadPool特性
 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

非核心线程数与核心线程数相等,非核心线程数的等待时间是0毫秒,单位是毫秒, 表示非核心空闲线程不存在等待时间,即不会从缓存中移除

第二步:创建任务(线程)

//任务
 private Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            try {
                Log.e("TAG","pb1:"+Thread.currentThread().getName());
                while(pb1.getProgress()< pb1.getMax()){
                    Thread.sleep(100);
                    pb1.setProgress(pb1.getProgress()+5);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

第三步: 提交任务

fixedExecutorService.execute(runnable1);

3、newScheduledThreadPool

第一步:初始化线程池

//调度型线程池
 scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize,factory);

源码分析 调度型线程池特性

  public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

corePoolSize核心线程数,非核心线程数为整型的最大值, 非核心线程的空闲线程等待时间10L,单位是毫秒, 已提交但未执行的任务存储在DelayedWorkQueue队列中, threadFactory是生产线程的线程池

第二步:创建任务(线程)

//任务
 private Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            try {
                Log.e("TAG","pb1:"+Thread.currentThread().getName());
                while(pb1.getProgress()< pb1.getMax()){
                    Thread.sleep(100);
                    pb1.setProgress(pb1.getProgress()+5);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

第三步 提交任务

提交方式1:

//非延时周期的的任务提交
scheduledExecutorService.execute(runnable1);

提交方式2:

//延时任务提交执行
scheduledExecutorService.schedule(runnable1,10L,TimeUnit.SECONDS);

10L表示延时的时间,TimeUnit.SECONDS表示延时的时间单位 即延时10秒后提交执行任务

提交方式3:

scheduledExecutorService.scheduleAtFixedRate
(Runnable command, long initialDelay, long period, TimeUnit unit)

command提交的任务,initialDelay延时的时间 period 任务启动后每过period时间再次启动任务 即周期性的执行任务 unit表示时间的单位

4、newSingleThreadExecutor

第一步:初始化线程池

//创建线程池
 singleExecutorService = Executors.newSingleThreadExecutor(factory);

源码分析 newSingleThreadExecutor线程池的特性

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

核心线程数为1,非核心线程数为1, 非核心线程空闲线程等待时间0,即不从缓存中移除, 非核心线程的的队列采用LinkedBlockingQueue, threadFactory表示生产线程的工厂

第二步:创建任务(线程)

//任务
 private Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            try {
                Log.e("TAG","pb1:"+Thread.currentThread().getName());
                while(pb1.getProgress()< pb1.getMax()){
                    Thread.sleep(100);
                    pb1.setProgress(pb1.getProgress()+5);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

第三步 提交任务

singleExecutorService.execute(runnable1);

对比LinkedBlockingQueue和ArrayBlockingQueue区别结论:

(1)都是阻塞队列. (2)存储数据方式不一样:ArrayBlockingQueue使用数组方式,并且使用两个下标来表明取出数据的位置和加入数据的位置. LinkedBlockingQueue使用的是单项链表方式存储数据,使用头和尾节点来指明链表取出数据和加入数据的地方,并且头节点不存储数据. 都通过变量来记录存储数据的数量:ArrayBlockingQueue使用int变量来记录存储数据数量,而LinkedBlockingQueue使用线程安全的AtomicInteger来记录数据数量,很显然AtomicInteger的效率更低. (3)由于ArrayBlockingQueue采用数组方式存储数据,所以其最大容易是在定义ArrayBlockingQueue的时候就已经确定的.不能再次修改. 而LinkedBlockingQueue采用链表存储数据,所以其容易可以不用指定. (4)向对来说,由于ArrayBlockingQueue采用数组来存储数据,所有在加入数据和获取数据时候效率都会更高. (5)都是使用ReentrantLock来实现线程安全,不过LinkedBlockingQueue采用了两个重入锁,并且使用了 AtomicInteger,所以相对来说实现同步ArrayBlockingQueue更简单效率更高.

demo链接:  https://download.csdn.net/download/haoxuhong/10522474

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值