从原理上粗略认知Android线程池(ThreadPoolExecutor)

时间:2022-08-26 16:27:14

在开发过程中,可能经常遇到下面的开发场景,即一个列表加载网络图片,比如加载100个网络图片,我们是不是就要去新建100个线程来下载对应的图片,这种做法当然是能够实现功能的,但是100个图片就100个线程也太夸张了,可能导致线程之间互相抢占系统资源以及线程创建和销毁会给应用带来额外的性能开销。所以这个时候我们就想到了利用线程池来实现这个功能,在Android开发艺术探索一书中,总结了线程池的以下三个优点。

1、重用线程池中的线程,避免因为线程池的创建和销毁所带来的性能开销;

2、有效控制线程的最大并发数,避免大量线程之间因为枪战系统资源而出现的阻塞现象;

3、对线程进行简单的管理,并提供定时执行以及制定间隔循环执行等功能;

这里强调一点第三点,在Android中的线程池管理的线程不是开发者提供给线程池的线程,而是管理线程池自己创建的线程,这一点我之前没弄明白,这里强调一下。实际上,Android中的使用线程池也只需要开发者提供给线程Runnbble或者Callable对象实例。在调用线程池的execute方法时提供Runnable对象实例,而在调用submit方法时可以提供Callable对象实例,由于submit方法实际上还是调用了execute方法,所以本篇文章就以execute方法使用线程池来进行代码分析。

1、实例化线程池

在分析线程池原理之前来先看看实例化线程池的一些参数含义,这个参数的介绍很多博客文章都有,这里也简单说下。先看看ThreadPoolExecutor构造方法

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

可以看到这里有7个参数,之前一篇文章说的很形象,七个参数就是星期一到星期天,那么这七个参数的含义是什么呢?

corePoolSize

核心线程数,默认情况下,核心线程会一直在线程池中存活,即便它们处于闲置状态。但是如果将ThreadPoolExecutor的allowCoreThreadTimeOut设置为true,那么闲置的核心线程也会在超时时被终止,这个超时的时间间隔由keepAliveTime参数来决定。

maxPoolSize

最大线程数,上面说到了corePoolSize表示核心线程数,那么肯定就有非核心线程数,所以非核心线程数就是最大线程数减去核心线程数。

keepAliveTime

这个是非核心线程或者核心线程(allowCoreThreadTimeOut为true时)在闲置的时候能够存留的时间。

unit

前一个参数keekAliveTime指明了存留时间数值,但是没有说明计时单位,而这个参数就表示时间单位,可以是毫秒(TimeUnit.MILLISECONDS)、秒(TimeUnit.SECONDS)以及分钟(TimeUnit.MINUTES)等。

workQueue

BlockingQueue类型的队列对象,当Runnable对象超过核心线程时,剩下的Runnbable对象就放入workQueue中,当workQueue中放满了,之后继续添加Runnable对象时,则线程池会新建非核心线程来执行Runnable对象。需要说明的是BlockingQueue是阻塞队列,当它没有数据了,继续去取时会阻塞取数据的操作所在线程,当添加新的数据时会激活阻塞线程,这一点对于线程池能够正常工作十分重要。常用的BlockingQueue类型队列有ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue以及SynchronousQueue,不同的BlockingQueue会影响存储进去的数据(Runnable对象)取出来时的顺序。

threadFactory

线程工厂,顾名思义其提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法

Thread newThread(Runnable var1);

我们如果想自定义线程池创建线程的方法即可通过重写ThreadFactory来实现,不过一般都是使用默认的方法来创建线程,所以这里就不多介绍了。

rejectedHandler

当线程无法执行新任务时,则通过该参数来提供回调。RejectedExecutionHandler也是一个接口,也只有一个方法

void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);

如果开发者想处理无法执行新任务的情况,可以通过重写这个参数来实现,这个很简单,不影响本篇博客从原理上分析线程池,所以这里也使用默认值。

2、线程池使用方法

这里介绍一个及其简单的使用线程池的例子

int coreThreadNum = 3;
int maxThreadNum = 5;
int keepAliveTime = 1;
Executor poolExecutor = new ThreadPoolExecutor(coreThreadNum,maxThreadNum,keepAliveTime, TimeUnit.SECONDS,new LinkedBlockingQueue<>(128));

for (int i = 0; i < 30; i++) {
    final int finalI = i;
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            SystemClock.sleep(2000);
            Log.d(TAG, "run: " + finalI);
        }
    };
    poolExecutor.execute(runnable);
}

这里核心线程数为3,非核心线程数为2,闲置留存时间为1秒,BlockQueue的容量为128,输出时为每间隔2秒输出三组数据,最终完成各个Runnable任务的执行(图片和代码来自Android开发之线程池使用总结 这篇博客)
从原理上粗略认知Android线程池(ThreadPoolExecutor)

3、线程池执行任务规则说明

按照Android开发艺术探索艺术中所说,ThreadPoolExecutor执行任务时大致遵循如下规则

1、如果线程池中线程数量未达到核心线程数量是,那么直接新建并启动一个核心线程执行任务;

2、如果线程池中的线程数量已经达到或者超过核心线程数量,那么任务会被添加到workQueue中等待执行;

3、如果步骤2中无法将任务添加到workQueue中,这通常是workQueue已经满了,这个时候如果线程数量未达到规定的最大值,那么会立刻启动一个非核心线程来执行任务;

4、如果步骤3中的线程数量已经达到了线程池规定的最大值,那么就拒绝执行此任务,并通过调用RejectedExecutionHandler的rejectedExecution方法来通知调用者;

这里需要说明的是无论是第1步中的说的核心线程还是第3步中说的非核心线程,实际上只是这么称呼而已,都是普通的线程,没有什么特殊的,当线程中的线程数量小于核心线程池时新创建的线程被称为核心线程,大于核心线程数时创建的线程就叫做非核心线程,但是核心线程和非核心线程创建之后便无差别,都开始从workQueue中领任务执行。在核心线程和非核心线程闲置时,我们前面说过keepAliveTime时间到时会销毁非核心线程,实际上线程池在操作时只是从总的线程(核心线程和非核心线程)中随机的销毁线程,使得总的线程数量达到核心线程数而已。关于这一点会在后面的代码分析中加以论证。

这一节指出了线程池执行任务时的规则,那么为什么是这个规则呢,这里我们就来看看源码。因为线程池添加任务时执行如下代码

poolExecutor.execute(runnable);

那么我们就来看看ThreadPoolExecutor的execute方法

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    //workerCountOf方法获取线程池中有几个线程了
    if (workerCountOf(c) < corePoolSize) {

        //addWorker方法用来添加新的线程,true参数表示添加的是核心线程
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }

    //workQueue.offer方法添加Runnable任务和workQueue队列中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //添加不了就通过addWorker方法添加非核心线程
    else if (!addWorker(command, false))
        //非核心线程添加失败就通知调用者
        reject(command);
}

从对execute的源码和注释可以看出以上线程池执行基本规则是对的

4、workQueue中的任务如何被执行的

我们知道线程池中最多创建maxThreadSize这么多个线程,很可能很多任务都在workQueue中等待被线程执行,那么这些任务都怎么被放到线程中去执行的呢?

我们把目光放到execute方法中说的添加新线程的addWorker方法上来,这里截取addWorker的部分关键代码

try {
    //在Worker的构造函数中新建线程
    w = new Worker(firstTask);
    final Thread t = w.thread;
    if (t != null) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // Recheck while holding lock.
            // Back out on ThreadFactory failure or if
            // shut down before lock acquired.
            int rs = runStateOf(ctl.get());

            if (rs < SHUTDOWN ||
                (rs == SHUTDOWN && firstTask == null)) {
                if (t.isAlive()) // precheck that t is startable
                    throw new IllegalThreadStateException();
                workers.add(w);
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        if (workerAdded) {
            //启动线程
            t.start();
            workerStarted = true;
        }
    }
}

由注释可知,新线程时通过Workder的构造函数创建的

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

这里可以看出新建线程时,传了参数this,也就是Worker对象本身(Worker是Runnable的实现),同时也可以看出我们自己提供ThreadFactory的话也会在这里起作用。

在启动线程的时候,会调用现成的run方法

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

而这里的target就是上面的this,也就是Worker,所以新建线程的target.run方法自然就调用到了Worker的run方法中,继而调用到了ThreadPoolExecutor的runWorker方法,这里看看其部分代码

try {
    while (task != null || (task = getTask()) != null) {
        w.lock();
        // If pool is stopping, ensure thread is interrupted;
        // if not, ensure thread is not interrupted.  This
        // requires a recheck in second case to deal with
        // shutdownNow race while clearing interrupt
        if ((runStateAtLeast(ctl.get(), STOP) ||
             (Thread.interrupted() &&
              runStateAtLeast(ctl.get(), STOP))) &&
            !wt.isInterrupted())
            wt.interrupt();
        try {
            beforeExecute(wt, task);
            Throwable thrown = null;
            try {
                task.run();
            } catch (RuntimeException x) {
                thrown = x; throw x;
            } catch (Error x) {
                thrown = x; throw x;
            } catch (Throwable x) {
                thrown = x; throw new Error(x);
            } finally {
                afterExecute(task, thrown);
            }
        } finally {
            task = null;
            w.completedTasks++;
            w.unlock();
        }
    }
    completedAbruptly = false;
} 

runWorker方法中有一个while循环,其不断的通过getTask方法从workQueue中获取Runnable任务并通过task.run来执行任务(这里的task是Runnable对象)。这里的代码可以看出各个线程(核心和非核心)不断的从workerQueue中获取任务来执行,执行任务时并不区分核心和非核心线程。

我们再进一步的看看getTask方法

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

正常情况下getTask方法返回workQueue中的Runnable任务,我们注意到如下代码

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

这个代码决定了workQueue中无任务时,会有多少线程不被销毁,因为当workQueue中无任务时,执行workQueue.take方法会阻塞线程,而执行workQueue.poll时并不会阻塞线程。被阻塞掉的线程会保留,而没有阻塞的线程就会被销毁。这里很明显阻塞和销毁的线程都是随机的,allowCoreThreadTimeOut 为false的情况下会阻塞掉corePoolSize数量的线程,当allowCoreThreadTimeOut 为true时,所有线程都会被销毁。

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            //线程阻塞
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

根据代码take方法代码可知,在workQueue中无任务时,take方法所处的线程被阻塞,这就是说此时调用getTask的runWorker所在线程将被阻塞掉,而我们再看ThreadPoolExecutor中调用的workQueue.offer(command)压入Runnable任务时,会调用workQueue的offer方法

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    if (count.get() == capacity)
        return false;
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();//激活线程
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}

看注释位置signal将激活一个线程,同时因为workQueue中有了任务数据,所以getTask不再阻塞,线程继续运行获取到新的任务来执行。

到这里应该就说清楚了workQueue中添加的任务时如何被执行的,以及任务执行完后,以及后面新添加任务时,线程根据参数的变化(销毁、阻塞、激活)。

而且通过本小节,应该清楚实际上核心线程和非核心线程没啥区别,需要的收常见n(核心) + m(非核心)个线程,大家一起从workQueue中取活来干,活干完了,就从n + m个线程中随机干掉m个线程,以确保下次有活干的时候,还是有n个线程能干活。

需要注意到是本小节实际上await和signal的关系没有讲清楚,实际上我现在也不清楚,关于Condition的await和signal使用,以后弄清出了再另写一篇博文吧,这里只需要知道起到阻塞和激活线程的作用就行了。

5、ThreadPoolExecutor的submit方法

ThreadPoolExecutor的submit方法有以下几个重载方法

    /** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public Future<?> submit(Runnable task) 

    /** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public <T> Future<T> submit(Runnable task, T result) 

    /** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */
    public <T> Future<T> submit(Callable<T> task) 

由于执行原理基本相同,这里选取最后一个重载方法来说明submit的执行原理,这里直接引用Java多线程-线程池ThreadPoolExecutor的submit返回值Future 这篇文章的例子

try{
    ExecutorService executor = Executors.newFixedThreadPool(2);
    //创建一个Callable,3秒后返回String类型
    Callable myCallable = new Callable() {
        @Override
        public String call() throws Exception {
            Thread.sleep(3000);
            System.out.println("calld方法执行了");
            return "call方法返回值";
        }
    };
    System.out.println("提交任务之前 "+ getStringDate());
    Future future = executor.submit(myCallable);
    System.out.println("提交任务之后,获取结果之前 "+getStringDate());
    System.out.println("获取返回值: "+ future.get());
    System.out.println("获取到结果之后 "+getStringDate());
}catch (InterruptedException e){
    e.printStackTrace();
}catch (ExecutionException e){
    e.printStackTrace();
}

输出内容为

提交任务之前 12:13:01
提交任务之后,获取结果之前 12:13:01
calld方法执行了
获取返回值: call方法返回值
获取到结果之后 12:13:04

从例子可以看到,submit方法和execute方法不同

1、submit方法提供了Future 类型的返回值;
2、Future的get方法返回submit参数Callable 对象重载重载方法call的返回值;
3、Future对象的get方法是一个阻塞方法,会阻塞掉当前所在线程,知道获取到返回值(call方法执行完毕);

这里就从源码的角度简单分析下,首先看看submit方法

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

这里通过newTaskFor方法实例化一个Future对象,看看newTaskFor方法,直接通过new FutureTask,来创建一个FureTask对象(Future的子类对象,同时也是Runnable的子类对象)。

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

我们知道线程执行时会执行到Runnable的run方法,这里看看FureTask的run方法,这里只取其run方法的部分代码

try {
    Callable<V> c = callable;
    if (c != null && state == NEW) {
        V result;
        boolean ran;
        try {
            result = c.call();
            ran = true;
        } catch (Throwable ex) {
            result = null;
            ran = false;
            setException(ex);
        }
        if (ran)
            set(result);
    }
}

在FutureTask的run方法中,我们看到执行了Callable的call方法,也就是我们在例子中重载的call方法,到此ThreadPoolExecutor的submit方法源码分析就算完成。那么我们提到的Future的get方法是一个阻塞方法,怎么理解呢,实际上submit方法返回的是一个FutureTask对象,那么看看FutureTask对象的get方法便知

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

很明显,这里的awaitDone方法便是一个阻塞方法阻塞了当前线程直到call方法执行完成,而为什么get方法返回的值是call返回值呢,这个需要结合FutureTask的run、get以及report这几个方法来分析,这实际上不难,感兴趣的童鞋可以自己分析看看。

6、常用的线程池总结

在开发过程中,我们可以自己通过不同核心线程、总线程等参数来构造线程池,也可以通过Executors提供的方法来直接构造常用的线程池,这里直接引用Android开发之线程池使用总结 这篇文章的例子吧。常用的线程池有FixedThreadPool、SingleThreadExecutor、CachedThreadPool以及ScheduledThreadPool几种,它们有不同的使用环境,这里来看看

6.1、FixedThreadPool

FixedThreadPool是一个只有核心线程,没有非核心线程的线程池,创建方式如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); 

源码如下:

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

从源码中可以看出FixedThreadPool核心线程数和最大线程数一样,说明在FixedThreadPool中没有非核心线程,所有的线程都是核心线程,且线程的超时时间为0,说明核心线程即使在没有任务可执行的时候也不会被销毁(这样可让FixedThreadPool更快速的响应请求),最后的线程队列是一个LinkedBlockingQueue,但是LinkedBlockingQueue却没有参数,这说明线程队列的大小为Integer.MAX_VALUE(2的31次方减1)。看完参数,我们大概也就知道了FixedThreadPool的工作特点了,当所有的核心线程都在执行任务的时候,新任务将会被放入workQueue队列中,等待被线程执行。这里来看一个例子

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  

for (int i = 0; i < 30; i++) {  
    final int finalI = i;  
    Runnable runnable = new Runnable(){  
        @Override  
        public void run() {  
            SystemClock.sleep(3000);  
            Log.d("google_lenve_fb", "run: "+ finalI);  
        }  
    };  
    fixedThreadPool.execute(runnable);  
}  

执行结果如下:
从原理上粗略认知Android线程池(ThreadPoolExecutor)

从执行结果可以看出FixedThreadPool线程池,创建了上线程,且三个线程从workQueue中取任务来执行,由于每个任务的执行时间一样,所以从结果上来看,就是每隔3秒钟三个线程就差不多同时输出一次日志。这里30个任务呗执行完成后,FixedThreadPool创建的是哪个线程不会被销毁,当workQueue中有新的任务时会被激活来继续执行任务,这样就避免了线程创建和销毁的系统开销。

6.2、SingleThreadExecutor

SingleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心线程数只有1个,最大线程也是1个,如下所示

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

从源码我们可以看出,实际上SingleThreadExecutor就是FixedThreadPool的一个特例(至于为啥名字不叫SingleThreadExecutor,这个其实也无所谓反正都是返回ThreadPoolExecutor就是了)。当FixedThreadPool的参数为1时,实际上就是创建了一个SingleThreadExecutor。使用SingleThreadExecutor,线程池中始终只有一个线程执行任务,这实际上就相当与我们开了一个单线程执行任务,不过这里的这种单线程会不断的workQueue中获取任务来执行,而且在任务执行完成之后,该线程不会被销毁。这里来看个例子

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();  

for (int i = 0; i < 30; i++) {  
    final int finalI = i;  
    Runnable runnable = new Runnable() {  
        @Override  
        public void run() {  
            Log.d("google_lenve_fb", "run: " + Thread.currentThread().getName() + "-----" + finalI);  
            SystemClock.sleep(1000);  
        }  
    };  
    singleThreadExecutor.execute(runnable);  
}  

执行效果如下:
从原理上粗略认知Android线程池(ThreadPoolExecutor)
从执行效果也可以验证我们前面说的一个线程从workQueue中获取任务执行的说法,这里每隔1秒总输出一条日志。

6.3、CachedThreadPool

CachedTreadPool一个特点是它可以根据程序的运行情况自动来调整线程池中的线程数量,CachedThreadPool源码如下

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

通过源码可知,CachedThreadPool中没有和线程,而最大线程数是Integer.MAX_VALUE,实际上可以理解为CachedThreadPool可以创建无穷多个非核心线程。同时,CachedThreadPool的超时时间为60秒,这说明创建的这些非核心线程在没有任务执行时,60秒回会被销毁掉。而当有新的任务时如果有空闲的线程则由空闲线程执行任务,如果没有空闲的线程则,创建新的线程来执行任务。由此可知CacheThreadPool适合有大量任务请求时使用,比如文章开始提到的列表的大量图片下载请求。这里来看看一个使用CachedThreadPool的例子

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  

for (int i = 0; i < 30; i++) {  
    final int finalI = i;  
    Runnable runnable = new Runnable(){  
        @Override  
        public void run() {  
            Log.d("google_lenve_fb", "run: " + Thread.currentThread().getName() + "----" + finalI);  
        }  
    };  
    cachedThreadPool.execute(runnable);  
    SystemClock.sleep(2000);  
}  

由于每次添加完任务之后都停两秒再添加新任务,并且添加的任务不费时,可以推测这里所有的任务都会由同一个线程来执行(因为每次添加新任务的时候都有空闲的线程),运行结果如下

从原理上粗略认知Android线程池(ThreadPoolExecutor)

由输出日志可以看到和我们的推测一致,基本是隔两秒由同一线程输出日志,这里把代码稍微改动一下

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  

for (int i = 0; i < 30; i++) {  
    final int finalI = i;  
    Runnable runnable = new Runnable(){  
        @Override  
        public void run() {  
            SystemClock.sleep(2000);  
            Log.d("google_lenve_fb", "run: " + Thread.currentThread().getName() + "----" + finalI);  
        }  
    };  
    cachedThreadPool.execute(runnable);  
    SystemClock.sleep(1000);  
}  

每个任务在执行的过程中都先休眠两秒,但每隔一秒便向CachedTreadPool中添加一个线程,也就是说一个线程都没有执行完任务的时候,就会有新的任务添加到workQueue中,那么按照上面说的,CachedTreadPool便会创建新的线程来执行任务。再进一步分析一下线程1在执行第一个任务时,添加了第二个任务,此时线程1不空闲,那么线程池会添加新的线程2执行第二个任务,当第三个任务到来之时,线程1应该已经空闲了(也不一定),那么第三个任务可能就是线程1来执行,当有第四个任务时线程2应该空闲了,第四个任务便由线程2来执行,所以理论上只需要两个线程便能完成任务执行,而实际上由于CPU的时间片轮转并不是公平的,所以可能两个线程不够用,但可以肯定的是基本3个线程可以搞定这里的任务执行,所以就有了下面的运行结果

从原理上粗略认知Android线程池(ThreadPoolExecutor)

6.4、ScheduledThreadPool

ScheduledThreadPool是一个具有定时定期执行任务功能的线程池,源码如下:

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

从源码可以看出,ScheduledThreadPool的核心线程数量是固定的,但是非核心线程是无穷大。这里的DEFAULT_KEEPALIVE_MILLIS参数值如下

private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;

但是单位却是MILLISECONDS,所以非核心线程闲置时,将会被很快回收。

使用ScheduledThreadPool时,可以通过如下几个方法来添加任务

1、延迟启动任务:

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit); 

示例代码如下

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);  

Runnable runnable = new Runnable(){  
    @Override  
    public void run() {  
        Log.d("google_lenve_fb", "run: ----");  
    }  
};  

scheduledExecutorService.schedule(runnable, 1, TimeUnit.SECONDS); 

这里将延迟一秒钟启动任务,ScheduledThreadPool线程池将正常的执行任务。

2、延迟定时执行任务:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,  
                                              long initialDelay,  
                                              long period,  
                                              TimeUnit unit);  

延迟initialDelay单位时间后每隔period单位时间执行一次任务。示例代码如下

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);  

Runnable runnable = new Runnable(){  
    @Override  
    public void run() {  
        Log.d("google_lenve_fb", "run: ----");  
    }  
};  

scheduledExecutorService.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS); 

延迟1秒之后线程池开始执行任务,以后每隔1秒执行一次新任务。

3、延迟执行任务

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,  
                                                 long initialDelay,  
                                                 long delay,  
                                                 TimeUnit unit);  

第一次延迟initialDelay单位时间,以后每次延迟delay单位时间执行一个任务。示例代码:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);  

Runnable runnable = new Runnable(){  
    @Override  
    public void run() {  
        Log.d("google_lenve_fb", "run: ----");  
    }  
};  

scheduledExecutorService.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS);  

第一次延迟1秒之后,以后每次也延迟1秒执行,从字面上理解这个延迟执行任务和上面的延迟定时执行任务好像最终表现都一样,这里具体怎么回事这篇博文也不深究了,这里只需要知道有一个ScheduledThreadPool线程池可以控制任务的执行时间就行了,在实际开发过程中有对应使用场景的时候再来研究具体使怎么回事
,当然感兴趣的童鞋也可以去研究研究ScheduledThreadPoolExecutor这个类。

7、总结

到这里关于Android线程池的介绍基本就告一段落了,通过以上介绍,我们应该能够明白文章开头提出的线程池的以下几个优点了

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

另外通过本篇博文的介绍还应该明白线程池的基本工作原理,在这里非常感谢上面提到文献的几位同学的贡献,本文直接引用了以上几篇文献的代码和一些博文内容,非常感谢,如有侵权,麻烦告知删除,谢谢!

参考文献

1、深入理解在Android中线程池的使用
2、Android开发之线程池使用总结
3、Android开发艺术探索 [任玉刚]
4、并发编程3:线程池的使用与执行流程
5、Java进阶(三)多线程开发关键技术
6、Java多线程-线程池ThreadPoolExecutor的submit返回值Future