线程池原理源码详解

 

 

1、线程池是什么

线程池就是存放线程的一个池子,应用池化技术把创建线程的工作统一交给线程池来管理,就可以避免创建大量的线程带来的开销,以提高响应速度。
那么,JDK给我们提供的默认线程池有哪些呢?

 

2、JDK默认提供了哪些线程池

2-1、newFixedThreadPool

大小固定,无可用线程时,任务需等待,直到有可用线程

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

适用场景:负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量

2-2、newCachedThreadPool

大小不受限,当线程释放时,可重用该线程

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

适用场景:并发执行大量短期的小任务,或者是负载较轻的服务器.

2-3、newSingleThreadExecutor

创建一个单线程,任务会按顺序依次执行

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

适用场景:串行执行任务,每个任务必须按顺序执行,不需要并发执行

2-4、newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行

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

适用场景:需要多个后台线程执行周期任务,同时需要限制线程数量

3、线程池的实现原理

3-1、线程池的运行流程

通过JDK默认提供的线程池我们可以知道,他们都是通过ThreadPoolExecutor来创建的。ThreadPoolExecutor的全参数的构造方法如下:

    public ThreadPoolExecutor(int corePoolSize, 	 //核心线程数
                              int maximumPoolSize,  //最大线程数
                              long keepAliveTime,  //空闲线程等待工作的超时时间
                              TimeUnit unit, 	 //超时时间的时间单位
                              BlockingQueue<Runnable> workQueue,  //保存待执行任务的队列
                              ThreadFactory threadFactory,  //线程工厂
                              RejectedExecutionHandler handler //拒绝策略
                              ) {  
       	//do                       
    }

其中 BlockingQueue 常用的实现有四种

  • ArrayBlockingQueue 先进先出的基于数组的有界阻塞队列
  • LinkedBlockingDeque 先进先出的基于链表的阻塞队列,其内部维持一个基于链表的数据队列
  • SynchronousQueue 一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作
  • PriorityBlockingQueue 支持优先级的无界阻塞队列:默认情况下元素采取自然顺序升序排列,可以以自定义compareTo()方法来指定元素的排序规则

其中 handler 默认有四种拒绝策略。也可以自定义拒绝策略,实现 RejectedExecutionHandler 接口即可

  • CallerRunsPolicy:直接用调用者所在线程来运行任务
  • AbortPolicy:直接抛出 RejectedExecutionException 异常
  • DiscardPolicy:直接丢弃任务
  • DiscardOldestPolicy:丢弃队列中最久的任务,然后再调用 execute()

ThreadPoolExecutor的核心是execute()方法:

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

        int c = ctl.get();
        //当前池中线程数比核心线程数少,创建新线程执行任务
        if (workerCountOf(c) < corePoolSize) {
        	//未超过核心线程数,则新增 Worker 对象,true表示核心线程
            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);
    }

执行流程如下图:

线程池执行流程

3-2、核心线程如何保证不被销毁的

上述 execute()方法中,addWorker(command, true)方法部分源码如下:

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            ...
            // 省略代码
            ...
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //CAS累加
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        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 {
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 线程添加到workers中
                        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;
    }

 

其中workers对象是个Worker的HashSet集合。

private final HashSet<Worker> workers = new HashSet<Worker>();

Worker 对象部分源码如下:

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {

        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

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

通过源码可知,初始化Worker对象时,通过Worker对象创建了一个线程放在成员变量thread的位置,所以启动线程t.start()等价于调用Worker对象的run()方法。继续看runWorker(this)方法:

    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();
        		// 省略代码
        		...
                task.run();
				...
        		// 省略代码
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

 

最终是调用的task.run()方法,此处为什么不用task.start()方法呢?或者说可以用这个方法吗?
仔细思考一下,就可以得到答案,不可以。因为如果用的是task.start()方法,那么会再启动一个线程去执行这个任务,就达不到线程池的效果了。打个比方就是:一个核心线程数为2个线程池,如果有10个任务,会重新创建10个线程来执行这些任务。
上面的runWorker(this)方法,我们可以看到有个while循环一直用getTask()方法取任务,这里是线程池不被销毁的核心,继续看一下getTask()方法:

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            // 仅在必要时检查队列是否为空
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            int wc = workerCountOf(c);
            // workers会被淘汰吗
            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;
            }
        }
    }

可以看到在for循环中,通过不断的检查线程池状态和队列容量,来获取可执行任务。
在 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();代码中,分为两种情况

  • timed 为 true,允许淘汰Worker,即实际运行的线程,则通过
    workQueue.poll的方式定时等待拉取任务,如果在指定keepAliveTime时间内获取任务则返回,如果没有任务则继续for循环并直到timed等于false
  • timed 为 false,则会调用 workQueue.take() 方法,队列中 take() 方法的含义是当队列有任务时,立即返回队首任务,没有任务时则一直阻塞当前线程,直到有新任务才返回。这里就可以保持线程一直存活。

3-3、线程池的状态流转

    // 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 指调用了 shutdown() 方法,不再接受新任务了,但是需要等待队列里的任务执行完毕
  • STOP 指调用了 shutdownNow() 方法,不再接受新任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行任务
  • TIDYING 所有任务都执行完毕,在调用 shutdown()/shutdownNow() 中都会尝试更新为这个状态
  • TERMINATED 终止状态,当执行完 terminated() 方法后会更新为这个状态
    线程池状态流转图

4、任务提交的两种方式

  1. execute():提交不需要返回值的任务,不能抛出异常
    void execute(Runnable command);
  1. submit():提交需要返回值的任务,可以把异常继续往外抛
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

5、如何合理地配置线程数

设置线程池的线程数需要针对不同的任务类型而定,任务类型可以分为cpu密集型、IO密集型和混合型。(CPU核心数用Ncpu表示)

  1. CPU密集型。线程数 = Ncpu + 1
  2. IO密集型。
    cpu所占用时间不多的情况下:线程数 = Ncpu * 2
    cpu所占用时间较多的情况下:线程数 = 线 程 等 待 时 间 + 线 程 c p u 时 间 线 程 c p u 时 间 \frac{线程等待时间+线程cpu时间}{线程cpu时间}线程cpu时间线程等待时间+线程cpu时间​ * Ncpu
  3. 混合型。根据具体情况来判断,可拆分为IO密集型和CPU密集型

6、总结

全篇介绍了线程池的基本原理,实际使用过程中,需要根据实际情况决定使用何种线程池。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值