线程池基本使用及原理分析

线程池基本使用及原理分析

从源码入手,分析线程池的基本使用场景以及核心代码原理分析。分析版本:jdk1.8

线程池介绍

In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model,[1] a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks.[2] The number of available threads is tuned to the computing resources available to the program, such as parallel processors, cores, memory, and network sockets.[3]

– 摘要自维基百科

线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

– 摘要自百度百科

介绍一般都能理解,这个概念,在平常的开发中也非常常见,例如数据库的连接池这些。主要还是为了方便管理和控制开销。

基本使用

@Test
public void contextLoads() throws IOException {
    //创建一个固定大小的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(10);
	//执行线程
    executorService.execute(() -> {
        System.out.println("running thread " + Thread.currentThread());
    });
    //关闭线程池
    executorService.shutdown();
    System.in.read();
}

上面的代码,就是一个简单的线程池使用方法。Executors是JUC提供的一个简单使用线程池的工具类,为不是很了解线程池的开发者提供了一层封装。

上面的代码是创建了一个FixedThreadPool。在Executors中还提供了一些其他的线程池的创建方法。

FixedThreadPool

创建一个固定大小的线程池

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

CachedThreadPool

这个线程池核心线程数为0,在执行线程时会优先判断是否有可用线程,如果有则使用,如果没有则创建。60秒后,线程被释放(下面的案例中)。

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

SingleThreadExecutor

创建一个只包含单个线程的线程池。

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

ScheduledThreadPoolExecutor

支持延迟执行的线程池。

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

除了以上四种外,Executors中还提供了一些其他的线程池有需要可以简单了解一下。

原理分析

线程池创建

从上面的几种线程看到,最终核心部分还是在于线程池的创建。下面是线程池创建的构造方法:

/**
 * 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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

corePoolSize:核心线程数

maximumPoolSize:最大线程数

keepAliveTime:非核心线程的存活时间

unit:时间的单位

workQueue:这个是任务的队列

threadFactory:线程工厂类

handler:执行拒绝策略时的回调,类似熔断的补偿

上面的部分不同类型的线程池实际就是通过调整这些参数实现的。

线程执行

流程图

线程池执行流程图

execute

下面是线程池执行调度线程的方法:

/**
 * Executes the given task sometime in the future.  The task
 * may execute in a new thread or in an existing pooled thread.
 *
 * If the task cannot be submitted for execution, either because this
 * executor has been shutdown or because its capacity has been reached,
 * the task is handled by the current {@code RejectedExecutionHandler}.
 *
 * @param command the task to execute
 * @throws RejectedExecutionException at discretion of
 *         {@code RejectedExecutionHandler}, if the task
 *         cannot be accepted for execution
 * @throws NullPointerException if {@code command} is null
 */
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();//这里的ctl是用来保存线程池的运行状态和线程数量,高低位运算的机制,高3位用来存状态,低29位用来保存线程数量
    if (workerCountOf(c) < corePoolSize) {//判断当前运行的线程数是否小于核心线程数
        if (addWorker(command, true))//添加工作线程,true false表示是否核心线程
            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);
}

上面就是执行的大致流程,其实都是一些判断。具体的执行步骤实际在源码中作者已经有了标注:

/*
 * 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.
 */

而执行的核心代码,在于addWorker,接下来分析一下addWorker的核心代码。

addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {//其实这段代码就是检查一下状态和队列情况,然后cas工作线程数
        int c = ctl.get();
        int rs = runStateOf(c);
		//检查线程池状态
        //如果线程池已经shutdown,不会接受新的任务,但是未执行完的任务还是会继续执行
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))//cas工作线程数,成功跳出循环
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)//如果状态发生变化,continue
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;//启动标识
    boolean workerAdded = false;//添加成功标识
    Worker w = null;
    try {
        w = new Worker(firstTask);//这里的worker实现了Runnable
        final Thread t = w.thread;//执行worker的线程
        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());
				//检查线程池状态、检查firstTask状态、检查worker线程状态
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);//将worker添加到集合
                    int s = workers.size();
                    if (s > largestPoolSize)//如果集合长度大于最大线程数,更新记录最大线程数
                        largestPoolSize = s;
                    workerAdded = true;//标识添加成功标识
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {//添加成功后,启动worker中的线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)//如果失败了,会去执行回滚
            addWorkerFailed(w);//执行回滚
    }
    return workerStarted;
}

addWorker的方法就是做一些判断校验,以及将firstTask添加到工作线程worker中。实际将逻辑委派给worker来执行,所以还需要了解一下worker的执行逻辑。worker的run方法中是调用runWorker(),因此看一下runWorker的方法逻辑。

runWorker
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;//拿到初始化的firstTask
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        //这个线程就是从task中取任务,如果任务不为空就一直循环执行任务
        while (task != null || (task = getTask()) != null) {//这里的getTask是从阻塞队列中取任务
            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方法,而不是通过线程启动的
                    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 {
        //销毁worker
        processWorkerExit(w, completedAbruptly);
    }
}

分析完runWorker方法后,可以得出,task任务的执行实际就是通过直接调用task的run方法来完成的,而在执行完一个任务后,会循环从队列中取任务,在任务都执行完成后,去销毁worker。这块逻辑的关注点主要在getTask()方法以及processWorkerExit()方法,也就是从队列中获取任务以及任务执行完成后的销毁。接下来逐步分析一下getTask和processWorkerExit。

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

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
		//判断线程池如果shutdown了并且为stop或者队列中没有任务了,则减去workerCount并返回null(返回null后线程会退出)。
        //可以看到,如果队列中还有任务,这里还是会执行完的(除非线程池STOP了)
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
		//拿到工作线程数
        int wc = workerCountOf(c);
		//这里的timed用来做超时判断
        //allowCoreThreadTimeOut默认为false,表示核心线程默认一直保持活动
        //如果工作线程数已经大于核心线程数了,也就是非核心线程
        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
		//如果工作线程数已经大于最大线程数了或者校验超时控制(也就是任务超时了)
        //并且任务队列已经没有任务了,则返回null
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            //如果需要超时控制,通过poll取任务,否则通过take
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)//取到任务返回
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

分析完getTask方法可以发现,线程池如果shutdown了,任务还是会被执行完,但是如果是stop了,则不会继续执行了。核心线程通过take阻塞一直保持(除非设置了核心线程超时),非核心线程通过poll取任务,超时后会进入销毁逻辑。最后,在来看下销毁。

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();
    try {
        //记录完成数量
        completedTaskCount += w.completedTasks;
        workers.remove(w);//集合中删除worker
    } finally {
        mainLock.unlock();
    }
	//尝试终止
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {//判断是否已经终止
        if (!completedAbruptly) {//为false表示任务已经执行完了的场景
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;//这个min用来判断核心线程的数量
            if (min == 0 && ! workQueue.isEmpty())//如果核心线程keep数量为0并且任务队列不为空则会至少保持一个worker的存活
                min = 1;
            if (workerCountOf(c) >= min)//如果当前的worker已经大于配置的核心线程数量了,则让线程被销毁
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}
总结

通过线程池来执行任务,原理是通过委派给一个Worker对象来完成。

Worker对象本身也实现了Runnable接口,通过一个线程启动调用到Worker对象的run方法,Worker对象在来调用任务的run方法完成对象任务的逻辑执行。Worker对象同时继承了AQS,这是为了自定义实现独占锁的功能,这是为了表示当前线程正在执行任务中,不应该被中断。

核心线程会在getTask的时候通过调用阻塞队列的take方法阻塞,直到有新的任务加入队列重新开始执行。非核心线程在执行完任务后会被销毁,超时时间的表现是在从阻塞队列中取任务时,是调用take还是poll来完成。

核心线程会被回收的场景是在设置了allowCoreThreadTimeOut参数为true后,会将超时的核心线程销毁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值