Runnable,Thread, 线程池
首先,Runnable本质上就是一个接口,接口里面有一个run()方法。
![在这里插入图片描述](https://img-blog.csdnimg.cn/6c977f44b9a44fd3b9ded38fa29c1141.png
也就是说,当其他类实现这个接口的时候,也会去重写这个run()方法,而且他的注释说的很清楚
注释的意思是当使用实现接口 Runnable 的对象创建线程时,启动线程会导致在该单独执行的线程中调用对象的 run 方法。
也就是 当实现Runnable接口的对象(比如Thread类)创建线程时,启动线程会导致在该单独执行的线程中(也就是Thread对象的这个线程中)调用对象的 run 方法。(调用Thread的run()方法)
。
这就是所有实现Runnable的方法。
对于Thread而言,
他是实现Runnable接口的,并且他还提供了更多的可用方法和成员变量。对于Thread类而言,他的注释中写了这么两句
这第一句的意思是有两种方法可以创建新的执行线程。一种是将类声明为 Thread 的子类。此子类应重写类 Thread 的运行方法。然后可以分配并启动子类的实例.
第二句的意思是创建线程的另一种方法是声明一个实现 Runnable 接口的类。然后,该类实现 run 方法。然后可以分配类的实例,在创建 Thread 时作为参数传递,然后启动。
很有意思,Thread类在他的源码里已经告诉我们创建线程的两种方法了。
对于我的理解而言,我觉得Thread和Runnable的一些区别,可能是在于,实现Runnable接口,我们可以把想要执行的任务(或者说代码。方法,想干啥的),和实现该任务的线程分开,这样可以更好的去控制任务与任务的实现。
线程池
关于线程池,其他很多文章多有介绍,这里不再过多赘述,我们直接去看源码。
先看看其构造方法(直接底层Ctrl+F找就可以了)
我们先看看上面注释
第一句
大意:使用给定的初始参数和默认线程工厂以及被拒绝的执行处理程序创建ThreadPoolExecutor。使用 Executors工厂方法之一而不是此通用构造函数可能更方便
(我还没有理解这句话的意思,我得去看看其他书或者博客)
然后就是线程的几个参数,这里源码给出了有5个,6个,7个的参数(基于不同的构造方法,我只列举5个和7个,展示下区别):
5个:
7个:
1:corePoolSize 要保留在池中的线程数(也就是核心线程),即使它们处于空闲状态,除非设置了 {@code allowCoreThreadTimeOut} (意思是将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收)
2:maxumPoolSize 池中允许的最大线程数
3: keepAliveTime 当线程数大于核心时,这是多余的空闲线程在终止之前等待新任务的最长时间。(就是maxumPoolSize减去corePoolSize的剩余的线程的存活数量)
4:unit keepAliveTime参数的时间单位
5:workQueue队列,以便在执行任务之前用于保留任务。此队列将仅包含 {@code execute} 方法提交的 {@code Runnable} 任务。(阻塞队列)
6:threadFactory 执行器创建新线程时使用的工厂
7:handler 由于达到线程边界和队列容量而被阻止执行时要使用的处理程序(拒绝策略)
然后 下面是源码的4中构造方法:
其实 这所谓的四种方式 ,本质上是通过this调用最后一个构造方法(也就是最后一个,7个参数都被使用的那个构造方法)。
这些是线程池所谓的7大参数,还有构造方法,那么他们的具体实现,或者说执行方法是execute
那我们来看看
还是先看看方法上面的注释
这两句话的本意是在将来的某个时候执行给定的任务。任务可以在新线程或现有池线程中执行。如果无法提交任务执行,无论是因为此执行程序已关闭还是已达到其容量,则任务由当前 {@code 拒绝执行处理程序} 处理。(就是最大线程数满了,阻塞队列满了,还有任务过来,已经无法提交任务了,就执行拒绝策略)
然后,执行线程池的步骤分三步(源码已经告诉我们这三步分别是什么):
我们仔细看一下这三步
1. 如果正在运行的线程少于 corePoolSize,请尝试使用给定命令作为其第一个任务来启动新线程。对 addWorker 的调用以原子方式检查 runState 和 workerCount,因此通过返回 false 来防止错误警报,这些警报会在不应该添加线程时添加线程
2: 如果一个任务可以成功排队,那么我们仍然需要仔细检查我们是否应该添加一个线程(因为现有线程自上次检查以来就死了)或者池在进入此方法后关闭了。因此,我们重新检查状态,如有必要,如果停止,则回滚排队,如果没有,则启动一个新线程。
3:如果我们不能排队任务,那么我们尝试添加一个新线程。如果失败了,我们知道我们已经关闭或饱和,因此拒绝这项任务。
这是注释这一部分的意思,我们具体到源码去看着三步的意思
第一步:
那我们点进去看看int c = ctl.get();是什么意思
:
那我简单介绍一下
分成三个部分:
我先介绍第三部分的方法:
三个方法分别是
1:计算当前运行状态private static int runStateOf(int c) { return c & ~CAPACITY; }
2:计算当前线程数量private static int workerCountOf(int c) { return c & CAPACITY; }
3:得到当前运行状态和线程数量
到底什么意思呢?
首先,我们是构建一个AtomicInteger
对象ctl
来控制数量和状态的
而AtomicInteger
是32位,于是当初人家就把前三位当做状态,也就是000-111,足够线程的所有状态表示了,而后面29位就表示线程数量,通过位数运算,不同的位数不影响,于是就可以得出
:
注意,中间的线程状态是池的状态,我所说的全部指的是线程池,因为我们一直在 ThreadPoolExecutor
里面
而这几个状态是指:
RUNNING:能接受新提交的任务,也能处理阻塞队列中的任务 //111
SHUTDOWM:不再接受新提交的任务,但却可以继续处理阻塞队列中的任务。(干完这些活就结束了) //000
STOP:不再接受新提交的任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行任务(直接就结束了,不干了) //001
TIDYING:所有任务都执行完毕,workerCount 有效线程数为 0 //010
TERMINATED:终止状态 // 011
好,弄清楚了这些
回到执行方法这边:
通过一个int c
拿到当前线程池工作线程是否小于核心线程
然后添加到工作队列
我们浅浅分析一下addworker
(读一下注释就行)
:
什么意思呢?
大致意思是说(addworker
):
检查是否可以根据当前池状态和给定边界(核心或最大)添加新工作线程。如果是这样,则会相应地调整工作线程计数,如果可能,将创建并启动一个新工作线程,将 firstTask 作为其第一个任务运行。如果池已停止或符合关闭条件,则此方法返回 false。如果线程工厂在询问时无法创建线程,它也返回 false。如果线程创建失败,要么是由于线程工厂返回 null,要么是由于异常(通常是 Thread.start() 中的 OutOfMemoryError),我们将干净地回滚
就是两种:1,如果核心线程还没用完,就使用核心线程,如果核心线程用完了,最大线程还没用完,就创建新线程,执行任务队列中的第一个任务,如果最大线程也用完了,或者线程池已经关闭了,或者线程工厂在询问时无法创建线程,返回false,如果是创建线程失败,就回滚。总的来说,就是新建一个线程并且启动,把线程数量+1。
好,回来:
此时仅仅是说需要添加一个新核心线程去工作(boolean类型返回true)
,退出,如果创建失败了(并发),那么我就再获取c的值,退出。
第二步:
判断池的状态是否在运行,并且确定任务已经加到工作队列中,如果是运行状态,并且插入成功,就意味着任务队列中还有位置
`重新检查任务状态(并发),如果说池没有运行,就把这个任务抛弃,如果这个任务被抛弃,抛弃成功了,调用拒绝策略,如果失败了,就会认为工作线程数量为0,判断一下是不是等于0,是的话,就创建一个任务为空的线程,去处理这个任务(抛弃这个任务,因为这个任务加载工作队列中)。