# 线程池状态

img

  • 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)
    代表线程执行完毕

image-20230719235443525

# 核心线程 和 救急线程的区别

救急线程是有个生存时间的,它执行完任务了,过了一段时间,没有新任务了,救急线程就会销毁掉,变成结束的状态

核心线程没有生存时间,它执行完任务后,它仍然会被保存在线程池中,不会让核心线程结束,会让核心线程一直去运行

KeepAliveTime 生存时间、unit时间单位,这两个参数就是针对于救急线程的

image-20221014221100359

使用救急线程的前提,是要配合有界队列的使用。

如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。

如果队列选择的是无界队列,那么就不会用到救急线程,任务会一直存入无界队列,然后由核心线程来轮流去处理无界队列里的任务。

如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,

但是很多第三方框架都不是使用的jdk提供的,而是选择使用 更功能上的增强,在这些 功能上进行扩展

image-20221014222804634

# Executors-固定大小线程池

image-20221015090229540

# Executors-单线程线程池

image-20221015091919686

# 线程池中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()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

image-20221015095349872

image-20221015095425618

# 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
2
3
4
5
6
7
8
9
10
11
12
public boolean compareAndSet(V   expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}

我们可以发现,该类检查了当前引用与当前标志是否与预期相同,如果全部相等,才会以原子方式将该引用和该标志的值设为新的更新值,这样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的)等。