Android知识点整理4:线程池

目录

1、简介

2、Android系统提供的四种线程池使用

(1)、Executors.newSingleThreadExecutor

 (2)、Executors.newFixedThreadPool

(3)、Executors.newCachedThreadPool

(4)、Executors.newScheduledThreadPool

3、自定义线程池

(1)、线程池部分关键类继承实现以及成员变量定义  

(2)、构造方法

(3)、线程池工作原理

(4)、线程池如何复用的

(5)、线程回收

(6)、阻塞队列

(7)、拒绝策略

(8)、关闭线程池

4、 合理配置线程池


1、简介

线程池是一种多线程处理形式,处理过程中将任务提交,然后根据条件创建线程或者复用空闲线程,然后自动启动这些任务。

2、Android系统提供的四种线程池使用

(1)、Executors.newSingleThreadExecutor

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

单线程线程池,核心线程数 和 最大线程数 都是1,阻塞队列是 无界的阻塞队列,有任务添加不停入阻塞队列。直到核心线程空闲,再复用核心线程,是一个串行任务链。

 (2)、Executors.newFixedThreadPool

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

 和newSingleThreadExecutor和相似,核心线程数 和 最大线程数都是nThreads,有固定大小的线程池,最多同时进行nThreads个任务。多余的任务会进入阻塞队列。

(3)、Executors.newCachedThreadPool

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


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

通过构造参数可以看出,cacheThreadPool 线程池没有 核心线程数量,使用的阻塞队列是 SynchronousQueue队列,这个阻塞队列特点是无实际存储 ,只有正好有空闲线程,才会入队成功,否则总是创建新线程,直到达到maximumPoolSize。

然后空闲线程可存活时间是 60 s,就是一种可以无限创建线程的线程池,有空闲机制,可以重复利用空闲线程。

(4)、Executors.newScheduledThreadPool

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

任务周期线程池可以用来执行 定时任务,支持定时及周期性任务执行,阻塞队列使用的是DelayedWorkQueue,DelayedWorkQueue也是一种无界的阻塞队列,用数组实现堆排序,具体原理可以看这个链接:https://www.jianshu.com/p/587901245c95

执行的方法和前面三种不太一样,前面三种是使用 如下方式:

ExecutorService threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
       threadPoolExecutor.execute(new Runnable() {
           @Override
           public void run() {
               
           }
       });

使用的是 execute方法,提交一个任务

而任务周期线程池,是使用如下方式:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
       scheduledExecutorService.schedule(new Runnable() {
           @Override
           public void run() {
               
           }
       }, 3, TimeUnit.SECONDS);

支持延时任务。

 

各个线程池对比

newFixedThreadPool和newSingleThreadExecutor:
  弊端:有可能会堆积的请求处理队列,阻塞队列可能会耗费非常大的内存,甚至OOM。
newCachedThreadPool和newScheduledThreadPool:
  弊端:是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

3、自定义线程池

(1)、线程池部分关键类继承实现以及成员变量定义  

ThreadPoolExecutor相关类

public class ThreadPoolExecutor extends AbstractExecutorService

public abstract class AbstractExecutorService implements ExecutorService

package java.util.concurrent;

import java.util.Collection;
import java.util.List;

 *
 * @since 1.5
 * @author Doug Lea
 */
public interface ExecutorService extends Executor {

    void shutdown();

  
    List<Runnable> shutdownNow();


    boolean isShutdown();

    boolean isTerminated();


    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;


    <T> Future<T> submit(Callable<T> task);

  
    <T> Future<T> submit(Runnable task, T result);

    Future<?> submit(Runnable task);


    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

   
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

   
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

   
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}


public interface Executor {
    void execute(Runnable command);
}

 成员变量定义 

  // Android-added: @ReachabilitySensitive
    @ReachabilitySensitive
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

我们逐行来看看到底什么意思:

@ReachabilitySensitive
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl Integer原子变量,jdk中实现原子操作的类,详情可以看 :https://blog.csdn.net/wuqiqi1992/article/details/107942034

中CAS 部分 jdk中相关原子操作

记录线程池状态 和 线程池中线程数量,转换成二进制,高3位代表 线程池状态,后面29位代表线程池线程数量

private static final int COUNT_BITS = Integer.SIZE - 3;

这个结合<<运算符,高效率得到几个状态码和线程的数量,状态码是高三位 ,数量是后29位

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

这个是线程最大个数,低位29位

 // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

这几个是线程池的几个状态码,高三位

状态含义:

RUNNING:接受新任务并且处理阻塞队列里的任务

SHUTDOWN:拒绝新任务但是处理阻塞队列里的任务

STOP:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务

TIDYING:所有任务都执行完(包含阻塞队列里面任务)当前线程池活动线程为 0,将要调用 terminated方法

TERMINATED:终止状态,terminated方法调用完成以后的状态。

线程池状态和切换图


 

状态转换:

RUNNING -> SHUTDOWN:显式调用 shutdown() 方法,或者隐式调用了 finalize(),它里面调用了 shutdown() 方法。

RUNNING or SHUTDOWN -> STOP:显式调用 shutdownNow() 方法时候。

SHUTDOWN -> TIDYING:当线程池和任务队列都为空的时候。

STOP -> TIDYING:当线程池为空的时候。

TIDYING -> TERMINATED:当 terminated() hook 方法执行完成时候。

(2)、构造方法

 /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数含义:

corePoolSize:线程池核心线程数

maximunPoolSize:线程池最大线程数。

keeyAliveTime:空闲线程存活时间。如果当前线程池中的线程数量比核心线程数量要多,并且是闲置状态的话,这些闲置的线程能存活的最大时间。

TimeUnit:空闲线程存活时间的时间单位。

workQueue:用于保存等待执行的任务的阻塞队列

比如基于数组的有界 ArrayBlockingQueue,基于链表的无界 LinkedBlockingQueue,最多只有一个元素的同步队列 SynchronousQueue,优先级队列 PriorityBlockingQueue 等。

ThreadFactory:创建线程的工厂。

RejectedExecutionHandler:拒绝策略,当队列满了并且线程个数达到 maximunPoolSize 后采取的策略,比如 AbortPolicy (抛出异常),CallerRunsPolicy(使用调用者所在线程来运行任务),DiscardOldestPolicy(调用 poll 丢弃一个任务,执行当前任务),DiscardPolicy(默默丢弃,不抛出异常)。

(3)、线程池工作原理

直接看流程图

 再来直接看源码execute方法,为了方便我直接在代码上进行了注解

 public void execute(Runnable command) {
//command 未null 抛出空异常
        if (command == null)
            throw new NullPointerException();
//由上面模块介绍ctl实际高三位代表状态,低29位代表线程数量,
        int c = ctl.get();
//workerCountOf 这个方法得到当前worker,每个command都会包装成一个Worker对象,也就是如果当前线程
//数少于核心线程数,直接添加一个Worker,也就是创建一个线程,执行这个任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
//判断如果当前线程池状态时 RUNNING状态,并且工作队列未满,则添加任务到工作队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
//二次判断如果当前线程池状态不是RUNNING则从队列删除任务,并执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
//否则,如果工作线程池为空,则添加一个线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
//工作队列满了,再试试添加任务能否成功,失败则执行拒绝策略
//(一般有界阻塞队列满了,且超过了线程池最大容量)
        else if (!addWorker(command, false))
            reject(command);
    }

总结下:

如果 当前活动线程数 < 指定的核心线程数,则创建并启动一个线程来执行新提交的任务(此时新建的线程相当于核心线程);

如果 当前活动线程数 >= 指定的核心线程数,且缓存队列未满,则将任务添加到缓存队列中;

如果 当前活动线程数 >= 指定的核心线程数,且缓存队列已满,则创建并启动一个线程来执行新提交的任务(此时新建的线程相当于非核心线程);

然后我们再来看看新增Worker,也就是新增一个线程方法,为了方便我直接在代码里面就行了注解

  private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
//检查只有在必要的情况下,queue才能为空
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
//循环
            for (;;) {
//判断增加一个线程后得到的数量,具体可以卡看面的workerCountOf方法
                int wc = workerCountOf(c);
//判断是否大于等于 系统设置的线程池最大容量,如果是判断核心线程数 就判断是否超过核心线程数
//如果是不是判断核心线程数,就判断是否是大于最大线程数
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
//CAS操作,即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。
//CAS增加线程个数,同时只有一个线程成功
                if (compareAndIncrementWorkerCount(c))
                    break retry;
//增加失败,再次判断线程池状态,则看线程池状态是否变化了,变化则跳到外层循环重试重新获取线程池状//态,否者内层循环重新cas。
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
//到这里说明cas成功了,这时候就要开新线程了
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
//创建Worker对象
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
//这里会使用可重入锁,独占锁,防止多个线程调用线程execute方法
                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());
//如果复合条件,状态时RUNNING,或者 状态时SHUTDOWN,且firstTask为空这里会添加work到一个//workers集合里面
                    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;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

 private static int workerCountOf(int c)  { return c & CAPACITY; }

   /**
     * Attempts to CAS-increment the workerCount field of ctl.
     */
    private boolean compareAndIncrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect + 1);
    }

 addWorker的作用分两步:代码主要分两部分,

第一部分的双重循环目的是通过 CAS 操作增加线程池线程数,

 关于CAS可以看:https://blog.csdn.net/wuqiqi1992/article/details/107942034

第二部分主要是并发安全的把任务添加到 workers 里面,并且启动任务执行。

我们再来看看Worker 这个类的继承和实现:

 private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {

这个 AbstractQueuedSynchronizer 是个 同步队列器,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。具体内容可以看

https://blog.csdn.net/wuqiqi1992/article/details/107942034

这个文章的AQS

看看怎么运转的,Worker的构造方法

 Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this); //得到线程工厂类,创建线程
        }

 先是设置了 Worker 的State为 -1,是为了避免当前 worker 在调用 runWorker 方法前被中断(当其它线程调用了线程池的 shutdownNow 时候,如果 worker 状态 >= 0 则会中断该线程)。这里设置了线程的状态为 -1,所以该线程就不会被中断了。

我们知道当启动一个线程后,native 层C代码会创建一个线程,然后回调到 Runnable的 run方法,所以当Worker对象中的thread被start后,最后也会回调到 run方法。这方面的知识可以查看:https://blog.csdn.net/wuqiqi1992/article/details/107878581

我们可以看看Worker的run方法

  /** Delegates main run loop to outer runWorker. */
        public void run() {
            runWorker(this);
        }

会调用 runWorker 方法

再看看下面的runWorker方法,同样我也对关键代码做了注释

 final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
//status设置为0,允许中断
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        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 {
//任务执行,这里会调用run方法,这是真正执行任务的地方
                        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;
//统计Worker完成的Task
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
//Worker 完成后执行 清理工作
            processWorkerExit(w, completedAbruptly);
        }
    }

 w.unlock(); // allow interrupts 允许中断 

我们看看 unlock做了啥

 public void unlock()      { release(1); }

// Android-removed: @ReservedStackAccess from OpenJDK 9, not available on Android.
    // @ReservedStackAccess
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

这里的作用是把Worker的状态变成 0,这时候调用 shutdownNow 会中断 worker 线程了。

(4)、线程池如何复用的

看了上面的线程池工作流程是不是还是很困惑,每次都是新建一个线程去执行任务,没有复用空闲线程啊。其实关键代码是在Worker类中runWorker方法的循环判断,我们再来回顾下runWorker方法

   final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        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;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

关键 是看看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);
 // timed变量用于判断是否需要进行超时控制。
        // allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
        // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
        // 对于超过核心线程数量的这些线程或者允许核心线程进行超时控制的时候,需要进行超时控制
        // Are workers subject to culling?
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
 // 如果需要进行超时控制,且上次从缓存队列中获取任务时发生了超时(timedOut开始为false,后面的循//环末尾超时时会置为true)
  // 或者当前线程数量已经超过了最大线程数量,那么尝试将workerCount减1,即当前活动线程数减1,
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
 如果减1成功,则返回null,这就意味着runWorker()方法中的while循环会被退出,其对应的线程就要//销毁了,也就是线程池中少了一个线程了
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
// 注意workQueue中的poll()方法与take()方法的区别
 //poll方式取任务的特点是从缓存队列中取任务,最长等待keepAliveTime的时长,取不到返回null
//take方式取任务的特点是从缓存队列中取任务,若队列为空,则进入阻塞状态,直到能取出对象为止
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
//这里说明已经超时了
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

这里总结下:

如果当前线程池线程数量大于核心线程数量或者设置了对核心线程进行超时控制的话(此时相当于对所有线程进行超时控制),就会去任务队列获取超时时间内的任务(队列的poll方法),获取到的话就会继续执行任务,也就是执行runWorker方法中的while循环里的任务的run方法,执行完成后,又继续进入getTask从任务队列中获取下一个任务。如果在超时时间内没有获取到任务,就会走到getTask的倒数第三行,设置timeOut标记为true,此时继续进入getTask的for循环中,由于超时了,那么就会进入尝试去去对线程数量-1操作,-1成功了,就直接返回一个null的任务,这样就回到了当前线程执行的runWorker方法中,该方法的while循环判断getTask为空,直接退出循环,这样当前线程就执行完成了,意味着要被销毁了,这样自然就会被回收器择时回收了。也就是线程池中少了一个线程了。因此只要线程池中的线程数大于核心线程数(或者核心线程也允许超时)就会这样一个一个地销毁这些多余的线程。

如果当前活动线程数小于等于核心线程数(或者不允许核心线程超时),同样也是去缓存队列中取任务,但当缓存队列中没任务了,就会进入阻塞状态(队列的take方法),直到能取出任务为止(也就是队列中被新添加了任务时),因此这个线程是处于阻塞状态的,并不会因为缓存队列中没有任务了而被销毁。这样就保证了线程池有N个线程是活的,可以随时处理任务,从而达到重复利用的目的。

看这个 getTask类之前我们再来回顾下之前添加任务的时候关键代码

  public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        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);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

我们发现 在execute在核心线程满了,但是队列不满的时候会把任务加入到队列中,并没有新建线程。

所以线程之所以能达到复用,就是在当前线程执行的runWorker方法中有个while循环,while循环的第一个判断条件是执行当前线程关联的Worker对象中的任务,执行一轮后进入while循环的第二个判断条件getTask(),从任务队列中取任务,取这个任务的过程要么是一直阻塞的,要么是阻塞一定时间直到超时才结束的,超时到了的时候这个线程也就走到了生命的尽头。

然而在我们开始分析execute的时候,这个方法中的三个部分都会调用addWorker去执行任务,在addWorker方法中都会去新建一个线程来执行任务,这样的话是不是每次execute都是去创建线程了?事实上,复用机制跟线程池的阻塞队列有很大关系,我们可以看到,在execute在核心线程满了,但是队列不满的时候会把任务加入到队列中,一旦加入成功,之前被阻塞的线程就会被唤醒去执行新的任务,这样就不会重新创建线程了。

(5)、线程回收

runWorker方法最后的 processWorkerExit方法,看详情

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
//统计整个线程池完成的任务个数,并从工作集里面删除当前woker
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
//尝试将线程池状态置为terminate状态
//判断如果当前线程池状态是 shutdonw 状态并且工作队列为空或者当前是 stop 状态当前线程池里面没有//程
        tryTerminate();

        int c = ctl.get();

        if (runStateLessThan(c, STOP)) {
//则判断当前线程里面线程个数是否小于核心线程个数,如果是则新增一个线程。需要保证核心线程数量
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

 final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
//如果当前是shutdonw状态并且工作队列为空
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
//活动线程则设置线程池状态为 TERMINATED,如果设置为了 TERMINATED 状态还需要调用条件变量 //termination 的 signalAll() 方法激活所有因为调用线程池的 awaitTermination 方法而被阻塞的线
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

结合(5)总结:

  • 当有新任务来的时候,先看看当前的线程数有没有超过核心线程数,如果没超过就直接新建一个线程来执行新的任务,如果超过了就看看缓存队列有没有满,没满就将新任务放进缓存队列中,满了就新建一个线程来执行新的任务,如果线程池中的线程数已经达到了指定的最大线程数了,那就根据相应的策略拒绝任务。

  • 当缓存队列中的任务都执行完了的时候,线程池中的线程数如果大于核心线程数,就销毁多出来的线程,直到线程池中的线程数等于核心线程数。此时这些线程就不会被销毁了,它们一直处于阻塞状态,等待新的任务到来。

(6)、阻塞队列

 ArrayBlockingQueue:有界队列(数组结构)

LinkedBlockingQueue :默认无界(链表结构)内部由单链表实现,只能从head取元素,从tail添加元素。添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。LinkedBlockingQueue采用可重入锁(ReentrantLock)来保证在并发情况下的线程安全。

PriorityBlockingQueue:无界 (基于数组的平衡二叉堆)

SynchronousQueue:(没实际存储空间),只有正好有空闲线程,才会入队成功,否则总是创建新线程,直到达到maximumPoolSize。

使用无界队列,需要注意,线程数最多达到corePoolSize,新任务来只能排队,maximumPoolSize没意义。

 

(7)、拒绝策略

ThreadPoolExecutor.AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最长等待时间任务,也就是最前面的任务,然后重新提交被拒绝的任务

ThreadPoolExecutor.CallerRunsPolicy:由提交任务的线程去处理该任务

(8)、关闭线程池

可以通过调用线程池的shutdownshutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的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来处理。它可以让优先级高的任务先执行。

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

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

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

本文参考文章:

https://www.cnblogs.com/jimmyfan/articles/11424332.html

https://www.cnblogs.com/huangjuncong/p/10031525.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值