关于多线程:多线程线程池源码一

8次阅读

共计 2861 个字符,预计需要花费 8 分钟才能阅读完成。

上一篇文章讲了无关线程池的一些简略的用法,这篇文章次要是从源码的角度进一步带大家理解线程池的工作流程和工作原理。

首先先来回顾下 如何应用线程池开启线程

private static void createThreadByThreadPoolExecutor() {ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    for (int i = 0; i < 10; i++) {MyThread myThread = new MyThread();
        executor.execute(myThread);
    }

能够看到其实没有其它非凡的中央,除了构建线程池的代码,其它最终要的就是executor.execute(myThread) 行代码了。

筹备工作

在多线程系列的第一篇文章中提到了线程和过程的状态,线程池同样也有状态,如下:

  • Running: 容许承受新的工作,并且解决队列中的工作
  • Shutdown: 不承受新的工作,然而依然会解决队列中的工作
  • Stop: 不承受新的工作,不解决队列中的工作,而且中端在运行的工作
  • Tidying: 所有的工作都曾经终端,并且工作线程数归 0,该状态下的线程都会调用terminated() 函数
  • Terminated:terminated() 函数调用完后就进入了此状态

首先须要晓得 4 个概念WorkerworkersworkQueuetask.

  • 在线程池中有个比拟重要的类,那就是 Worker,能够看到其实现了Runnable 接口(其实就是 工作线程 ),继承了AbstractQueuedSynchronizer 类(俗称AQS,在多线程中是很重要的类)

  • workers 就是Worker 的一个汇合,private final HashSet<Worker> workers = new HashSet<Worker>();
  • task:须要 执行的工作 ,也就是execute() 中的参数,实现了Runnable 接口
  • workQueue 就是 工作队列 ,就是上一篇文章中线程池构造函数中的工作队列,外面存储的就是须要执行的工作,队列是实现BlockingQueue 接口的类,有以下这些实现

execute()

本办法传进去的类是须要实现 Runnable 接口的,作为一个command 传进去

遇到 新的工作

  1. 如果工作线程数 < 外围线程数,那么间接加 1 个 worker
  2. 如果线程池是失常的工作状态,并且工作队列可能增加工作,此时须要第二轮判断

    1. 如果线程池因为某种原因不失常了,并且可能胜利从工作队列中删除工作,那么间接采取回绝策略
    2. 如果此时工作线程数为 0,此时须要新建一个线程(并且这里创立的是非核心线程)来执行这个工作,为什么是 null 呢,因为曾经把工作放在工作队列外面了。如果新建的 worker 的 firstTask 是该工作的话,就会反复执行两次工作。
    3. 其它状况也就是失常状况下,是啥都不干,因为次要目标是把工作放到工作队列中
  3. 如果工作队列曾经满了,则须要判断是否可能胜利增加一个非核心线程,如果连非核心线程数都满足不了了,就是线程池真的搞不了了,累了,所以也会调用回绝策略。

留神:外围线程和非核心线程只是语义上的说法,没有实质上的区别

addworker()

addworker 的作用是查看是否能够依据以后池状态和给定界线(外围或最大值)增加新线程 ,并且 通过第二个参数来断定是否创立外围线程,当为 true 的时候就是外围线程,反之就是非核心线程。源码里的正文如下

来看看代码具体是如何的

  1. 一进来就是一个死循环,这个死循环最次要的目标是 确认线程池状态是否失常。如果线程池的状态大于 SHUTDOWN,也就是处于 STOP、TIDYING 或者 TERMINATED 的时候,线程池都没了,还创立 worker 干啥,间接返回 fasle;当线程池处于 SHUTDOWN 的时候,又得再次判断:

    1. 传进来的工作如果不为空,那么就不必增加 worker 的,银行上班了,办理完当初的顾客就不在办理了,不然始终来那不是始终不能上班。
    2. 传进来的工作为空,然而工作队列为空,同样不必增加 worker,银行都没有工作了,而且又到点上班了。除了以上 3 中状况返回 false,其它状况不做任何反馈,往下走。
  2. 又是一个死循环,首先失去工作线程数如果超过了边界,比方超过了容量、外围线程数或者最大线程数,就不必增加 worker 了,银行切实是办理不了新的顾客了;当工作线程数失常的状况下,通过 CAS 来减少工作线程数 ,如果能减少胜利就退出最外层循环。如果减少工作线程失败,那就是其它线程减少了该数量,如果此时线程池的 运行状态产生了扭转 ,则反复外层循环,否则就 自旋直到胜利减少工作线程数。
  3. 到这里就能够说 根本排除了阻碍,以传进来的 firstTask 新建一个 Worker,而后获取 Worker 里的线程。如果线程为 null,那么就间接执行增加 Worker 失败的逻辑,否则就是失常的逻辑。
  4. 先来看 失常的逻辑,拿到锁,并且开始又一次的获取线程池的状态

    1. 如果线程是失常运行状态,或者说是敞开状态下 firstTask 依然为 null,此时如果线程是alive 那么而阐明线程曾经开启,间接抛出异样。
    2. 失常状况下是 wokers 汇合中增加新的 worker 元素,并且调整线程池最大值,设置 workerAdded 标记为 true。
    3. 重点,当 workerAdded 为 true 的时候,开启工作线程,也就是代码中的t.start()
  5. 再来看 失败的逻辑,是在第 3 点结构一个 Worker 对象,获取线程的时候,有可能获取到的线程为空,这样其实就是减少 worker 失败,返回 false,在返回 false 之前会触发执行失败的逻辑addWorkerFailed() 逻辑。整个的逻辑是比较简单的,就不再破费篇幅去论述。

到这里,整个 addWorker() 的流程是比拟清晰的,值得一提的就是 第 2 行 代码中的retry 这个看起来是关键字,但其实不是,仅仅只是一个相似标记位的货色,能够是 retry,也能够是 abc。

通常是配合 for 循环来应用,搭配 continue 和 break 能够达到 goto 的成果。比方continue retry; 就是跳到一开始最外层的 for 循环,break retry; 相当于退出整个循环。写一个最简略的例子大家都能晓得了。

public class RetryExample {public static void main(String[] args) {
        abc:
        for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {if (j == 2) {continue abc;}
                if (i == 8) {break abc;}
                System.out.println("i:" + i + ", j:" + j);
            }
        }
    }
}

输入后果如下图所示

创作不易,如果对你有帮忙,欢送点赞,珍藏和分享啦!

上面是集体公众号,有趣味的能够关注一下,说不定就是你的宝藏公众号哦,根本 2,3 天 1 更技术文章!!!

正文完
 0