Android中的线程池

在操作系统中,线程是操作系统调度的最小单元,同时线程也是受限制的一种系统资源,所以不可能无限制的产生,线程的创建和销毁都需要一定的开销。线程量大时,系统不可能做到所有线程同步进行,除非线程数量小于CPU核心数,但是一般来说是不可能的。在一个进程中频繁的创建和销毁线程同样不是一种高效的做法,这时,我们就可以采用线程池的方式来管理线程。
线程池可以缓存一定数量的线程,可以避免因频繁的创建和销毁所带来的开销。

线程池的优点

线程池可以管理一定数量的线程,这样就方便我们的线程调度,他具备以下的一些优点

  1. 具有重用性,可以重用线程池中线程,避免更多的开销

  2. 有效的控制线程池中最大的并发数,避免线程之间抢占资源导致阻塞

  3. 可以对线程进行简单的管理,提供间隔和循环执行的一些功能

其实Android中的线程池来源于java中的Executor,这个仅仅是一个接口,真正实现是通过ThreadPoorExecutor实现的。Android中线程池都是通过直接或间接通过ThreadPoorExecutor操作的,所以我们先看一下这个类


ThreadPoorExecutor

ThreadPoorExecutor是线程池的真正实现,他的构造方法提供了一些参数来配置线程池

构造方法

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

下面我们来看一下,具体参数的含义

  1. corePoolSize:线程池中所保存的核心线程数,核心线程数会一直存在,即使处于空闲状态也不会销毁,所以包括空闲线程。

  2. maximumPoolSize:线程池中允许的最大线程数,当活动线程数目达到这个数目后,后续任务会被阻塞

  3. keepAliveTime
    当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间,即非核心线程闲置时的超长时间,超过这个时长,非核心线程就会被回收

  4. unit
    用于指定keepAliveTime参数时间的单位,常用TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)等

  5. workQueue
    线程池中的任务队列,此队列仅保存由线程池execute 方法提交的 Runnable 任务

  6. threadFactory
    线程工厂,为线程池提供创建新线程的任务,ThreadFactory是一个接口,只有一个方法Thread newThread(Runnable r)

ThreadPoolExecutor 将根据 corePoolSize和 maximumPoolSize设置的边界自动调整池大小。

1)当新任务通过 execute 方法提交时,如果运行的线程少于 corePoolSize,则会直接创建新线程来处理请求,即使其他辅助线程是空闲的。

2)如果运行的线程多于corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建一个非核心线程执行任务,队列不满时会插入任务队列中排队等待执行

3)如果此时线程数量已达到maximumPoolSize且队列已满,就会调用RejectedExecutionHandler的rejectedExecution方法通知调用者拒绝执行此任务。


使用场景

如果设置的 corePoolSize 和 maximumPoolSize相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。

在AsyncTask中就用到了ThreadPoorExecutor,我们具体看一下其配置情况

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

通过以上信息,我们可以得知,AsyncTask的核心线程数(corePoolSize)是当前CPU数目+1; 最大线程数(maximumPoolSize)是CPU数目*2+1; 任务队列的容量是128; 非核心线程闲置超时时间为1秒。


线程池分类

在Android中,最常见的四种具有不同功能的线程池都是通过ThreadPoorExecutor配置的,通过不同的配置来实现自己一定特殊的功能作用,创建他们要通过Executors工厂类,他专门创建各种线程池。
Android常用的线程池有以下几种,在Executors里面都有对应的方法来创建ExecutorService(实现了Executor接口的接口类)

FixedThreadPool

可重用固定线程数的线程池:是一种线程数量固定的线程池,当线程处于空闲状态时并不会回收,除非关闭了线程池。当所有线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲下来,这种线程池只有核心线程且不会被回收,所以能够更快的处理请求

FixedThreadPool实现方法如下

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
 }

可以看到FixedThreadPool中只有核心线程且这些核心线程没有超时机制,任务的队列也没有限制大小,定长线程池的大小最好根据系统资源进行设置,如Runtime.getRuntime().availableProcessors()

适用于一定数目的耗时操作

下面配合一段代码

 ExecutorService fixExecutor =   Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.v("myTag", "do:" + index + "..." + Thread.currentThread() + ";time" + System.currentTimeMillis());
                }
            });
 }

在我的测试机中,先显示了8个,然后显示了2个,可以看出我的测试机是具有8个CPU处理器

CachedThreadPool

根据需要创建新线程的线程池:其线程数量不确定且只有非核心线程,最大线程数为MAX_VALUE,表示最大线程数目可以无限大。当线程池中的线程都处于活动状态的时候,线程池会产生新线程来处理任务,否则会用空闲线程处理任务,空闲机制都包含超时机制,60s后闲置线程会被收回。在这个线程池中任何任务都会被立即执行

CachedThreadPool的实现方法如下

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

当整个线程池闲置下来,线程池中的线程都会被闲置超时回收,所以他几乎是不占用任何资源的

适用于执行量大的但是耗时较少的任务

看一段代码


        ExecutorService fixExecutor = Executors.newCachedThreadPool();
        for(int i=0; i<100; i++){
            final int index = i;
            fixExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.v("myTag", "do:" + index + "..." + Thread.currentThread() + ";time" + System.currentTimeMillis());
                }
            });
        }

这段代码执行后在控制台瞬间执行完毕100条信息,可见线程池会产生MAX_VALUE的新线程来处理任务

ScheduledThreadPool

调度型线程池:其核心线程的数量是固定的,非核心线程数量是没有限制的,无超时机制,表示非核心线程闲置时会立即收回

ScheduledThreadPool的实现方法如下

public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

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

这个池子里的线程可以按schedule依次delay执行,或周期执行

适用于执行定时任务和具有固定周期的重复任务


SingleThreadExecutor

内部只有一个核心线程,确保所有任务在线程中按照顺序执行,意义在于统一所有外界任务到一个线程中,使得这些任务之间不需要处理线程同步的问题,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

SingleThreadExecutor的实现方法如下

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

可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的,用的是和cache池和fixed池相同的底层池

适用于需要按照指定顺序(FIFO, LIFO, 优先级)执行的任务

当然,我们也可以根据实际情况灵活的配置线程池,达到我们需要的效果和功能

看一下代码

ExecutorService fixExecutor = Executors.newSingleThreadExecutor();
        for(int i=0; i<5; i++){
            final int index = i;
            fixExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.v("myTag", "do:" + index + "..." + Thread.currentThread() + ";time" + System.currentTimeMillis());
                }
            });
  }

运行后是一个一个的执行输出效果,可以看到是队列形式输出,并不存在并发执行。
以上就是对Android线程池的一点总结恩

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值