# 线程池状态
- RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务。
- SHUTDOWN:指调用了 shutdown() 方法,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。
- STOP:指调用了 shutdownNow() 方法,不再接受新提交的任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行任务。
- TIDYING: 所有任务都执行完毕,workerCount 有效线程数为 0。
- TERMINATED:终止状态,当执行 terminated() 后会更新为这个状态。
# 线程状态
线程实际上是分为六种状态的,既
-
1.初始状态(NEW)
- 线程被构建,但是还没有调用start方法
-
2.运行状态(RUNNABLE)
- Java线程把操作系统中就绪和运行两种状态统一称为“运行中”
-
3.阻塞状态(BLOCKED)
表示线程进入等待状态,也就是线程因为某种原因放弃了CPU的使用权,阻塞也分为几种情况(当一个线程试图获取一个内部的对象锁(非java.util.concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态。)
等待阻塞:运行的线程执行了Thread.sleep、wait、join等方法,JVM会把当前线程设置为等待状态,当sleep结束,join线程终止或者线程被唤醒后,该线程从等待状态进入阻塞状态,重新占用锁后进行线程恢复
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么JVM会把当前项城放入到锁池中
其他阻塞:发出I/O请求,JVM会把当前线程设置为阻塞状态,当I/O处理完毕则线程恢复
- 4.等待(WAITING)
- 等待状态,没有超时时间(无限等待),要被其他线程或者有其他的中断操作
执行wait、join、LockSupport.park()
- 5.超时等待(TIME_WAITING)
- 与等待不同的是,不是无限等待,超时后自动返回
执行sleep,带参数的wait等可以实现
- 6.终止(Teminated)
代表线程执行完毕
# 核心线程 和 救急线程的区别
救急线程是有个生存时间的,它执行完任务了,过了一段时间,没有新任务了,救急线程就会销毁掉,变成结束的状态
核心线程没有生存时间,它执行完任务后,它仍然会被保存在线程池中,不会让核心线程结束,会让核心线程一直去运行
KeepAliveTime 生存时间、unit时间单位,这两个参数就是针对于救急线程的
使用救急线程的前提,是要配合有界队列的使用。
如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
如果队列选择的是无界队列,那么就不会用到救急线程,任务会一直存入无界队列,然后由核心线程来轮流去处理无界队列里的任务。
如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,
但是很多第三方框架都不是使用的jdk提供的,而是选择使用 更功能上的增强,在这些 功能上进行扩展
# Executors-固定大小线程池
# Executors-单线程线程池
# 线程池中shutdown()和shutdownNow()方法的区别
一般情况下,当我们频繁的使用线程的时候,为了节约资源快速响应需求,我们都会考虑使用线程池,线程池使用完毕都会想着关闭,关闭的时候一般情况下会用到shutdown和shutdownNow,这两个函数都能够用来关闭线程池,那么他们俩之间的区别是什么呢?下面我就用一句话来说明白shutdown和shutdownNow的区别。
一句话说明白shutdown和shutdownNow的区别:
shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。
而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。
例子:举个工人吃包子的例子,一个厂的工人(Workers)正在吃包子(可以理解为任务),假如接到shutdown的命令,那么这个厂的工人们则会把手头上的包子给吃完,没有拿到手里的笼子里面的包子则不能吃!而如果接到shutdownNow的命令以后呢,这些工人们立刻停止吃包子,会把手头上没吃完的包子放下,更别提笼子里的包子了。
1、shutDown()
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
2、shutdownNow()
执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
# ABA 问题及解决
CAS引发的ABA问题
ABA问题是指在CAS操作时,其他线程将变量值A改为了B,但是又被改回了A,等到本线程使用期望值A与当前变量进行比较时,发现变量A没有变,于是CAS就将A值进行了交换操作,但是实际上该值已经被其他线程改变过,这与乐观锁的设计思想不符合。ABA问题的解决思路是,每次变量更新的时候把变量的版本号加1,那么A-B-A就会变成A1-B2-A3,只要变量被某一线程修改过,改变量对应的版本号就会发生递增变化,从而解决了ABA问题。在JDK的java.util.concurrent.atomic包中提供了AtomicStampedReference来解决ABA问题,该类的compareAndSet是该类的核心方法,实现如下:
1 | public boolean compareAndSet(V expectedReference, |
我们可以发现,该类检查了当前引用与当前标志是否与预期相同,如果全部相等,才会以原子方式将该引用和该标志的值设为新的更新值,这样CAS操作中的比较就不依赖于变量的值了。
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了
AtomicMarkableReference:
基本和AtomicStampedReference差不多,AtomicStampedReference主要关注版本号,即reference的值被修改了多少次;AtomicMarkableReference是使用boolean mark来标记reference是否被修改过
# Synchronized 和 Lock 的主要区别
Synchronzied 和 Lock 的主要区别如下:
-
存在层面:Syncronized 是Java 中的一个关键字,存在于 JVM 层面,Lock 是 Java 中的一个接口
-
锁的释放条件:1. 获取锁的线程执行完同步代码后,自动释放;2. 线程发生异常时,JVM会让线程释放锁;Lock 必须在 finally 关键字中释放锁,不然容易造成线程死锁
-
锁的获取: 在 Syncronized 中,假设线程 A 获得锁,B 线程等待。如果 A 发生阻塞,那么 B 会一直等待。在 Lock 中,会分情况而定,Lock 中有尝试获取锁的方法,如果尝试获取到锁,则不用一直等待
-
锁的状态:Synchronized 无法判断锁的状态,Lock 则可以判断
-
锁的类型:Synchronized 是可重入,不可中断,非公平锁;Lock 锁则是 可重入,可判断,可公平锁
-
锁的性能:Synchronized 适用于少量同步的情况下,性能开销比较大。Lock 锁适用于大量同步阶段:
-
Lock 锁可以提高多个线程进行读的效率(使用 readWriteLock)
-
在竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
-
ReetrantLock 提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。