线程池底层原理

一、概述

java线程池大体由一个线程集合workerSet和一个阻塞队列workQueue组成。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行

public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数
                              long keepAliveTime,//空闲线程存活时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler)//拒绝策略

1.corePoolSize:核心线程数,已创建的线程<corePoolSize,则新建一个线程执行,>corePoolSize,则放入阻塞队列workQueue中
2.maximumPoolSize:最大线程数,当阻塞队列满了,则会创建新线程,直达maximumPoolSize的值
3.keepAliveTime:当阻塞队列的任务被执行完了,且有空闲线程,使线程个数<=corePoolSize,的时间值
4.unit:keepAliveTime 的单位
5.workQueue:阻塞队列,(1)ArrayBlockingQueue基于数组的有界队列。
(2)LinkedBlockingQueue基于链表的有界队列,但是界限为int的最大值,会一直存放任务,maximumPoolSize无效 。
(3)SynchronousQueue 不缓存任务,放一个执行一个
(4)PriorityBlokingQueue 具有优先级的队列通过comparater实现
6.threadFactory:线程工厂 用老创建线程,指定名字,查日志方便。
7.handler:拒绝策略,(1)Abortpolicy:直接拒绝,抛异常(2)DiscardPolicy:忽略任务,不报错(3)DiscardOlddestPolicy:从队列移除最老的任务,放入新任务 (4)CallerRunsPolicy:如果提交任务失败,会由提交任务的这个线程自己来调用execute执行任务

二、为何生产中一般不用jdk自带的线程池

1.Executors.newFixedThreadPool(int size) 创建固定线程池,用的LinkedBlockingQueue,无限接受任务,导致OOM
2.Executors.newSingleThreadExecutor() 创建一个线程的线程池,用的LinkedBlockingQueue,无限接受任务,导致OOM
3.Executors.newCachedThreadPool() 创建一个带缓存的池,最大无限大,核心线程为0,最大无限大,用的SynchronousQueue 来一个执行一个,任务越多,线程越多
4.Executors.newScheduledThreadPool(int size) 传入的参数为核心线程,最大为无限大,用的DelayedWordQueue 延迟队列

三、源码解析

1.重要属性

/**
     * 这个ctl就是用来保存 线程池的状态(runState) 和 线程数(workerCount) 的
     * 这里使用AtomicInteger 来保证原子操作
     * 这里的ctl的初始值其实就是-1左移29位,即3个1和29个0, 
     * 111 00000000000000000000000000000
     */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;

// COUNT_BITS值为29,代表着低29位用于存储线程数,高3位用于存储线程池的状态
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池最大的容量,值为3个 0和29个1。也就是536870911
// 000 11111111111111111111111111111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// 下面这5个值代表线程池的状态,存储在高3位中
// 3个1,29个0   111 00000000000000000000000000000
private static final int RUNNING    = -1 << COUNT_BITS;
// 全是0  000 00000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 001 00000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;
// 010 00000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;
// 011 00000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS;


2.ThreadPoolExecutor.execute()方法

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();//
        if (workerCountOf(c) < corePoolSize) {//当前正在运行的worker数量<corePoolSize
            if (addWorker(command, true))//创建一个worker,直接执行任务
                return;
            c = ctl.get();
        }
        // isRunning(c)判断线程池是否在运行中,如果线程池被关闭了就不会再接受任务
        // workQueue.offer(command) 将任务加入到队列中
        if (isRunning(c) && workQueue.offer(command)) {
         	//如果添加到队列成功了,会再检查一次线程池的状态
            int recheck = ctl.get();
            //如果线程池关闭了,就将刚才添加的任务从队列中移除
            if (! isRunning(recheck) && remove(command))
            	//执行拒绝策略
                reject(command);
            // 如果线程是处于RUNNING状态,并且当前线程池中的线程数为0,开启一个新的线程
            // 因为有可能任务添加到队列中了,但是却没有线程可执行
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果失败,说明当前线程数已达到maximumPoolSize,需要执行拒绝策略
        else if (!addWorker(command, false))//addWorker(command, false)是新开线程执行超过核心线程的任务
            reject(command);
    }

3.BlockingQueue中的offer(E e)方法

BlockingQueue接口提供了3个添加元素方法:

  • add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会抛出IllegalStateException异常;
  • offer:添加元素到队列里,添加成功返回true,添加失败返回false;
  • put:添加元素到队列里,如果容量满了会阻塞直到容量不满。
    已ArrayBlockingQueue为例
public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);//入队操作
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
private void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

4.addWorker()方法

    // 这个方法会创建线程并且执行任务
    // 以下几种情况这个方法会返回false:
    // 1.传入的core这个参数为true,代表线程数的上限为corePoolSize,
    //   如果当前线程数已达到corePoolSize,返回false
    // 2.传入的core这个参数为false,代表线程数的上限为maximumPoolSize,
    //   如果当前线程数已达到maximumPoolSize,返回false
    // 3.线程池stopped或者shutdown
    // 4.使用ThreadFactory创建线程失败,或者ThreadFactory返回的线程为null
    // 5.或者线程启动出现异常
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            // 这个rs就是线程池的状态
            int rs = runStateOf(c);
 
            // 这里的if说的是以下3种情况,直接返回false,不会创建新的线程:
            // 1.rs大于SHUTDOWN,说明线程状态是STOP,TIDYING, 或者TERMINATED,
            //   这几种状态下,不接受新的任务,并且会中断正在执行的任务。所以直接返回false
            // 2.线程池状态处于SHUTDOWN,并且firstTask!=null。
            //   因为SHUTDOWN状态下,是不接收新的任务的。所以返回false。
            // 3.线程池处于SHUTDOWN并且firstTask为null,但是workQueue是空的。
            //   因为SHUTDOWN虽然不接收新的任务,但是已经进入workQueue的任务还是要执行的,
            //   恰巧workQueue中没有任务。所以也是返回false,不需要创建线程
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
 
            for (;;) { // 注意:这里是个for循环
                // 获取线程池中线程的数量
                int wc = workerCountOf(c);
                // 这里传入的core为true代表线程数上限为corePoolSize,
                // false代表线程数上限为maximumPoolSize,如果线程数超出上限,直接返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 使用CAS对线程计数+1,如果成功,说明已经满足创建线程的条件了
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 如果上面的CAS失败,说明有并发,再次获取ctl的值
                c = ctl.get();  // Re-read ctl
                // 如果线程池的状态发生了变化,例如线程池已经关闭了,
                // 导致的CAS失败,那么回到外层的for循环(retry)
                // 否则,说明是正常的CAS失败,这个时候进入里面的循环
                if (runStateOf(c) != rs)
                    continue retry;
                
            }
        }
        // 已经做好创建线程的准备了 
        // worker是否已经启动的标志位
        boolean workerStarted = false;
        // 我们前面说了workers这个HashSet用于存储线程池中的所有线程,
        // 所以这个变量是代表当前worker是否已经存放到workers这个HashSet中
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 传入firstTask这个任务构造一个Worker
            w = new Worker(firstTask);
            // Worker的构造方法中会使用ThreadFactory创建新的线程,
            // 所以这里可以直接获取到对应的线程
            final Thread t = w.thread;
            // 如果创建线程成功
            if (t != null) {               
                final ReentrantLock mainLock = this.mainLock;
                // 获取线程池的全局锁,下面涉及线程池的操作都需要在持有全局锁的前提下进行
                mainLock.lock();
                try {
                    // 获取线程池的状态
                    int rs = runStateOf(ctl.get());
                    // 如果rs<SHUTDOWN,说明线程池处于RUNNING状态
                    // 或者 线程池处于SHUTDOWN状态并且没有新的任务
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 如果线程已经启动,抛出异常
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 将包装线程的worker加入到workers这个HashSet中
                        workers.add(w);
                        int s = workers.size();
                        // 我们前面说了,largestPoolSize记录的是线程池中线程数曾经到达的最大值
                        // 线程池中worker的数量是会变化的,所以记录下worker数的最大值
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        // 修改标志,代表当前worker已经加入到workers这个HashSet中
                        workerAdded = true;
                    }
                } finally {
                    // 释放全局锁
                    mainLock.unlock();
                }
                // 如果worker添加成功,启动线程执行任务
                if (workerAdded) {
                    // 启动线程
                    t.start();
                    // 代表worker已经启动
                    workerStarted = true;
                }
            }
        } finally {
            // 如果线程没有启动,这里还需要进行一些清理工作
            if (! workerStarted)
                addWorkerFailed(w);
        }
        // 返回线程是否成功启动
        return workerStarted;
    }
 
    // 这个方法做下面几件事:
    // 1.将worker从workers中移除
    // 2.worker的数量-1
    // 3.检查termination
    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        // 要操作workers这个HashSet,先获取java线程池全局锁
        mainLock.lock();
        try {
            if (w != null)
                // 从worker中移除
                workers.remove(w);
            // WorkerCount -1
            decrementWorkerCount();
            // 处理TERMINATED状态
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }

5.启动线程执行任务的操作就是在addWorker中,t.start,调用Worker.run()

    public void run() {
        // 这里调用的runWorker方法
        runWorker(this);
    }
    // 这里就是执行任务的代码了,有一个while循环不断从队列中取出任务并执行,
    // 退出循环的条件是获取不到要执行的任务
    final void runWorker(Worker w) {
        // 当前线程
        Thread wt = Thread.currentThread();
        // 前面说了new Worker的时候可以指定firstTask,代表Worker的第一个任务
        Runnable task = w.firstTask;
        // 这一步就已经将firstTask置为null了
        w.firstTask = null;
        // 释放Worker的独占锁,这里它释放锁的操作一定会成功,也就是将AQS中state设置为0
        w.unlock(); // allow interrupts
        // completedAbruptly这个标志位代表当前Worker是否因为执行任务出现异常而停止的
        boolean completedAbruptly = true;
 
        try {
            // while循环;如果firstTask不为null那就直接执行firstTask,
            // 否则就要调用getTask()从队列中获取队列。
            // 也就是说Worker的第一个任务是不需要从队列中获取的
            while (task != null || (task = getTask()) != null) {
                // 给这个worker上独占锁
                // Worker加锁的意义在于,在线程池的其他方法中可能会中断Worker,
                // 为了保证Worker安全的完成任务,必须要在获取到锁的情况下才能中断Worker,
                // 如tryTerminate(),shutdown()等都会关闭worker。
                w.lock();
                // 如果ctl的值大于等于STOP,说明线程池的状态是STOP,TIDYING或TERMINATED。
                // 这个时候需要确保该线程已中断,否则就应该确保线程没有中断
                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,准备接受下一个任务
                    task = null;
                    // 这个worker已完成任务数+1
                    w.completedTasks++;
                    // 释放独占锁
                    w.unlock();
                }
            }
            // 到这一步说明没抛出异常
            completedAbruptly = false;
        } finally {
 
            // 执行到这里说明:要么队列中已经没有任务了,要么执行任务出现了异常。
            // 这个时候需要调用processWorkerExit关闭线程
            processWorkerExit(w, completedAbruptly);
        }
    }

6.从队列中获取任务getTask()

    // 这个方法就是从队列中获取任务,返回null代表线程需要被关闭。一共有以下三种可能:
    // 1.阻塞获取任务直到获取成功
    // 2.获取任务超时了,也就说线程空闲了keepAliveTime这么久了,还是没有获取到任务,
    //   这个时候线程需要被关闭(这里有个前提就是线程数要大于corePoolSize)
    // 3.如果出现下面几种情况返回null,返回null说明线程需要被关闭
    //   池中worker的数量大于maximumPoolSize(由于调用setMaximumPoolSize进行了设置)
    //   线程池处于STOP状态,这个时候不能执行任务队列中的任务
    //   线程池处于SHUTDOWN状态,但是任务队列是空的
    private Runnable getTask() {
        boolean timedOut = false; // 最后一次的poll操作是否超时
        for (;;) {// for循环
            int c = ctl.get();
            // 获取线程池的状态
            int rs = runStateOf(c);
            // 如果线程池的状态大于等于STOP,或者线程池状态等于SHUTDOWN并且任务队列为空
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                // 使用CAS对workerCount-1
                decrementWorkerCount();
                // 返回null
                return null;
            }
            // 获取线程池中的线程数
            int wc = workerCountOf(c); 
            // 如果allowCoreThreadTimeOut设置为true,
            // 或者线程池中线程数>corePoolSize,说明有可能发生超时
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            // 如果当前线程数大于maximumPoolSize,或者超时
            // 注意:如果开发者调用了setMaximumPoolSize() 将maximumPoolSize变小了,
            // 就有可能出现当前线程数大于maximumPoolSize。
            // 这个时候多余的线程肯定是获取不到任务的,需要被关闭
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                // workerCount-1
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }            
            // 到这里,说明线程数小于maximumPoolSize等于且没有超时
            try {
                // 从任务队列中取出任务
                // 如果timed为true,调用带超时的poll方法,否则执行take方法阻塞获取任务。
                // timed这个变量的值取决于allowCoreThreadTimeOut || wc > corePoolSize
                // 其实这里说的是,如果线程池中线程数量在corePoolSize以内,
                // 且不支持回收核心线程数内的线程,这个时候线程池中的线程是不会被回收的。
                // 所以调用take方法阻塞获取任务,直到获取成功。
                // 否则的话,线程隔了keepAliveTime这么久还是获取不到任务,是需要被回收的
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                // 如果成功获取到任务,返回这个runnable任务,
                // 否则就是超时了,再进入下一轮循环的时候返回null
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

7.阻塞队列中的workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 或者 workQueue.take();方法,移除队列头部元素,通过ReentrantLock 加锁await(),signal()来阻塞唤醒线程

BlockingQueue接口提供了3个删除方法:

  • poll:删除队列头部元素,如果队列为空,返回null。否则返回元素;
  • remove:基于对象找到对应的元素,并删除。删除成功返回true,否则返回false;
  • take:删除队列头部元素,如果队列为空,一直阻塞到队列有元素并删除。
    以ArrayBlockingQueue为例
/** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)//队列中没有元素时调用condition.await阻塞
                notEmpty.await();
            return dequeue();//出队操作
        } finally {
            lock.unlock();
        }
    }

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);//队列中没有元素时,或者超时,调用condition.await阻塞
            }
            return dequeue();//出队操作
        } finally {
            lock.unlock();
        }
    }

	private E dequeue() {
        final Object[] items = this.items;
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

四、总结

线程池内部工作原理:

  1. 首先,调用execute()执行方法时,先判断线程池中工作线程的数量小于corePoolSize,则调用addWorker()方法,在addWorker()方法里创建新线程(判断当前线程池的状态,shundown,stop等不执行,返回false)
  2. 创建线程成功,ReentrantLock加锁,放入HashSet 中 解锁
    t.start()执行线程。如果大于corePoolSize,则调用workQueue.offer(command)放入阻塞队列中,阻塞队列采用ReentrantLock加锁保证线程安全
  3. 如果阻塞队列满了,调用addWorker(command, false)方法,新建线程加入HashSet
    中,如果>maximumPoolSize !addWorker(command, false)
    true调用reject(command) 执行拒绝策略方法。
  4. addWorker()中线程创建好了,t.start()执行任务 就是调用runWorker(Worker w)方法,Runnable
    task = w.firstTask;第一个任务直接执行,后续的调用getTask()从队列中for(;;)调用
    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)或者
    workQueue.take()从阻塞队列头部获取任务,方法内部ReentrantLock加锁保证线程安全

五、问题

  1. 线程执行任务过程中出现异常是怎么处理的?

    如果一个线程执行任务出现异常,那么执行任务的线程会被关闭,而不会继续执行其他任务。最后会启动一个新的线程来取代它

  2. 线程池是怎么实现线程复用的?

    runWorker()方法中,一个线程执行完一个任务后会不断从任务队列中取出任务来执行,如果队列中已经没有任务了,allowCoreThreadTimeOut设置为false并且线程数<=corePoolSize,调用BlokingQueue.take()方法阻塞,直到获取到任务
    如果队列中没有任务了,allowCoreThreadTimeOut设置为true或者线程数>corePoolSize,调用BlockingQueue带超时的poll方法尝试获取任务,获取不到的话,这个线程就会被回收掉

  3. shutdown() 和 shutdownNow()有什么区别?

    线程在拿到任务的时候开始执行的时候,是会获取Worker的独占锁的。shutdown()方法中断worker会先调用Worker.tryLock()获取独占锁,如果线程正在执行任务,那就获取不到独占锁,也就无法中断线程。而shutdownNow()方法是直接尝试中断所有线程,它们底层都是调用Thread.interrupt()方法给线程设置interrupt标记,所以只有响应中断的任务在interrupt()以后才会终止

1.当创建线程池后,初始时,线程池处于RUNNING状态,此时线程池中的任务为0;

2.如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

3.如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

4.当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。接着会执行terminated()函数。

5.线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED,线程池被设置为TERMINATED状态。

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值