Android 线程池框架、Executor、ThreadPoolExecutor详解

来源:互联网 发布:游戏匹配算法 编辑:程序博客网 时间:2024/06/13 00:00

Java/Android线程池框架的结构主要包括:

1.任务:包括被执行任务需要实现的接口类:Runnable 或 Callable

2.任务的执行器:包括任务执行机制的核心接口类Executor,以及继承自Executor的EexcutorService接口。

3.执行器的创建者,工厂类Executors

一、Executor 和 ExecutorService

Executor只是一个接口,它是Java/Android线程池框架的基础,它将任务的提交与任务的执行分离开来。

ExecutorService继承自Executor,有两个关键类实现了ExecutorService接口:ThreadPoolExecutor和ScheduledThreadPoolExecutor。

(1)ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。
(2)ScheduledThreadPoolExecutor 也是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。它比Timer更灵活,功能更强大。


android中线程池的概念来源于java中的Executor, 线程池真正的实现类是ThreadPoolExecutor,它间接实现了Executor接口。ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数配置实现不同功能特性的线程池,android中的Executors类提供了4个工厂方法用于创建4种不同特性的线程池给开发者用.
android中的线程池都是直接或是间接通过配置ThreadPoolExecutor来实现的.

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

ThreadPoolExecutor 的配置参数
public ThreadPoolExecutor(int corePoolSize,                      int maximumPoolSize,                      long keepAliveTime,                      TimeUnit unit,                      BlockingQueue<Runnable> workQueue,                      ThreadFactory threadFactory,                      RejectedExecutionHandler handler) {    this.corePoolSize = corePoolSize;    this.maximumPoolSize = maximumPoolSize;    this.workQueue = workQueue;    this.keepAliveTime = unit.toNanos(keepAliveTime);    this.threadFactory = threadFactory;    this.handler = handler;}corePoolSize: 线程池的核心线程数,默认情况下, 核心线程会在线程池中一直存活, 即使处于闲置状态. 但如果将allowCoreThreadTimeOut设置为true的话, 那么核心线程也会有超时机制, 在keepAliveTime设置的时间过后, 核心线程也会被终止.maximumPoolSize: 最大的线程数, 包括核心线程, 也包括非核心线程, 在线程数达到这个值后,新来的任务将会被阻塞.keepAliveTime: 超时的时间, 闲置的非核心线程超过这个时长,讲会被销毁回收, 当allowCoreThreadTimeOut为true时,这个值也作用于核心线程.unit:超时时间的时间单位.workQueue:线程池的任务队列, 通过execute方法提交的runnable对象会存储在这个队列中.threadFactory: 线程工厂, 为线程池提供创建新线程的功能.handler: 任务无法执行时,回调handler的rejectedExecution方法来通知调用者.AsyncTask的底层实现ThreadPoolExecutor

AsyncTask的底层实现ThreadPoolExecutor
AsyncTask对ThreadPoolExecutor进行参数配置,它的核心底层实现就是线程池.public abstract class AsyncTask<Params, Progress, Result> {    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();//CPU数    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);    public static final Executor THREAD_POOL_EXECUTOR            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);}配置的参数归纳为:核心线程数 = CPU数+1最大线程数 = CPU数*2 + 1非核心线程的超时时间为1秒任务队列的容量为128

二、Executors工厂类

Executors是一个工厂类,它不继承任何其它类,它通过ThreadPoolExecutor、ScheduledThreadPoolExecutor创建出四种不同的线程池,分别为:

(1)newCachedThreadPool 创建一个可缓存线程池,线程池的最大长度无限制,但如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

Executors.java

[java] view plain copy
print?
  1. public static ExecutorService newCachedThreadPool() {  
  2.     return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
  3.                                      60L, TimeUnit.SECONDS,  
  4.                                      new SynchronousQueue<Runnable>());  
  5. }  

特点:没有核心线程,非核心线程数量没有限制, 超时为60秒.
适用于执行大量耗时较少的任务,当线程闲置超过60秒时就会被系统回收掉,当所有线程都被系统回收后,它几乎不占用任何系统资源.


使用示例:

[java] view plain copy
print?
  1. ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
  2. for (int i = 0; i < 10; i++) {  
  3.     final int index = i;  
  4.     try {  
  5.         Thread.sleep(index * 1000);  
  6.     } catch (InterruptedException e) {  
  7.         e.printStackTrace();  
  8.     }  
  9.    
  10.     cachedThreadPool.execute(new Runnable() {  
  11.    
  12.         @Override  
  13.         public void run() {  
  14.             System.out.println(index);  
  15.         }  
  16.     });  
  17. }  

(2)newFixedThreadPool  创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

Executors.java

[java] view plain copy
print?
  1. public static ExecutorService newFixedThreadPool(int nThreads) {  
  2.     return new ThreadPoolExecutor(nThreads, nThreads,  
  3.                                   0L, TimeUnit.MILLISECONDS,  
  4.                                   new LinkedBlockingQueue<Runnable>());  
  5. }  

特点:只有核心线程数,并且没有超时机制,因此核心线程即使闲置时,也不会被回收,因此能更快的响应外界的请求.

使用示例:

[java] view plain copy
print?
  1. ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
  2. for (int i = 0; i < 10; i++) {  
  3.     final int index = i;  
  4.     fixedThreadPool.execute(new Runnable() {  
  5.    
  6.         @Override  
  7.         public void run() {  
  8.             try {  
  9.                 System.out.println(index);  
  10.                 Thread.sleep(2000);  
  11.             } catch (InterruptedException e) {  
  12.                 // TODO Auto-generated catch block  
  13.                 e.printStackTrace();  
  14.             }  
  15.         }  
  16.     });  
  17. }  
(3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

Executors.java

[java] view plain copy
print?
  1. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {  
  2.     return new ScheduledThreadPoolExecutor(corePoolSize);  
  3. }  

public ScheduledThreadPoolExecutor(int corePoolSize) {
      super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}

特点:核心线程数是固定的,非核心线程数量没有限制, 没有超时机制.
主要用于执行定时任务和具有固定周期的重复任务.

使用示例:表示延迟3秒执行

[java] view plain copy
print?
  1. ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);  
  2. scheduledThreadPool.schedule(new Runnable() {  
  3.    
  4.     @Override  
  5.     public void run() {  
  6.         System.out.println("delay 3 seconds");  
  7.     }  
  8. }, 3, TimeUnit.SECONDS);  
使用示例:表示延迟1秒后每3秒执行一次
[java] view plain copy
print?
  1. scheduledThreadPool.scheduleAtFixedRate(new Runnable() {  
  2.    
  3.     @Override  
  4.     public void run() {  
  5.         System.out.println("delay 1 seconds, and excute every 3 seconds");  
  6.     }  
  7. }, 13, TimeUnit.SECONDS);  

(4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

Executors.java

[java] view plain copy
print?
  1. public static ExecutorService newSingleThreadExecutor() {  
  2.     return new FinalizableDelegatedExecutorService  
  3.         (new ThreadPoolExecutor(11,  
  4.                                 0L, TimeUnit.MILLISECONDS,  
  5.                                 new LinkedBlockingQueue<Runnable>()));  
  6. }  

特点:只有一个核心线程,并没有超时机制.
意义在于统一所有的外界任务到一个线程中, 这使得在这些任务之间不需要处理线程同步的问题.

使用示例:

[java] view plain copy
print?
  1. ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();  
  2. for (int i = 0; i < 10; i++) {  
  3.     final int index = i;  
  4.     singleThreadExecutor.execute(new Runnable() {  
  5.    
  6.         @Override  
  7.         public void run() {  
  8.             try {  
  9.                 System.out.println(index);  
  10.                 Thread.sleep(2000);  
  11.             } catch (InterruptedException e) {  
  12.                 // TODO Auto-generated catch block  
  13.                 e.printStackTrace();  
  14.             }  
  15.         }  
  16.     });  
  17. }  

ScheduledThreadPoolExecutor的scheduleAtFixedRate方法探究

ScheduledThreadPoolExecutor除了具有ThreadPoolExecutor的所有功能外,还可以延迟执行任务或者周期性的执 行某个任务。scheduleWithFixedDelay和scheduleAtFixedRate就是用来完成这个功能的。平常使用 scheduleAtFixedRate这个方法时并没有多想,但是这几天在实现一个功能的时候,需要考虑scheduleAtFixedRate所执行 的task是否会影响任务的周期性,比如scheduleAtFixedRate(command,5,10,TimeUnit.SECONDS),那么 这个command的执行会不会影响这个10秒的周期性。因此特意仔细看了下ScheduledThreadPoolExecutor的源代码,这里记录 一下,以便以后查看。

    scheduleAtFixedRate有两个时间参数,initialDelay和period,对应该方法的两个主要功能,即延迟运行任务和周期性执行任务。

 

  1. public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,  
  2.                                               long initialDelay,  
  3.                                               long period,  
  4.                                               TimeUnit unit) {  
  5.     if (command == null || unit == null)  
  6.         throw new NullPointerException();  
  7.     if (period <= 0)  
  8.         throw new IllegalArgumentException();  
  9.     RunnableScheduledFuture<?> t = decorateTask(command,  
  10.         new ScheduledFutureTask<Object>(command,  
  11.                                         null,  
  12.                                         triggerTime(initialDelay, unit),  
  13.                                         unit.toNanos(period)));  
  14.     delayedExecute(t);  
  15.     return t;  
  16. }  
  17.   
  18. /** 
  19.  * Specialized variant of ThreadPoolExecutor.execute for delayed tasks. 
  20.  */  
  21. private void delayedExecute(Runnable command) {  
  22.     if (isShutdown()) {  
  23.         reject(command);  
  24.         return;  
  25.     }  
  26.     // Prestart a thread if necessary. We cannot prestart it  
  27.     // running the task because the task (probably) shouldn't be  
  28.     // run yet, so thread will just idle until delay elapses.  
  29.     if (getPoolSize() < getCorePoolSize())  
  30.         prestartCoreThread();  
  31.   
  32.     super.getQueue().add(command);  
  33. }  

    首先创建一个ScheduledFutureTask,然后通过delayedExecute执行这个task。在delayedExecute中,首先 预先启动一个线程,这里要注意的是这个这里用来启动一个新线程的firstTask参数是null,所以新启动的线程是idle状态的,然后把这个 task加入到workQueue。ScheduledThreadPoolExecutor里的workQueue用的是 DelayedWorkQueue,这个DelayedWorkQueue就是实现delay的关键。DelayedWorkQueue内部使用的是 DelayQueue,DelayQueue实现task delay的关键就在于其Offer(E e)和Take.下面,通过分析这两个方法和结合ThreadPoolExecutor的运行原理来说明delay操作是如何实现的

 

  1. public boolean offer(E e) {  
  2.     final ReentrantLock lock = this.lock;  
  3.     lock.lock();  
  4.     try {  
  5.         E first = q.peek();  
  6.         q.offer(e);  
  7.         if (first == null || e.compareTo(first) < 0)  
  8.             available.signalAll();  
  9.         return true;  
  10.     } finally {  
  11.         lock.unlock();  
  12.     }  
  13. }  
  14.   
  15. public E take() throws InterruptedException {  
  16.     final ReentrantLock lock = this.lock;  
  17.     lock.lockInterruptibly();  
  18.     try {  
  19.         for (;;) {  
  20.             E first = q.peek();  
  21.             if (first == null) {  
  22.                 available.await();  
  23.             } else {  
  24.                 long delay =  first.getDelay(TimeUnit.NANOSECONDS);  
  25.                 if (delay > 0) {  
  26.                     long tl = available.awaitNanos(delay);  
  27.                 } else {  
  28.                     E x = q.poll();  
  29.                     assert x != null;  
  30.                     if (q.size() != 0)  
  31.                         available.signalAll(); // wake up other takers  
  32.                     return x;  
  33.   
  34.                 }  
  35.             }  
  36.         }  
  37.     } finally {  
  38.         lock.unlock();  
  39.     }  
  40. }  

      ScheduledThreadPoolExecutor执行task是通过工作线程Work来承担的,Work的Run方法如下:

 

  1. public void run() {  
  2.     try {  
  3.         Runnable task = firstTask;  
  4.         firstTask = null;  
  5.         while (task != null || (task = getTask()) != null) {  
  6.             runTask(task);  
  7.             task = null;  
  8.         }  
  9.     } finally {  
  10.         workerDone(this);  
  11.     }  
  12. }  

     因为前面在delayedExecute方法里面创建work线程的firstTask参数为null,所以就通过getTask去从workQueue 里面获取task,getTask在正常情况下(即线程池没有关闭,线程数量没有超过corePoolSize等)是通过 workQueue.take()从workQueue里获取任务。根据上面的贴出来的take方法的代码,如果queue是空的,则take方法会阻塞 住,直到有新task被add进来。而在上面的delayedExecute方法的最后,会把创建的scheduledFutureTask加入到 workQueue,这样take方法中的available.await()就被唤醒;在take方法里面,如果workQueue不为空,则执行 task.getDelay()方法获取task的delay

  1. public long getDelay(TimeUnit unit) {  
  2.     return unit.convert(time - now(), TimeUnit.NANOSECONDS);  
  3. }  

   这里的time是通过两个方法把initialDelay变成一个triggerTime

  1. /** 
  2.  * Returns the trigger time of a delayed action. 
  3.  */  
  4. private long triggerTime(long delay, TimeUnit unit) {  
  5.      return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));  
  6. }  
  7.   
  8. /** 
  9.  * Returns the trigger time of a delayed action. 
  10.  */  
  11. long triggerTime(long delay) {  
  12.      return now() +  
  13.          ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));  
  14. }  

注意看这个方法,这里返回的delay不是固定不变的,从task被放入workQueue起,不同的时间调用getDelay方法会得出不同的 delay。如果放入workQueue的task的initialDelay是5秒,那么根据take方法的代码,如果在放入workQueue5秒 后,就可以从delayQueue中拿到5秒前put进去的task,这样就实现了delay的功能。

 

   在本文的最前面提到scheduleAtFixedRate能够周期性地执行一项任务,那么这个是如何实现的呢?在 scheduleAtFixedRate方法里创建了一个ScheduledFutureTask,这个ScheduledFutureTask包装了 command,最后周期性执行的是ScheduledFutureTask的run方法。

  1. private void runPeriodic() {  
  2.     boolean ok = ScheduledFutureTask.super.runAndReset();  
  3.     boolean down = isShutdown();  
  4.     // Reschedule if not cancelled and not shutdown or policy allows  
  5.     if (ok && (!down ||  
  6.                (getContinueExistingPeriodicTasksAfterShutdownPolicy() &&  
  7.                 !isStopped()))) {  
  8.         long p = period;  
  9.         if (p > 0)  
  10.             time += p;  
  11.         else  
  12.             time = triggerTime(-p);  
  13.         ScheduledThreadPoolExecutor.super.getQueue().add(this);  
  14.     }  
  15.     // This might have been the final executed delayed  
  16.     // task.  Wake up threads to check.  
  17.     else if (down)  
  18.         interruptIdleWorkers();  
  19. }  
  20.   
  21. /** 
  22.  * Overrides FutureTask version so as to reset/requeue if periodic. 
  23.  */  
  24. public void run() {  
  25.     if (isPeriodic())  
  26.         runPeriodic();  
  27.     else  
  28.         ScheduledFutureTask.super.run();  
  29. }  

     由上面的代码可以看出,scheduleAtFixedRate(command,5,10,TimeUnit.SECONDS)这个方法的周期性会受 command的影响,如果command方法的执行时间是10秒,那么执行command的周期其实是20秒,即 scheduleAtFixedRate这个方法要等一个完整的command方法执行完成后才继续周期性地执行command方法。