ThreadPoolExecutor线程池原理

本文参考
Java线程池---addWorker方法解析
Java线程池ThreadPoolExecutor实现原理
线程池如何复用

Executor(Interface):

执行提交的线程任务的对象。这个接口提供了一种将任务提交与每个任务将如何运行实现了分离,包括线程使用、调度等细节。该接口只定义了一个execute()方法。

// 用来执行一个指令任务,这个任务可能在一个新的线程中执行,可能在线程池已有的线程中执行,也可能在当前线程执行,由Executor接口的实现方决定;
void execute(Runnable command);

ExecutorService(Interface):

提供用于管理终止的方法如 shutDown()和shutDownNow()用于关闭线程池的方法以及判断线程池是否关闭的方法如,isShutdown(),isTerminated()的方法,还提供了involveAll()和submit()

// 关闭线程池,不接受新的任务,继续执行已经提交的任务,但是不等待已经提交的任务完成,使用awaitTermination接口
void shutdown();

// 判断当前线程池是否被关闭
boolean isShutdown();

// 判断是否所有提交的任务在showDown()后已经执行完毕 
boolean isTerminated(); 

// 执行一个任务,并且返回这个任务的Future对象,用于表示这个任务的挂起结果
 Future submit(Callable task);

// 执行许多任务,返回任务的results
 T invokeAny(Collection> tasks)
        throws InterruptedException, ExecutionException;

// 执行许多任务,返回许多Future(包含status and results)
 List> invokeAll(Collection> tasks)
        throws InterruptedException;
Callable:

和Runnable类似,是java开启子线程的一种方式,用Callable开启的线程可以取消执行;

  • call():类似run方法
Future:

一个代表线程执行结果的类,可以用来判断线程是否运行结束,主动终止线程

  • get():等待线程执行完毕,返回线程的结果
  • cancel():停止线程
  • isDown():判断线程是否结束
  • isCancel():判断线程是否取消

ThreadPoolExecutor:

AbstractExecutorService的实现类,java线程池的核心实现类,下面我们重点看这个类;


线程池执行任务的流程
重要变量:
//  构造方法中的参数
private volatile int corePoolSize;// 核心线程数
private volatile int maximumPoolSize;// 最大线程数
private volatile long keepAliveTime; // 空闲线程的可存活时间 
private final BlockingQueue workQueue; // 工作队列,存放任务的数据结构
private volatile ThreadFactory threadFactory; // 线程工厂,创建新线程的工厂
private volatile RejectedExecutionHandler handler;  // 抛弃线程策略

// 线程池状态变量(这是一个atomic integer)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

深入解析Java AtomicInteger 原子类型
在线程池的内部,封装了一个Worker对象,Worker对象是线程池维护的用来处理用户提交的任务的线程;

// worker的本质是一个任务
private final class Worker extends AbstractQueuedSynchronizer implements Runnable

execute():

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
                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) // 如果当前的线程数为0(线程池没有被关闭),开启新线程执行
                addWorker(null, false);
        }
        else if (!addWorker(command, false))  // 如果无法创建新线程去执行这个任务 ,也放弃这个任务  
            reject(command);
    }
execute()的流程分为三部分(上面有详细的英文注释):
  • 如果比正在运行的核心线程数量小,则尝试开启一个新线程,将这个任务作为他的第一个任务,并且调用addWorker()去检查 runState 和workerCount,避免不必要的错误警报
  • 如果一个任务可以成功的加入阻塞队列中排队,我们需要再次双重检验是否我们需要创建一个新的线程,如果线程池被关闭(由于这个任务的某个逻辑导致)或者现存的线程死掉了,我们就调用handler去拒绝这个任务,如果当前线程池没有线程,则创建一个新的线程
  • 如果这个线程无法进入阻塞队列又无法创建新线程去执行,我们就用handler丢弃他;

addWorker():

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        //  使用CAS机制轮询线程池的状态,如果线程池处于SHUTDOWN及大于它的状态则拒绝执行任务
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary. 
            // 如果是STOP,TIDYING,TERMINATED状态的话,则会返回false,如果现在状态是SHUTDOWN,但是firstTask不为空或者workQueue为空的话,那么直接返回false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                 //  获得当前workerCount,如果当前workerCount大于容量/核心线程数/最大线程数,return false
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 判断当前的线程池状态是否可以创建新的worker,可以的话 break retry,创建新worker
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                 // CAS的自旋,再次比较ctl,如果不一致retry
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        //   创建新的worker
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            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());
                    // 在锁内部在进行一次判断是否满足创建worker的条件
                    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;
    }

addWorker:

根据当前线程池的状态和给定的线程数大小(core/maximum)判断是否可以添加一个新的worker,如果可以,创建一个新的worker,当前任务作为他的first task,当线程池被关闭或者工厂创建线程失败,这个方法返回false;
CAS机制
什么是retry?

自带的ThreadPoolExecutor对象
  • newFixedThreadPool:一个固定线程数量的线程池:
  • newCachedThreadPool:不固定线程数量,且支持最大为Integer.MAX_VALUE的线程数量:
  • newSingleThreadExecutor:可以理解为线程数量为1的FixedThreadPool:
  • newScheduledThreadPool:支持定时以指定周期循环执行任务:
拒绝策略(当阻塞队列和max都满了做出的处理)
  • AbortPolicy:直接抛出异常。
  • CallerRunsPolicy:由调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最老的一个任务,并执行当前任务。
  • DiscardPolicy:丢弃掉当前提交的新任务。

封装的自定义的ThreadPoolExecutor

public class ThreadPoolManager {
    /**
     * 单例设计模式(饿汉式)
     * 单例首先私有化构造方法,然后饿汉式一开始就开始创建,并提供get方法
     */
    private static ThreadPoolManager mInstance = new ThreadPoolManager();

    public static ThreadPoolManager getInstance() {
        return mInstance;
    }

    private int corePoolSize;//核心线程池的数量,同时能够执行的线程数量
    private int maximumPoolSize;//最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
    private long keepAliveTime = 1;//存活时间
    private TimeUnit unit = TimeUnit.HOURS;
    private ThreadPoolExecutor executor;

    private ThreadPoolManager() {
        /**
         * 给corePoolSize赋值:当前设备可用处理器核心数*2 + 1,能够让cpu的效率得到最大程度执行
         */
        corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
        maximumPoolSize = corePoolSize; //虽然maximumPoolSize用不到,但是需要赋值,否则报错
        executor = new ThreadPoolExecutor(
                corePoolSize, //当某个核心任务执行完毕,会依次从缓冲队列中取出等待任务
                maximumPoolSize, //5,先corePoolSize,然后new LinkedBlockingQueue(),然后maximumPoolSize,但是它的数量是包含了corePoolSize的
                keepAliveTime, //表示的是maximumPoolSize当中等待任务的存活时间
                unit,
                new LinkedBlockingQueue(), //缓冲队列,用于存放等待任务,Linked的先进先出
                Executors.defaultThreadFactory(), //创建线程的工厂
                new ThreadPoolExecutor.AbortPolicy() //用来对超出maximumPoolSize的任务的处理策略
        );
    }

    /**
     * 执行任务
     */
    public void execute(Runnable runnable) {
        if (runnable == null) return;
        executor.execute(runnable);
    }

    /**
     * 从线程池中移除任务
     */
    public void remove(Runnable runnable) {
        if (runnable == null) return;
        executor.remove(runnable);
    }
}

配置线程池

任务的性质:

  • CPU密集型(大量计算) :CPU数+1
  • IO密集型(磁盘IO,网络IO):CPU数*2+1

线程池如何复用线程?

一个大run()把其它小run()#1,run()#2,...给串联起来了
前提条件:假如coreSize=3,maxSize=10,当前存在线程数是5。
(注意,存在的这5个线程,并不是你执行ExecuteService.execute/submit时的参数,而是为了执行execute/submit的参数所启动的“内部线程”。这个“内部线程”其实是通过ThreadPoolExecutor的ThreadFactory参数生成的线程,而“execute/submit的参数”是执行在这些“内部线程”里面的。)
存在这5个“内部线程”,都访问同一个队列,从队列中去取任务执行(任务就是通过execute/submit提交的Runnable参数),当任务充足时,5个“内部线程”都持续执行。重点是没有任务时怎么办?
没有任务时,这5个“内部线程”都会做下面判断:
如果poolSize > coreSize,那就从队列里取任务,当过了keepaliveTime这么长时间还没有得到任务的话,当前这个“内部线程”就会结束(使用的是BlockingQueue.poll方法)。
如果poolSize <= coreSize,那就以“阻塞”的方式,去从队列里取任务,当得到任务后,就继续执行。这样的话,这个线程就不会结束掉。
如果没有任务可以继续执行了,最后只剩下coreSize那么多的“内部线程”留在线程池里,等待重用。

你可能感兴趣的:(ThreadPoolExecutor线程池原理)