java线程池:线程池怎么开启的、线程池怎么工作的、线程池是怎么关闭的

1.序

位于 java.util.concurrent 下的 ThreadPoolExecutor 是java的线程池,可以用来管理线程。

本文围绕线程池的开启、工作、关闭三个方向进行探究,去追溯源码的实现。

 

2.线程池怎么开启的

ThreadPoolExecutor 有四个构造方法,如下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

实际上前三个构造方法最终都是调用最后一个方法,然后前三个方法中没有的构造参数,在调用时候给了一个默认值。

可通过如下代码开启一个线程器并提交一个任务到池中:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
threadPoolExecutor.execute(()->{
        // todo
    });

那么问题来了,调用 execute() 方法后,线程池是怎么添加线程的呢?

execute() 的源码:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) { // 当前线程数是否大于核心线程数
            if (addWorker(command, true)) // 开启线程
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {// 如果线程池处于running并且添加任务到等待队列成功
            int recheck = ctl.get();
            if (!isRunning(recheck) && remove(command)) // 如果线程池处于非running并且移除任务成功,那么驳回任务
                reject(command);
            else if (workerCountOf(recheck) == 0) // 如果工作线程数为零,那么添加一个null的任务,以防新的任务进来却没有线程可以去工作
                addWorker(null, false);
        } else if (!addWorker(command, false)) // 如果添加非核心线程时失败,那么驳回任务
            reject(command);
    }

execute() 方法的工作是先判断线程数是否大于核心线程数,小于就调用 addWorker() 方法添加核心线程,否则就添加到任务队列等待。在添加任务到队列采用 offer() 方法,如果返回 false ,那么就立即再次调用 addWorker() 方法添加一个非核心线程。

这时再来看 addWorker() 方法,它是怎么添加一个线程的呢?

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            // 检验线程池...
        }
        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())
                            throw new IllegalThreadStateException();
                        workers.add(w); // 把线程添加到HashSet中保存
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start(); // 开启线程
                    workerStarted = true;
                }
            }
        } finally {
            // ...
        }
        return workerStarted;
    }

查看 addWorker() 方法,不难发现线程池的是把咱们的线程类封装成了一个任务,放到自己的 Worker 类中执行。

至此一个线程就开启成功了。

 

3.线程池怎么工作的

线程池是如何工作的呢?

那么再去查看 Worker 这个类的 run() ,Worker 是线程池的任务执行类,实现了 Runnable 接口。

Worker 类的 run 方法调用的是 runWorker() 这个方法。

final void runWorker(Worker w) {
        // ...
        Runnable task = w.firstTask; // 获取咱们提交的任务
        // ...
        try {
            while (task != null || (task = getTask()) != null) { // 获取任务去执行
                // ...
                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 {
                    // ...
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

runWorker() 先拿到当前线程的任务,调用 task.run() 来执行我们在业务侧提交的任务。处理完毕后,再次去等待队列中提取任务进行处理,如果没有任务那么终止这个 while 死循环,最后该线程结束。

可问题来了,线程池的核心线程数是永久活跃的,那么这个 while() 循环就不能退出,否则该核心线程就结束了。

这时带着这个问题去看下循环条件中的 getTask() 这个方法,

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        for (;;) {
            // ...
            int wc = workerCountOf(c); // 工作线程数
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // true-工作线程数>核心线程或者允许超时
            // ...
            try {
                Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        workQueue.take(); // 如果允许超时,那么在 keepAliveTime 内没有 poll  获取数据则返回 null ,否则使用 take 方法会一直阻塞,直到获取新的数据。
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

关键看 allowCoreThreadTimeOut 这个参数,源码中的注释:

如果为false(默认值),核心线程即使在空闲时也保持活动。

如果为真,核心线程使用keepAliveTime超时等待工作。

在默认情况下,allowCoreThreadTimeOut 是 false ,即不允许超时。再结合从队列获取任务的代码,对线程是怎么一直保持活跃的就十分清晰了。如果工作线程数大于核心线程数,那么在 keepAliveTime 中没有拉取到任务的话,就会返回一个 null ,这个 null 会使 runWorker() 的while循环终止,那么该非核心线程就运行完毕结束了。而如果工作线程数小于核心线程数,那么会使用 take() 方法一直阻塞的去拉取任务,直到新的任务添加进来,然后返回 runWorker() 去处理任务。

 

4.线程是怎么关闭的

ThreadPoolExecutor 为我们提供两种方式去关闭线程池:

shutdown()启动一个先前提交的有序关闭执行任务,但不接受新的任务。如果已经关闭,则调用没有额外的效果。
shutdownNow()试图停止所有正在执行的任务,将停止处理等待中的任务,并返回任务列表等待处决的人这些任务被耗尽(删除)从此方法返回时从任务队列中返回。

shutdown() 调用后不再接受新的任务,并在当前任务执行完成后关闭线程池。

shutdownNow() 调用后不再接受新的任务,并将当前等待队列中的任务返回给调用者,然后试图停止所有正在执行的任务,对于那种会抛出 InterruptedException 异常的方法会立即抛出异常。

如果需要检测线程池是否真正关闭,使用 awaitTermination() 方法去查看线程状态。

 

两个方法都是通过

t.interrupt();

使线程中断,不过的是 shutdown 会在调用去获取该线程的锁(任务执行任务时持有锁),而 shutdownNow 却不会。

t.interrupt(); 让可以抛出 InterruptedException 异常的线程立即抛出异常,如 sleep() ,take() 方法。

看线程池调用停止方法后,工作线程如何处理的:

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

        for (;;) {

            //...

            /**
             * 把线程数减一,并且返回null,null会是上一层方法的while循环结束,从而结束线程
             * 进入该 if 的条件如下:
             * 1. 线程状态为 STOP
             * 2. 线程状态为 SHUTDOWN 且任务队列没有任务了
             */
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

           //...

            try {
                Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        workQueue.take(); // take() 方法会在线程调用 interrupt() 方法是抛出异常,即该核心线程不在此阻塞等待任务。
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

 

两个方法的区别:

1.shutdown() 设置线程池的状态为 SHUTDOWN,shutdownNow() 设置线程池的状态为 STOP

2.shutdown() 什么都不返回。shutdownNow() 会把所有任务队列中的任务取出来,返回一个任务列表。

3.shutdown() 会执行完等待队列中的任务,shutdownNow() 不会。

 

5.总结

1.业务方提交线程

2.线程池收到任务,判断当前工作线程是否小于核心线程?是-创建新的线程,否-添加任务到等待队列

3.任务队列是否已满?是-创建新的线程去处理任务,否-添加任务到等待队列

4.当前工作线程是否大于最大线程数?是-调用线程池拒绝策略拒绝该任务,否-创建新的线程处理任务

5.核心线程处理完任务,调用 take() 方法阻塞拉取任务。非核心线程在 leepAliveTime 内拉取不到任务即结束工作。

6.任务全部处理完毕,关闭线程池。

6.常见问题

1.线程池一创建就自动创建相应的核心线程数吗?

答:不会。只有在提交线程的时候才会去创建。


2.为什么核心线程5个,最大线程10个,可提交6个线程却只有5个线程在工作?

答:只有当等待队列满了,才会去创建额外的线程去工作。

3.调用了 shutDownNow() 方法,为什么线程池却迟迟不关闭?

答:调用该方法后,仅仅是把线程池的状态设置为 STOP,并为所有线程执行 interrupt() 方法,如果该线程处理的任务中没有抛出 InterruptedException 异常的方法或者手动捕获了 InterruptedException 异常,那么该线程会继续运行直到结束。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值