java并发编程
本篇笔记记录与2020年10月,那时候也不记的是看的谁的课程记的笔记,内容挺好的,就是可惜我笔记有几次没保存中间丢失了一部分。迁移至此,分享给需要他的人。
1 Java 线程
- 创建和运行线程
- 查看线程
- 线程API
- 线程状态
1.1 创建和运行线程
方法一,直接使用Thread
Thread t1 = new Thread(){
@Override
public void run() {
log.debug("running...");
}
};
t1.setName("t1");
t1.start();
方法二,使用Runnable配合Thread
Runnable runnable = new Runnable() {
public void run() {
log.debug("running...");
}
};
Thread t1 = new Thread(runnable);
t1.setName("t1");
t1.start();
---
//简化版
Runnable r = ()->log.debug("running...");
Thread t1 = new Thread(r,"t1");
t1.start();
原理之Thread与Runnable的区别
分析源码
小结
- 方法1 是把线程和任务合并在一起,方法2把线程和任务分开
- 用Runnable更容易与线程池等高级API配合
- 用Runnable让任务脱离了Thread继承体系,更灵活
方法三,FutureTask配合Thread
FutureTask<String> task = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
log.debug("running");
Thread.sleep(1000);
return "callable....";
}
});
Thread t1 = new Thread(task);
t1.start();
String s = task.get();
log.debug("{}",s);
1.2 查看进程和杀死进程
windows
- tasklist
- taskkill
Linux
- ps -fe 查看所有进程
- ps -fT -p <PID> 查看某个进程
- kill 杀
- top 按大写H切换是否显示线程
- top -H -p <PID> 查看进程
Java
- jps 查看所有Java进程
- jstack <PID> 查看某个Java进程(PID) 的所有线程状态
- jconsole 查看某个Java进程中的运行状况(图形界面)
1.3原理之线程运行
栈与栈帧
Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道JVM中由堆栈方法区构成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所产生的内存。
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
线程的上下文切换(Thread Context Switch)
因为以下原因导致cpu不再执行当前线程,转而执行另一个线程的代码
- 线程的cpu时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了sleep、yield、wait、park、synchronized、lock等方法
当上下文切换发生时,需要操作系统保持当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址,是线程私有的。
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
- Context Switch频繁发生会影响性能
1.4 常见方法
方法名 | static | 功能 | 注意 |
---|---|---|---|
start() | N | 启动一个新的线程,在新的线程运行run方法中的代码 | start方法只能让线程处于就绪状态,每个线程对象的start方法只能调用一次。 |
run() | N | 新线程启动后调用 | 如果在构造Thread对象时传递了Runnable参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作,但可以创建Thread的子类对象,来覆盖默认行为。 |
join() | N | 等待线程运行结束 | |
join(long n) | N | 等待线程运行结束,最多等待n秒 | |
getId() | N | 获取线程的长整形ID | ID唯一 |
getName() | N | 获取线程名 | |
setName(String) | N | 修改线程名 | |
getPriority() | N | 获取线程优先级 | |
setPriority(int) | N | 修改线程优先级 | Java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被CPU调度的机率 |
getState() | N | 获取线程状态 | Java中线程的状态用6个enum表示,分别为:NEW|RUNNABLE|BLOCKED|WAITING|TIMED_WAITING |
isInterrupted() | N | 判断是否被打断 | 不会清除打断标记 |
isAlive() | N | 判断是否存活(还没有运行结束) | |
interrupt() | N | 打断线程 | 如果被打断的线程正在sleep,wait,join会导致被打断的线程抛出InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park的线程被打断,也会设置打断标记。 |
interrpted() | Y | 判断当前线程是否被打断 | 会清除打断标记 |
currentThread() | Y | 获取当前正在执行的线程 | |
sleep(long n) | Y | 让当前线程休眠n毫秒,休眠时让出cpu的时间片给其他线程 | |
yield() | Y | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
join方法详解
此段代码运行结果为0,并不是所期望的。
public class Test7 {
public static int i = 0;
public static void main(String[] args) {
test();
System.out.println(i);
}
public static void test(){
Thread r = new Thread(()->{
try {
Thread.sleep(1);
i=10;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
r.start();
}
}
由于r线程还没结束,主线程的打印语句就先执行导致结果为0,所以让主线程等待r线程执行结束。
public class Test7 {
public static int i = 0;
public static void main(String[] args) {
test();
System.out.println(i);
}
public static void test(){
Thread r = new Thread(()->{
try {
Thread.sleep(1);
i=10;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
r.start();
try {
r.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
interrupt方法详解
打断sleep,wait,join等被阻塞的线程
打断正常运行的线程后会添加一个打断标记(isInterrupted()方法查看),但像以上三个方法这样被阻塞的线程会清除打断标记,以异常的方式表示是否被打断,isInterrupt()的调用结果依然为假。执行interrupt()方法只是给被打断的线程上一个打断标记,告诉线程我想打断你,并不会真的直接打断。需要在被打断的线程里通过判断打断标记来自行通过代码决定之后的操作,如果没有判断,就不做处理继续运行。
1.5 守护线程
守护线程是随着java其他所有线程的结束而结束的,也就是说就算守护线程的代码还没有执行完,此时已经没有其他线程在运行了守护线程也会结束。
package test;
public class Test8 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-执行守护线程代码...");
}
},"t1");
//设置t1为守护线程
t1.setDaemon(true);
t1.start();
Thread.sleep(2000);
System.out.println("main线程运行结束...");
}
}
运行结果:
t1-执行守护线程代码... t1-执行守护线程代码... t1-执行守护线程代码... main线程运行结束...
注意
- 垃圾回收器就是一种守护线程
- Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待他们处理完当前请求。
1.6 Thread类中State状态枚举
NEW,
线程刚刚被创建,还没有调用start方法
---
RUNNABLE,
调用了start方法之后都认为是可运行。涵盖了操作系统中的可运行状态,运行状态和阻塞状态。
---
BLOCKED,
---
WAITING,
join,wait
---
TIMED_WAITING,
sleep
---
TERMINATED
终止状态,线程里的代码致谢结束。
2 管程 - 悲观锁 (阻塞)
2.1 共享带来的问题
临界区 Critical Section
- 一个程序运行多个线程本身是没有问题的
- 问题出在多个线程访问共享资源
- 多个线程读共享资源也没有问题
- 在多个线程对共享资源读写操作时指令交错,就有可能出现问题
- 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
竟态条件 Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,则称之为发生了竟态条件
2.2 synchronized 解决方案
应用之互斥
为了避免临界区的竟态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案:synchronized,Lock
- 非阻塞式的解决方案:原子变量
synchronized俗称对象锁,它采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其他的线程再想获得对象锁时就会阻塞住。这样就能拥有锁的线程可以安全的执行临界区内的代码,不用担心上下文切换。
注意
虽然Java中互斥和同步都可以采用synchronized关键字来完成,但他们还是有区别的:
- 互斥是保证临界区的竟态条件发生时,同一时刻只能有一个线程执行临界区代码
- 同步是由于线程执行的先后顺序不同,需要一个线程等待其他线程运行到某个点
synchronized
语法
synchronized(对象){
临界区
}
public class Test9 {
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int j = 0; j < 5000; j++) {
synchronized (Test9.class) {
i++;
}
}
},"t1");
Thread t2 = new Thread(()->{
for (int j = 0; j < 5000; j++) {
synchronized (Test9.class) {
i--;
}
}
},"t2");
t1.start();
t2.start();
while (true){
if (!t1.isAlive()&&!t2.isAlive()){
System.out.println(Thread.currentThread().getName()+":i="+i);
break;
}
}
}
}
运行结果 main:i=0
方法上的synchronized
class Test{
public synchronized void test(){
//...
}
}
等价于
class Test{
public void test(){
synchronized(this){
//...
}
}
}
--------------
class Test{
public static synchronized void test(){
//...
}
}
等价于
class Test{
public static void test(){
synchronized(Test.class){
//...
}
}
}
不加synchronized的方法无法保证原子性,线程不安全。
常见的线程安全的类
- Integer
- String
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent包下的类
这里的线程安全指的是多线程调用他们的同一个实例的某个方法时是线程安全的。也可以理解为
- 他们是原子的
- 但注意他们的多个方法组合不是原子的
2.3 Monitor
Java对象头
以32位虚拟机为例
普通对象
|-------------------------------------------|
| Object Header (64bits) |
|----------------------|--------------------|
| Mark Word (32bits) | Klass Word(32bots) |
|----------------------|--------------------|
数组对象
|--------------------------------------------------------------------|
| Object Header (96bits) |
|---------------------------------------------|----------------------|
| Mark Word (32bits) | Klass Word(32bots) | array length(32bits) |
|---------------------------------------------|----------------------|
其中Mark Word结构为
|--------------------------------------------------|----------------------|
| Mark Word (32bits) | State |
|--------------------------------------------------|----------------------|
| hashcodF:\stu\资料 25 | agF:\stu\资料4 |biased_lock:0|01 | Normal |
|--------------------------------------------------|----------------------|
| thread: 23 |epoch:2| agF:\stu\资料4 |biased_lock:1|01 | Biased |
|--------------------------------------------------|----------------------|
| ptr_to_lock_record:30 |00 | Lightweight Locked |
|--------------------------------------------------|----------------------|
| ptr_to_heavyweight_monitor:30 |10 | Lightweight Locked |
|--------------------------------------------------|----------------------|
| |11 | Marked for GC |
|--------------------------------------------------|----------------------|
Monitor(锁)
Monitor被翻译成监视器或管程
注意:
- synchronized进入同一对象的monitor
- 不加synchronized的对象不会关联监视器
2.4 轻量级锁
轻量级锁的应用场景:如果一个对象虽然是有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
- 轻量级锁对使用者是透明的,语法任然使用synchronized
假设有两个方法同步块,利用一个对象加锁
static final Object obj = new Object();
public static void method1(){
synchronized(obj){
//同步块A
method2();
}
}
public static void method2(){
synchronized(obj){
//同步块B
}
}
- 锁记录(Lock Record) 对象,每个线程的栈帧都包含一个锁记录的结构,内部可以存储锁定对象的Mark Word。
- 让锁记录中的Object reference指向锁对象,并尝试使用cas替换Object的Mark Word,将Mark Word的值存入锁记录。
- 如果cas替换成功,对象头中存储了锁记录地址和状态00(00表示轻量级锁),表示由该线程给对象加锁。
- 如果cas失败,有两种情况
- 如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程。
- 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record 作为重入的计数。(再次创建Lock Record 数据值为null,对象指针指向对象,自己的线程再次给同一个对象加锁,加几次锁就有多少个锁重入)
- 当退出synchronized代码块(解锁时)如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
- 当退出synchronized代码块(解锁时)锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头
- 成功,则解锁成功
- 失败,则说明轻量级锁进行了锁膨胀已经升级为了重量锁,进入重量级锁解锁流程
2.5 锁膨胀
如果在尝试加轻量级锁的过程中,cas操作无法成功,这时一种情况就是其他线程为此对象加了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
static Object obj = new Object();
public static void method1(){
synchronized(obj){
//同步块
}
}
- 当Thread-1进行轻量级锁加锁时,Thread-0已经对该对象加了轻量级锁
- 这时Thread-1加轻量级锁失败,进入锁膨胀流程
- 即为Object对象申请Monitor锁,让Object指向重量级锁地址
- 然后自己进入Monitor的EntryList BLOCKED(这个时候资源竞争发生了,使用重量级锁,没有被cpu分配到资源的线程进入BLOCKED阻塞状态,在Monitor的阻塞队列EntryList中阻塞等待)
- 当Thread-0退出同步块解锁时,使用cas将Mark Word的值恢复给对象头,失败。这时进入重量级锁的解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中BLOCKED的线程。
2.6 自旋优化 (自旋锁)
重量级锁竞争的时候,还可以使用自旋来优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块释放了锁),这时当前线程就可以避免阻塞。
在线程1给对象加了重量级锁的情况下,线程2来了发现对象被加了重量级锁开始自旋,一旦在自旋的过程中线程1释放了锁,Monitor的Owner为null。这时线程2成功获得锁,执行同步块。如果自旋次数达到一定数值就会自旋失败,进入EntryList BLOCKED进行阻塞等待。
值得注意的是
- 线程进入阻塞状态会进行上下文切换。
- 自旋是在有cpu分配的情况下,单核下自旋将毫无意义。
- Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就会多自旋几次;反之就会少自旋甚至不自旋,比较智能。
- 自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势。
- Java 7 之后不能控制是否开启自旋功能。
2.7 偏向锁
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行cas操作。
Java 6 中引入了偏向锁来做进一步优化:只有第一次使用cas 将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新cas。以后只要不发生竞争,这个对象就归该线程所有。
偏向状态
对象头格式:
|--------------------------------------------------|----------------------|
| Mark Word (32bits) | State |
|--------------------------------------------------|----------------------|
| hashcodF:\stu\资料 25 | agF:\stu\资料4 |biased_lock:0|01 | Normal |
|--------------------------------------------------|----------------------|
| thread: 23 |epoch:2| agF:\stu\资料4 |biased_lock:1|01 | Biased |
|--------------------------------------------------|----------------------|
| ptr_to_lock_record:30 |00 | Lightweight Locked |
|--------------------------------------------------|----------------------|
| ptr_to_heavyweight_monitor:30 |10 | Lightweight Locked |
|--------------------------------------------------|----------------------|
| |11 | Marked for GC |
|--------------------------------------------------|----------------------|
一个对象创建时:
- 如果开启了偏向锁(默认开启),那么对象创建后,Mark Word值为0x05即最后三位为101,这时它的thread、epoch、age都是0
- 偏向锁是默认延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数
-XX:BiasedLockingStartupDelay=0
来禁用延迟 - 如果没有开启偏向锁,那么对象创建后,markword值为0x01即为最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值。
值得注意的时,如果掉用了对象的hashCode()方法,就会禁用这个对象的偏向锁,原因是hash码存储在对象头中,而偏向锁的对象头中存储线程ID,所以在调用hashCode()方法的时候,必须需要空间存放hash值,也就只能舍弃使用偏向锁了。
撤销-调用对象hashCode()
撤销-其他线程使用对象
当其他线程使用偏向锁对象时,会将偏向锁升级为轻量级锁。
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread t1 = new Thread(()->{
synchronized (obj){
System.out.println(Thread.currentThread().getName()+":"+ClassLayout.parseInstance(obj).toPrintable());
}
synchronized (TestBiased1.class){
TestBiased1.class.notify();
}
},"t1");
Thread t2 = new Thread(()->{
synchronized (TestBiased1.class){
try {
TestBiased1.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (obj){
System.out.println(Thread.currentThread().getName()+":"+ClassLayout.parseInstance(obj).toPrintable());
}
},"t2");
t1.start();
t2.start();
}
结果:
t1:java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 40 11 1b (00000101 01000000 00010001 00011011) (454115333) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397) 12 4 (loss due to the next object alignment) Instance sizF:\stu\资料 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalt2:java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) d0 ed a9 1b (11010000 11101101 10101001 00011011) (464121296) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397) 12 4 (loss due to the next object alignment) Instance sizF:\stu\资料 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结果上不难看出当线程t2被唤醒之前,t1发现obj没有被竞争上来就是使用的偏向锁,当t1释放锁obj去唤醒了t2,t2拿到锁的时候发现obj已经被t1当做偏向锁使用过了,就将其升级为了轻量级锁。
撤销-调用wait/notify
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread t1 = new Thread(()->{
synchronized (obj){
obj.notify();
}
System.out.println(Thread.currentThread().getName()+":"+ClassLayout.parseInstance(obj).toPrintable());
},"t1");
Thread t2 = new Thread(()->{
System.out.println(Thread.currentThread().getName()+":"+ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj){
System.out.println(Thread.currentThread().getName()+":"+ClassLayout.parseInstance(obj).toPrintable());
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束");
},"t2");
t2.start();
Thread.sleep(5000);
t1.start();
}
结果:
`t2:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance sizF:\stu\资料 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalt2:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 58 f3 11 1b (01011000 11110011 00010001 00011011) (454161240)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance sizF:\stu\资料 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total结束
t1:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) da 31 b0 17 (11011010 00110001 10110000 00010111) (397423066)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance sizF:\stu\资料 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total`
批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程t1的对象仍有机会重新偏向t2,重偏向会重置对象的Thread ID
当撤销偏向锁阈值超过20次后,JVM会在给这些对象加锁时重新偏向至加锁线程。
由上面的内容我们脑子里似乎认为当有其他线程使用对象的时候偏向锁就会消失,但是事实上并不是如此。撤销偏向状态对jvm的开销是比较大的,当撤销次数达到阈值JVM就会自动调优不再进行撤销操作,转变为直接批量重偏向。
批量撤销
批量撤销的原理和批量重偏向异曲同工,阈值为40,当撤销的次数达,40次时,JVM就不会再进行重偏向,会认为偏向的有问题,直接批量的撤销这个类所有的偏向,舍弃偏向锁,之后新建的对象也是不可偏向的。
2.8 锁消除
Java 有及时编译器 JIT,会对热点代码进行优化分析(也有逃逸分析)。如果发现加锁资源不会被其他的地方访问到就会把加的锁给优化掉,也就是锁消除。
2.9 wait && notify
- Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
- BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片
- BLOCKED线程会在Owner线程释放锁时唤醒
- WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争。
wait && notify 的正确使用姿势
sleep(long n) 和wait(long n) 的区别
- sleep是Thread的方法,而wait是Object的方法。
- sleep不需要强制synchronized配合使用,而wait需要和synchronized一起使用。
- sleep在睡眠的同时不会释放对象锁,但wait在等待的时候会释放锁对象。
- 他们的状态都是TIMED_WATING
2.10 同步模式之保护性暂停
定义
Guarded Suspension ,用在一个线程等待另一个线程的执行结果
- 有一个等待结果要从一个线程到另一个线程,让他们关联同一个GuardedObject
- 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(生产者/消费者)
- JDK中,join的实现、Future的实现,采用的就是此模式
- 因为要等待另一方的结果,因此归类到同步方法
例子:
/**
* 保护性暂停
*/
public class GuardedObject {
private Object response;
public Object get() throws InterruptedException {
synchronized (this){
while (response == null){
wait();
}
return response;
}
}
//增加超时功能
public Object get(long timeout) {
synchronized (this){
long begin = System.currentTimeMillis();
long passedTime = 0;
while (response == null){
if (passedTime >= timeout){
throw new RuntimeException("等待超时...");
}
try {
wait(timeout - passedTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
passedTime = System.currentTimeMillis()-begin;
}
return response;
}
}
public void set(Object obj){
synchronized (this){
response = obj;
notifyAll();
}
}
}
class TestG{
public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
Thread t1 = new Thread(()->{
int o = (int) guardedObject.get(2000);
System.out.println(Thread.currentThread().getName()+"接收到:"+o);
},"t1");
Thread t2 = new Thread(()->{
int res = 0;
for (int i = 0; i < 1000; i++) {
if (i%2==0){
res = res+i;
}
}
guardedObject.set(res);
},"t2");
t1.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
运行结果:
Exception in thread "t1" java.lang.RuntimeException: 等待超时... at demo.GuardedObject.get(GuardedObject.java:31) at demo.TestG.lambda$main$0(GuardedObject.java:61) at java.lang.Thread.run(Thread.java:748)
join方法使用的就是保护性暂停的模式
保护性暂停例题之寄信与收信
public class PandP {
public static void main(String[] args) throws InterruptedException {
People p1 = new People();
Thread t1 = new Thread(p1);
t1.setName("收信人1");
t1.start();
Thread.sleep(2000);
Set<Integer> ids = MailBox.getIds();
for (int i = 0; i < ids.size(); i++) {
PostMan post = new PostMan(i,"内容"+i);
Thread t = new Thread(post);
t.setName("邮递员"+i);
t.start();
}
}
}
class GuardedObjectV{
private int id;
private Object response;
public GuardedObjectV(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Object get(){
synchronized (this){
while (response == null){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
public void set(Object response){
synchronized (this){
this.response = response;
notifyAll();
}
}
}
class MailBox{
private static int id = 0;
private static Map<Integer,GuardedObjectV> map = new Hashtable<>();
private static synchronized int generatedId(){
return id++;
}
public static GuardedObjectV createGuardedObj(){
GuardedObjectV go = new GuardedObjectV(generatedId());
map.put(go.getId(),go);
return go;
}
public static GuardedObjectV getObj(int id){
synchronized (MailBox.class){
GuardedObjectV remove = map.remove(id);
return remove;
}
}
public synchronized static Set<Integer> getIds(){
return map.keySet();
}
}
class People extends Thread{
@Override
public void run() {
GuardedObjectV guardedObjectV = MailBox.createGuardedObj();
Object response = guardedObjectV.get();
System.out.println(Thread.currentThread().getName()+":"+response+guardedObjectV.getId());
}
}
class PostMan extends Thread{
private int id;
private String mail;
public PostMan(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
GuardedObjectV obj = MailBox.getObj(id);
System.out.println("开始送信,内容:"+mail+" id:"+id);
obj.set(mail);
}
}
结果:
开始送信,内容:内容0 id:0 收信人1:内容00
特点:产生结果的线程和消费结果的线程一一对应。
2.11 异步模式之生产者/消费者
要点
- 与前面的保护性暂停中的GuardedObject不同,不需要产生结果和消费结果的线程一一对应
- 消费队列可以用来平衡生产和消费的线程资源
- 生产者仅负责产生结果数据,不关心数据如何处理,而消费者专心处理数据结果
- 消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
- JDK中各种阻塞队列,采用的就是这种模式
案例:
public class MessageQueue {
private int capacity;
private final LinkedList<Message> list = new LinkedList<>();
public MessageQueue(int capacity) {
this.capacity = capacity;
}
public int getCapacity() {
return capacity;
}
public Message get(){
synchronized (list){
while (list.isEmpty()){
try {
System.out.println(Thread.currentThread().getName()+":阻塞等待...");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message remove = list.remove();
System.out.println(Thread.currentThread().getName()+":拿了个东西...");
list.notifyAll();
return remove;
}
}
public void put(Message message){
synchronized (list){
while (list.size()==capacity){
try {
System.out.println(Thread.currentThread().getName()+":阻塞等待...");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":放了个东西...");
boolean add = list.add(message);
if (add){
list.notifyAll();
}
}
}
}
final class Message{
private int id;
private String value;
public Message(int id, String value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public String getValue() {
return value;
}
}
class TestMQ{
public static void main(String[] args) {
MessageQueue mq = new MessageQueue(2);
for (int i = 0; i < 3; i++) {
int id = i;
Thread t = new Thread(()-> mq.put(new Message(id,"东西")),"生产者-"+i);
t.start();
}
Thread c = new Thread(mq::get,"消费者");
c.start();
}
}
运行结果:
生产者-1:放了个东西... 生产者-0:放了个东西... 生产者-2:阻塞等待... 消费者:拿了个东西... 生产者-2:放了个东西...
2.12 Park && Unpark
基本使用
他们是LockSupport类中的方法
//暂停当前线程
LockSupport.park();
//恢复某个线程的运行
LockSupport.unpark(暂停的线程对象);
注意:
如果一个线程对象的park方法 在unpark方法之后调用,那么这个方法在调用park的时候就会被取消暂停。
例如:
public class TestParkUnpark1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":start\npark");
LockSupport.park();
System.out.println(Thread.currentThread().getName()+":resume...");
},"t1");
t1.start();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":unpark...");
LockSupport.unpark(t1);
}
}
结果:
main:unpark... t1:start park t1:resume...
其中park之后立即resume没有暂停
特点
与 Object 的 wait && notify 相比
- wait、notify 和 notifyAll 必须配合 object 一起使用,而 park 和 unpark 不用
- park && unpark 是以线程为单位来阻塞和唤醒线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程就不太精确
- park && unpark 可以先 unpark,而 wait && notify 不能先 notify
原理
每一个线程都有自己的一个Parker对象,由三部分组成_counter,_cond,_mutex,打比方:
-
线程就想一个旅人,Parker就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter就好比背包中的备用干粮(0为耗尽1为充足)
-
调用park就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果干粮充足,那么不需要停留,继续前进
-
调用unpark就好比令干粮充足
-
如果这时线程还在帐篷,就唤醒让他继续前进
-
如果线程还在运行,那么下次调用park的时候仅消耗备用干粮,不需要停留继续前进
- 因为背包空间有限,多次调用unpark仅会补充一份备用干粮
-
2.13 状态转换详解
假设有线程t
情况 1 NEW --> RUNNABLE
- 当调用t.start() 方法时,由NEW – > RUNNABLE
情况 2 RUNNABLE <–> WAITING
t线程用 synchronized(obj) 获取了对象锁后
- 调用obj.wait() 方法时,t线程从RUNNABLE --> WAITING
- 调用obj.notify() ,obj.notifyAll(),t.interrupt()时
- 竞争锁成功,t线程从WAITING --> RUNNABLE
- 竞争锁失败,t线程从WAITING – > BLOCKED
情况 3 RUNNABLE <–> WAITING
- 当线程调用t.join()方法时,当前线程从RUNNABLE --> WAITING
- 注意当前线程在t线程对象的监视器上等待
- t线程运行结束,或者调用了当前线程的interrupt()时,当前线程从WAITING --> RUNNABLE
情况 4 RUNNABLE <–>WAITING
- 当前线程调用LockSupport.park()方法会让当前线程从RUNNABLE --> WAITING
- 调用 LockSupport.unpark(目标线程) 或者调用了线程的interrupt(),会让目标线程从WAITING --> RUNNABLE
情况 5 RUNNABLE < – > TIMED_WAITING
t线程用synchronized(obj) 获取了对象锁后
- 调用obj.wait(long n) 方法时,t线程从RUNNABLE --> TIMED_WAITING
- t线程等待时间超过了n毫秒,或调用obj.notify(),obj.notifyAll(),t.interrupt()时
- 竞争锁成功,t线程从TIMED_WATING --> RUNNABLE
- 竞争锁失败,t线程从TIMED_WAITING --> BLOCKED
情况6 RUNNABLE <–> TIMED_WAITING
- 当前线程调用t.join(long n) 方法时,当前线程从RUNNABLE --> TIMED_WAITING
- 注意是当前线程在t线程对象的监视器上等待
- 当前线程等待时间超过了n毫秒,或t线程运行结束,或调用了当前线程的interrupt()时,当前线程从TIMED_WAITING --> RUNNABLE
情况7 RUNNABLE <–> TIMED_WAITING
- 当前线程调用Thread.sleep(long n),当前线程从RUNNABLE --> TIMED_WAITING
- 当前线程等待时间超过了n毫秒,当前线程从TIMED_WAITING --> RUNNABLE
情况 8 RUNNABLE <–> TIMED_WAITING
- 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis)时,当前线程从 RUNNABLE --> TIMED_WAITING
- 调用LockSupport.unpark(
目标线程
) 或者调用了线程的interrupt(),或者是等待超时,会让目标线程从TIMED_WAITING–>RUNNABLE
情况 9 RUNNABLE <–> BLOCKED
- t线程用synchronized(obj)获取了对象锁时如果竞争失败,从RUNNABLE – > BLOCKED
- 持obj锁线程的同步代码块执行完毕,会唤醒该对象上所有BLOCKED的线程重新竞争,如果其中t线程竞争成功,从BLOCKED --> RUNNABLE,其他失败的线程任然BLOCKED
情况 10 RUNNABLE <–> TERMINATED
当前线程所有代码运行完毕,进入TERMINATED
2.14 多把锁
多把不相干的锁
对于两个不相干的方法(不会对同一资源竞争)我们不期望直接锁住整个对象,那样效率太低,这时为各自准备对象进行上锁。
public class TestMultiLock {
public static void main(String[] args) {
BigRoom room = new BigRoom();
for (int i = 0; i < 5; i++) {
Thread t1 = new Thread(()->{
try {
room.study();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"s"+i);
Thread t2 = new Thread(()->{
try {
room.bed();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"b"+i);
t1.start();
t2.start();
}
}
}
class BigRoom{
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
public void study() throws InterruptedException {
synchronized (studyRoom){
System.out.println(Thread.currentThread().getName()+":我开始学习了\n...");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+":我学习结束了\n");
}
}
public void bed() throws InterruptedException {
synchronized (bedRoom){
System.out.println(Thread.currentThread().getName()+":我开始睡觉了\n...");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+":我睡觉结束了\n");
}
}
}
将锁细粒化
- 好处,可以增加并发度
- 坏处,如果一个线程同时获得多把锁,容易发生死锁。
2.15 活跃性
死锁
一个线程需要同时获得多把锁容易发生死锁。
定位死锁
- 检测死锁使用jconsole或者jps进行定位进程ID,再用jstack定位死锁
定位死锁
哲学家就餐问题
活锁
解决方法:可以增加随机的睡眠时间
饥饿
线程的优先级太低,始终得不到cpu调度执行。
2.16 ReentrantLock
相对于synchronized它具有如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与synchronized一样,都支持可重入
可重入
可重入指的是同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获得这把锁。如果不是可重入锁,那么第二次获得锁时,自己也会被锁挡住。
可打断
调用的 lockInterruptibly() 方法时,表示此时锁可被打断。
锁超时
调用 tryLock(long timeout,TimeUnit unit) ,返回boolean值,尝试获取锁失败时不会进入等待队列。
案例哲学家就餐问题
公平锁
可以通过构造方法的值设置为true获得公平锁,一般不用,降低并发度。
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个waitSet休息室,当条件不满足时进入waitSet等待
ReentrantLock 的条件变量比 synchronized 强大之处在于它是支持多个条件变量,这就好比
- synchronized 是那种不满足条件的线程都在一间休息室等信息
- 而ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按照休息室来唤醒
使用流程
- await前需要获得锁
- await执行后,会释放锁,进入conditionObject等待
- await的线程被唤醒(或打断、或超时)取重新竞争lock锁
- 竞争lock锁成功后,从await后继续执行
Condition condition = lock.newCondition();
来创建条件变量
在同步代码段中调用await()
方法来使环境变量的持有线程进入等待
在同步代码段中调用 signal()
和signalAll()
方法唤醒
3 JMM
3.1 Java 内存模型
JMM即Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等。
JMM体现在以下几个方面
- 原子性 - 保证指令不会受到线程上下文切换的影响
- 可见性 - 保证指令不会受到cpu缓存的影响
- 有序性 - 保证指令并行优化的影响
3.2 可见性
退不出的循环
@Slf4j
public class TestFor {
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while (run){
//..
}
},"t");
t.start();
Thread.sleep(2000);
log.info("run = false");
run = false;
}
}
main修改了run的值,并同步到主存,而t是从自己工作内存中的告诉缓存中读取这个变量的值,结果是旧值
解决办法
volatile(易变关键字)
volatile 可以用来修饰成员变量和静态变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。
注意:
- synchronized 和 volatile 都可以保证可见性。
- synchronized 是重量级锁,需要创建monitor,推荐使用 volatile。
3.3 可见性 && 原子性
注意:
- volatile 只能保证可见性 不能保证原子性。
小细节:
上面的例子在循环中加入System.out.println()打印语句就能保证可见性,因为println()的源码中使用了synchronized。
3.4 有序性
cpu在执行指令的时候为了提高效率会对指令进行指令重排(指令级并行)。
java层面也存在指令重排,是JIT编译器在运行时的一些优化,这种现象一般需要通过大量的测试才能复现。
可通过压力测试进行测试。
禁用指令重排:在多个线程共同访问的变量前加 volatile,在哪个变量前加就能防止哪个变量之前的代码被指令重排。
3.5 volatile 原理
1 如何保证可见性
-
写屏障 (sfence) 保证在该屏障之前的,对共享变量的改动,都同步到主存中
-
public void actor1(I_Result r){ num = 2; ready = true;//ready 是 volatile 赋值带写屏障 //写屏障 }
-
而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
-
public void actor2(I_Result r){ //读屏障 //ready 是 volatile 读取值带读屏障 if(ready){ r.r1 = num + num; } else{ r.r1 =1; } }
2 如何保证有序性
-
写屏障会确保指令重排时,不会将写屏障之前的代码排在写屏障之后
-
public void actor1(I_Result r){ num = 2; ready = true;// ready 是 volatile 赋值带写屏障 // 写屏障 }
-
读屏障会确保指令重排时,不会将读屏障之后的代码排在读屏障之前
-
public void actor2(I_Result r){ // 读屏障 // ready 是 volatile 读取值带读屏障 if(ready){ r.r1 = num + num; } else{ r.r1 =1; } }
注意:
- volatile 只能保证可见性和有序性,不能保证指令的交错。
3 double-checked locking问题
double checked 单例模式
public final class Singleton{
private Singleton(){}
private volatile static Singleton instance = null;
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
incetance = new Singleton();
}
}
}
return instance;
}
}
3.6 happens-before
happens-before 规定了对共享变量的写操作对其他线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下happens-before规则,JMM并不能保证一个线程对共享变量的写,对于其他线程对该共享变量的读可见
-
线程解锁m之前对变量的写,对于接下来对m加锁的其他线程对该变量的读可见。
static int x; static Object m = new Object(); new Thread(()->{ synchronized(m){ x = 10; } },"t1").start(); new Thread(()->{ synchronized(m){ System.out.println(x); } },"t2").start();
-
线程对 volatile 变量的写,对接下来其他线程对该变量的读可见
volatile static int x; new Thread(()->{ x = 10; },"t1").start(); new Thread(()->{ System.out.println(x); },"t2").start();
-
线程start前对变量的写,对该线程开始之后的对该变量的读是可见的。
-
线程结束前对变量的写,对其他线程得知他结束后的读可见(比如其他线程调用t1.isAlive() 或 t1.join()等待他结束)
static int x; Thread t1 = new Thread(()->{ x = 10; },"t1"); t1.start(); t1.join(); System.out.println(x);
-
线程 t1 打断 t2 (interrupt) 前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过t2.interrupted 或 t2.isInterrupted)
static int x; public static void main(String[] args){ Thread t2 = new Thread(()->{ while(true){ if(Thread.currentThread().isInterrupted()){ System.out.println(x); break; } } },"t2"); t2.start(); Thread t1 = new Thread(()->{ try{ Thread.sleep(1000); x = 10; t2.interrupt(); }catch(Exception e){ e.printStackTrace(); } },"t1"); t1.start(); while(!t2.isInterrupted()){ Thread.yield(); } System.out.println(x); }
-
对变量的默认值的写,对其他线程对该变量的读可见
-
具有传递性,如果 x hb -> y 并且 y hb -> z 那么 x hb -> z ,配合 volatile 的防指令重排,有下面例子
volatile static int x; static int y; new Thread(()->{ y = 10; x = 20; },"t1").start(); new Thread(()->{ // x=20 对 t2 可见,同时 y=10 也对 t2 可见 System.out.println(x); },"t2").start();
4 共享模型之无锁
直接一个无锁线程安全的取钱案例:
public class TestAccount {
static Account account = new AccountAto(10000);
public static void main(String[] args) throws InterruptedException {
Thread t = null;
for (int i = 0; i < 1000; i++) {
t = new Thread(()->{
account.withdraw(10);
System.out.println(Thread.currentThread().getName()+":"+account.getBalance());
});
t.start();
}
}
}
interface Account{
//获取余额
int getBalance();
//取钱
void withdraw(Integer amount);
}
class AccountAto implements Account{
//原子类型整型
private AtomicInteger balance;
public void setBalance(Integer balance) {
this.balance = new AtomicInteger(balance);
}
public AccountAto() {
}
public AccountAto(Integer balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public int getBalance() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
while (true){
int prev = balance.get();
int next = prev - amount;
//获取主存最新值和prev的值作比较,不一样就返回false
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
}
结果:
...
Thread-995:90 Thread-997:80 Thread-996:70 Thread-999:60 Thread-979:50 Thread-986:40 Thread-987:30 Thread-989:20 Thread-998:10 Thread-991:0
注意:
CAS的底层是 lock cmpxchg指令(X86架构) ,在单核CPU和多核CPU下都能保证 [比较 - 交换] 的原子性。
- 在多核的情况下,某个核执行到带lock的指令时,CPU会让总线锁住,当这个核把此指令执行完毕,再开启总线。这个过程中不会被线程调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。
CAS需要volatile的支持
获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。
它可以用来修饰成员变量和静态变量,他可以避免线程从自己的工作区中查找变量值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改,对另一个线程可见。
注意
volatile 仅仅保证共享变量的可见性,让其他线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)
CAS必须借助 volatile 才能读取到共享变量的最新值来实现 [ 比较并交换 ] 的效果
为什么无锁效率高
- 无锁的情况下,即使重试失败,线程始终高速运行没有停歇,而synchronized会
中间笔记丢失!!!!!!!
5 不可变
5.1 享元模式
5.2 final原理
加final直接复制到其他的对象栈内存之类的中 不加就访问共享内存 效率低。
6 并发工具
6.1 自定义线程池
package demo;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.TestPool")
public class TestPool {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(2, 10,(queue,task)->{
boolean offer = queue.offer(task, 1, TimeUnit.SECONDS);
log.info("延迟策略:{}",offer);
});
for (int i = 0; i < 4; i++) {
int j = i;
threadPool.excute(()-> {
log.info("Thread:{} - {}",Thread.currentThread().getName(),j);
});
}
}
}
@Slf4j(topic = "c.BlockingQueue")
class BlockingQueue<T> {
//任务队列
private LinkedList<T> queue = new LinkedList<>();
//锁
private ReentrantLock look = new ReentrantLock();
//条件变量
private Condition fullSet = look.newCondition();
private Condition emptySet = look.newCondition();
// 容量
private int capcity;
public BlockingQueue(int capcity) {
this.capcity = capcity;
}
//获取大小
public int getSize() {
look.lock();
try {
return queue.size();
} finally {
look.unlock();
}
}
// 阻塞获取
public T get() {
look.lock();
try {
while (queue.isEmpty()) {
try {
log.info("emptySet...");
emptySet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T remove = queue.remove();
log.info("queue removF:\stu\资料{}",remove);
if (remove != null) {
fullSet.signalAll();
}
return remove;
} finally {
look.unlock();
}
}
//代超时的阻塞获取
public T poll(long timeout,TimeUnit timeUnit){
look.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while (queue.isEmpty()){
try {
//判断是否超时,
if (nanos<0){
return null;
}
nanos = emptySet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T remove = queue.remove();
log.info("阻塞获取:{}",remove);
if (remove!=null){
fullSet.signalAll();
}
return remove;
} finally {
look.unlock();
}
}
//阻塞添加
public void put(T task) {
look.lock();
try {
while (queue.size() == capcity) {
try {
log.info("fullSet...");
fullSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean add = queue.add(task);
if (add) {
emptySet.signalAll();
}
} finally {
look.unlock();
}
}
//带超时时间的阻塞添加
public boolean offer(T task, long timeout, TimeUnit timeUnit){
look.lock();
try{
long nanos = timeUnit.toNanos(timeout);
while (queue.size() == capcity){
if (nanos <= 0){
return false;
} else {
try {
nanos = fullSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
log.info("阻塞添加 :{}",task);
queue.add(task);
emptySet.signalAll();
return true;
} finally {
look.unlock();
}
}
public void tryPut(RejeckPolicy<T> rejeckPolicy, T task) {
look.lock();
try {
if (queue.size() == capcity){
log.info("Thread :{} 调用拒绝策略...",Thread.currentThread().getName());
rejeckPolicy.reject(this,task);
} else {
log.info("queue add :{}",task);
queue.add(task);
}
} finally {
look.unlock();
}
}
}
@FunctionalInterface
interface RejeckPolicy<T>{
void reject(BlockingQueue<T> queue,T task);
}
@Slf4j(topic = "ThreadPool")
class ThreadPool {
//任务队列
private BlockingQueue<Runnable> taskQueue;
//线程集合
private final HashSet<Worker> workers = new HashSet<>();
//核心线程数
private int coreSize;
//拒绝策略
private RejeckPolicy<Runnable> rejeckPolicy;
public ThreadPool(int coreSize, int queueCapcity,RejeckPolicy<Runnable> rejeckPolicy) {
this.coreSize = coreSize;
taskQueue = new BlockingQueue<>(queueCapcity);
log.info("coreSizF:\stu\资料{},queueCapcity:{}", coreSize, queueCapcity);
this.rejeckPolicy = rejeckPolicy;
}
public void excute(Runnable task) {
synchronized (workers) {
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.info("添加线程 :{}", worker);
workers.add(worker);
log.info("启动线程 :{}",worker);
worker.start();
} else {
//taskQueue.put(task);死等
//权力下放
log.info("Thread:{} 权力下放...",Thread.currentThread().getName());
taskQueue.tryPut(rejeckPolicy,task);
}
}
}
class Worker extends Thread {
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
//执行任务
//1 当task 不为空,执行任务
//2 当task 执行完毕,再接着从任务队列获取任务执行
while (task != null || (task = taskQueue.poll(1,TimeUnit.SECONDS)) != null) {
try {
log.info("Thread:{} is running a task",Thread.currentThread().getName());
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
synchronized (workers) {
log.info("移除线程:{}",this);
boolean remove = workers.remove(this);
log.debug("是否移除成功:{}",remove);
}
}
}
}
[2020-10-27 16:24:24,022 下午]:INFO demo.ThreadPool.(TestPool.java:187)coreSizF:\stu\资料2,queueCapcity:10
[2020-10-27 16:24:24,037 下午]:INFO demo.ThreadPool.excute(TestPool.java:195)添加线程 :Thread[Thread-0,5,main]
[2020-10-27 16:24:24,037 下午]:INFO demo.ThreadPool.excute(TestPool.java:197)启动线程 :Thread[Thread-0,5,main]
[2020-10-27 16:24:24,038 下午]:INFO demo.ThreadPool.excute(TestPool.java:195)添加线程 :Thread[Thread-1,5,main]
[2020-10-27 16:24:24,038 下午]:INFO demo.ThreadPool W o r k e r . r u n ( T e s t P o o l . j a v a : 223 ) T h r e a d : T h r e a d − 0 i s r u n n i n g a t a s k [ 2020 − 10 − 2716 : 24 : 24 , 038 下午 ] : I N F O d e m o . T h r e a d P o o l . e x c u t e ( T e s t P o o l . j a v a : 197 ) 启动线程: T h r e a d [ T h r e a d − 1 , 5 , m a i n ] [ 2020 − 10 − 2716 : 24 : 24 , 038 下午 ] : I N F O d e m o . T e s t P o o l . l a m b d a Worker.run(TestPool.java:223)Thread:Thread-0 is running a task [2020-10-27 16:24:24,038 下午]:INFO demo.ThreadPool.excute(TestPool.java:197)启动线程 :Thread[Thread-1,5,main] [2020-10-27 16:24:24,038 下午]:INFO demo.TestPool.lambda Worker.run(TestPool.java:223)Thread:Thread−0isrunningatask[2020−10−2716:24:24,038下午]:INFOdemo.ThreadPool.excute(TestPool.java:197)启动线程:Thread[Thread−1,5,main][2020−10−2716:24:24,038下午]:INFOdemo.TestPool.lambdamain 1 ( T e s t P o o l . j a v a : 24 ) T h r e a d : T h r e a d − 0 − 0 [ 2020 − 10 − 2716 : 24 : 24 , 040 下午 ] : I N F O d e m o . T h r e a d P o o l . e x c u t e ( T e s t P o o l . j a v a : 202 ) T h r e a d : m a i n 权力下放 . . . [ 2020 − 10 − 2716 : 24 : 24 , 040 下午 ] : I N F O d e m o . T h r e a d P o o l 1(TestPool.java:24)Thread:Thread-0 - 0 [2020-10-27 16:24:24,040 下午]:INFO demo.ThreadPool.excute(TestPool.java:202)Thread:main 权力下放... [2020-10-27 16:24:24,040 下午]:INFO demo.ThreadPool 1(TestPool.java:24)Thread:Thread−0−0[2020−10−2716:24:24,040下午]:INFOdemo.ThreadPool.excute(TestPool.java:202)Thread:main权力下放...[2020−10−2716:24:24,040下午]:INFOdemo.ThreadPoolWorker.run(TestPool.java:223)Thread:Thread-1 is running a task
[2020-10-27 16:24:24,041 下午]:INFO demo.TestPool.lambda$main 1 ( T e s t P o o l . j a v a : 24 ) T h r e a d : T h r e a d − 1 − 1 [ 2020 − 10 − 2716 : 24 : 24 , 041 下午 ] : I N F O d e m o . B l o c k i n g Q u e u e . t r y P u t ( T e s t P o o l . j a v a : 159 ) q u e u e a d d : d e m o . T e s t P o o l 1(TestPool.java:24)Thread:Thread-1 - 1 [2020-10-27 16:24:24,041 下午]:INFO demo.BlockingQueue.tryPut(TestPool.java:159)queue add :demo.TestPool 1(TestPool.java:24)Thread:Thread−1−1[2020−10−2716:24:24,041下午]:INFOdemo.BlockingQueue.tryPut(TestPool.java:159)queueadd:demo.TestPool$Lambda 2 / 1327763628 @ 61 e 4705 b [ 2020 − 10 − 2716 : 24 : 24 , 042 下午 ] : I N F O d e m o . T h r e a d P o o l . e x c u t e ( T e s t P o o l . j a v a : 202 ) T h r e a d : m a i n 权力下放 . . . [ 2020 − 10 − 2716 : 24 : 24 , 042 下午 ] : I N F O d e m o . B l o c k i n g Q u e u e . p o l l ( T e s t P o o l . j a v a : 95 ) 阻塞获取 : d e m o . T e s t P o o l 2/1327763628@61e4705b [2020-10-27 16:24:24,042 下午]:INFO demo.ThreadPool.excute(TestPool.java:202)Thread:main 权力下放... [2020-10-27 16:24:24,042 下午]:INFO demo.BlockingQueue.poll(TestPool.java:95)阻塞获取:demo.TestPool 2/1327763628@61e4705b[2020−10−2716:24:24,042下午]:INFOdemo.ThreadPool.excute(TestPool.java:202)Thread:main权力下放...[2020−10−2716:24:24,042下午]:INFOdemo.BlockingQueue.poll(TestPool.java:95)阻塞获取:demo.TestPool$Lambda 2 / 1327763628 @ 61 e 4705 b [ 2020 − 10 − 2716 : 24 : 24 , 042 下午 ] : I N F O d e m o . T h r e a d P o o l 2/1327763628@61e4705b [2020-10-27 16:24:24,042 下午]:INFO demo.ThreadPool 2/1327763628@61e4705b[2020−10−2716:24:24,042下午]:INFOdemo.ThreadPoolWorker.run(TestPool.java:223)Thread:Thread-0 is running a task
[2020-10-27 16:24:24,042 下午]:INFO demo.BlockingQueue.tryPut(TestPool.java:159)queue add :demo.TestPoolKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2/1327763628@50…Lambda 2 / 1327763628 @ 50134894 [ 2020 − 10 − 2716 : 24 : 24 , 044 下午 ] : I N F O d e m o . T h r e a d P o o l 2/1327763628@50134894 [2020-10-27 16:24:24,044 下午]:INFO demo.ThreadPool 2/1327763628@50134894[2020−10−2716:24:24,044下午]:INFOdemo.ThreadPoolWorker.run(TestPool.java:223)Thread:Thread-0 is running a task
[2020-10-27 16:24:24,045 下午]:INFO demo.TestPool.lambda$main 1 ( T e s t P o o l . j a v a : 24 ) T h r e a d : T h r e a d − 0 − 3 [ 2020 − 10 − 2716 : 24 : 25 , 056 下午 ] : I N F O d e m o . T h r e a d P o o l 1(TestPool.java:24)Thread:Thread-0 - 3 [2020-10-27 16:24:25,056 下午]:INFO demo.ThreadPool 1(TestPool.java:24)Thread:Thread−0−3[2020−10−2716:24:25,056下午]:INFOdemo.ThreadPoolWorker.run(TestPool.java:232)移除线程:Thread[Thread-0,5,main]
[2020-10-27 16:24:25,056 下午]:DEBUG demo.ThreadPool W o r k e r . r u n ( T e s t P o o l . j a v a : 234 ) 是否移除成功: t r u e [ 2020 − 10 − 2716 : 24 : 25 , 056 下午 ] : I N F O d e m o . T h r e a d P o o l Worker.run(TestPool.java:234)是否移除成功:true [2020-10-27 16:24:25,056 下午]:INFO demo.ThreadPool Worker.run(TestPool.java:234)是否移除成功:true[2020−10−2716:24:25,056下午]:INFOdemo.ThreadPoolWorker.run(TestPool.java:232)移除线程:Thread[Thread-1,5,main]
[2020-10-27 16:24:25,059 下午]:DEBUG demo.ThreadPool$Worker.run(TestPool.java:234)是否移除成功:trueProcess finished with exit code 0
6.2 ThreadPoolExcutor
1) 线程池状态
ThreadPoolExcutor 使用int的高三位来表示线程池状态,低29位表示线程数量
状态名 | 高3位 | 接收新任务 | 处理阻塞队列任务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | Y | Y | |
SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余任务 |
STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列任务 |
TIDYING | 010 | - | - | 任务全执行完毕,活动线程为0即将进入终结 |
TERMINATED | 011 | - | - | 终结状态 |
从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,这样就可以用一次cas原子操作进行赋值
// c 为旧值,ctlOf返回结果为新值
ctl.compareAndSet(c,ctlOf(targetState,workerCountOf(c)));
//rs 为高 3 为代表线程池状态,wc 为低 29 位代表线程个数,ctl 是合并他们
private static int ctlOf(int rs,int wc) { return rs|wc;}
注意:用位或运算合并
2) 构造方法
public ThreadPoolExcutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit uint,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 核心线程数目(最后保留的线程数)
- maximumPoolSize 最大线程数目
- keepAliveTime 生存时间 - 针对 救急线程
- uint 时间单位 - 针对救急线程
- workQueue 阻塞队列
- threadFactory 线程工厂 - 可以为线程创建时起个好名字
- handler 拒绝策略
注意:
如果选择了无界的队列,那么救急线程就不会被创建。
如果达到maxmumPoolSize任然有任务,这时就会执行拒绝策略
- 当高峰期过去后,超过corePoolSize的救急线程如果一段时间没有任务做,需要节省资源,这个时间有keepAliveTime和uint控制
根据这个构造方法,JDK Excutors类中提供了众多工厂方法来创建各种用途的线程池。
3) newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,nThreads,
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点
- 核心线程数 == 最大线程数 (没有急救线程被创建),因此也无需超时时间
- 阻塞队列是无界的,可以放任意数量的任务
评论
适用于任务量已知,相对耗时的任务
4)newCachedThreadPool
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,
60L,TimeUint.SECONDS,
new SynchronousQueue<Runnable>());
}
特点
- 核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲存活时间是60s,意味着
- 全部都是救急线程(60s后可以回收)
- 救急线程可以无限创建
- 队列采用 SynchronousQueue 实现的特点是 没有容量,没有线程来取的话是放不进去的,(一手交钱一手交货)
注意
整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1分钟后释放线程。
适合任务数比较密集,但每个任务时间比较短的情况。
5)newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(){
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1,1,
0L,TimeUnit.MILLSECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用场景:
希望多个任务排队执行。线程数固定为1,任务数多于1时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
区别:
- 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作。
- Executors.newSingleThreadExecutor()线程个数始终为1,不能修改
- FinalizableDelegatedExecutorService应用的是装饰器模式,只对外暴露了ExecutorService接口,因此不能调用ThreadPoolExecutor中特有的方法
- Executors.newFixedThreadPool(1)初始值为1,以后还可以修改。
- 对外暴露的是ThreadPoolExecutor对象,可以强转后调用setCorePoolSize等方法进行修改
6) 提交任务
7)关闭线程
shutdown
/*
线程池状态变为SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
shutdownNow
/*
线程池状态变为
*/
List<Runnable> shutdownNow();
* 模式之 Worker Thread
8)任务调度线程池
在 任务调度线程池功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer的优点在于简单易用,但由于所有任务都是由一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务执行,前一个任务的延迟或异常都将会影响到之后的任务。
Timer
@Slf4j
public class TestTimer {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@SneakyThrows
@Override
public void run() {
log.info("task 1");
Thread.sleep(2000);
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
log.info("task 2");
}
};
log.info("start..");
timer.schedule(task1,1000);
timer.schedule(task2,1000);
}
}
[2020-10-28 17:21:30,643 下午]:INFO test.TestTimer.main(TestTimer.java:29)start…
[2020-10-28 17:21:31,654 下午]:INFO test.TestTimer$1.run(TestTimer.java:18)task 1
[2020-10-28 17:21:33,667 下午]:INFO test.TestTimer$2.run(TestTimer.java:25)task 2
ScheduledThreadPool
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.info("start..");
pool.schedule(()->{
log.info("task 1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},1, TimeUnit.SECONDS);
pool.schedule(()->{
log.info("task 2");
},1, TimeUnit.SECONDS);
pool.shutdown();
}
[2020-10-28 17:33:08,141 下午]:INFO test.TestTimer.main(TestTimer.java:18)start…
[2020-10-28 17:33:09,265 下午]:INFO test.TestTimer.lambda$main 1 ( T e s t T i m e r . j a v a : 28 ) t a s k 2 [ 2020 − 10 − 2817 : 33 : 09 , 265 下午 ] : I N F O t e s t . T e s t T i m e r . l a m b d a 1(TestTimer.java:28)task 2 [2020-10-28 17:33:09,265 下午]:INFO test.TestTimer.lambda 1(TestTimer.java:28)task2[2020−10−2817:33:09,265下午]:INFOtest.TestTimer.lambdamain$0(TestTimer.java:20)task 1
两个方法
1)scheduleWithFixedDelay
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
特点:定时计时会被方法执行时间抵消。
2)scheduleWithFixedDelay
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
特点:定时计时不会被方法执行时间抵消。
注意
ExecutorService pool = Executors.newFixedThreadPool(1); Future<Boolean> f = pool.submit(() -> { int i = 1 / 0; return true; }); log.debug("结果:{}",f.get());
Exception in thread “main” java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at test.TestThreadPoolExecutor.testSubmit(TestThreadPoolExecutor.java:67)
at test.TestThreadPoolExecutor.main(TestThreadPoolExecutor.java:41)
Caused by: java.lang.ArithmeticException: / by zero
at test.TestThreadPoolExecutor.lambda$testSubmit 5 ( T e s t T h r e a d P o o l E x e c u t o r . j a v a : 63 ) a t j a v a . u t i l . c o n c u r r e n t . F u t u r e T a s k . r u n ( F u t u r e T a s k . j a v a : 266 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r . r u n W o r k e r ( T h r e a d P o o l E x e c u t o r . j a v a : 1149 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r 5(TestThreadPoolExecutor.java:63) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor 5(TestThreadPoolExecutor.java:63)atjava.util.concurrent.FutureTask.run(FutureTask.java:266)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)atjava.util.concurrent.ThreadPoolExecutorWorker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)Futrue检测到异常会将异常作为结果返回。
定时任务
自己按照上面的方法操作一波 😂
9) Tomcat 线程池
Tomcat 在哪里用到了线程池呢
- LimitLatch 用来限流,可以控制最大连接数,类似JUC中的Semaphore后面再讲
- Acceptor 只负责【接收新的socket连接】
- Poller只负责监听socket channel 是否有【可读的I/O事件】
- 一旦可读,封装一个任务对象(socketProcessor),提交给Executor线程池处理
- Executor线程池中的工作线程最终负责【处理请求】
Tomcat线程池扩展了ThreadPoolExecutor,行为稍有不同
- 如果总线程数达到 maximumPoolSize
- 这时不会立刻抛出 RejectedExecutionException 异常
- 而是再次尝试将任务放入任务队列,如果还失败,才抛出异常
配置
Connector配置
配置项 | 默认值 | 说明 |
---|---|---|
acceptorThreadCount | 1 | acceptor线程数量 |
pollerThreadCount | 1 | poller 线程数量 |
minSpareThreads | 10 | 核心线程数,即corePoolSize |
maxThreads | 200 | 最大线程数,即maximumPoolSize |
executor | - | Executor名称,用来引用下面的Executor |
Executor配置
配置项 | 默认值 | 说明 |
---|---|---|
threadPriority | 5 | 线程优先级 |
daemon | true | 是否守护线程 |
minSpareThreads | 25 | 核心线程数,即corePoolSize |
maxThreads | 200 | 最大线程数,即maximumPoolSize |
maxIdleTime | 60000 | 线程生存时间,单位ms,默认一分钟 |
maxQueueSize | Integer.MAX_VALUE | 队列长度 |
prestartminSpareThreads | false | 核心线程是否在服务器启动时启动 |
6.3 Fork/Join
1)概念
Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的cpu密集型运算
所谓任务拆分,是将一个大的任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,如归并排序、斐波那契数列、都可以用分治思想求解。
Fork/Join 在分治的基础上加入多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率
Fork/Join 默认会创建与cpu核心数大小相同的线程池
2)使用
提交给Fork/Join线程池的任务需要继承RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下面定义了一个对1~n之间的整数求和的任务
@Slf4j
public class TestForkJoin {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
Integer invoke = pool.invoke(new MyTask(5));
log.debug("结果:{}",invoke);
}
}
@Slf4j
class MyTask extends RecursiveTask<Integer>{
private int n;
public MyTask(int n) {
this.n = n;
}
@Override
public String toString() {
return "MyTask{" +
"n=" + n +
'}';
}
@Override
protected Integer compute() {
if (n ==1){
log.info("终止..n={}",n);
return n;
}
MyTask myTask = new MyTask(n -1);
log.info("对象创建..{}",myTask);
log.info("拆分..");
myTask.fork();
log.info("合并..");
Integer join = myTask.join() + n;
log.info("join() {} + {} = {}",n,myTask,join);
return join;
}
}
[2020-10-28 21:13:38,954 下午]:INFO test.MyTask.compute(TestForkJoin.java:41)对象创建…MyTask{n=4}
[2020-10-28 21:13:39,079 下午]:INFO test.MyTask.compute(TestForkJoin.java:42)拆分…
[2020-10-28 21:13:39,082 下午]:INFO test.MyTask.compute(TestForkJoin.java:44)合并…
[2020-10-28 21:13:39,082 下午]:INFO test.MyTask.compute(TestForkJoin.java:41)对象创建…MyTask{n=3}
[2020-10-28 21:13:39,083 下午]:INFO test.MyTask.compute(TestForkJoin.java:42)拆分…
[2020-10-28 21:13:39,083 下午]:INFO test.MyTask.compute(TestForkJoin.java:44)合并…
[2020-10-28 21:13:39,083 下午]:INFO test.MyTask.compute(TestForkJoin.java:41)对象创建…MyTask{n=2}
[2020-10-28 21:13:39,084 下午]:INFO test.MyTask.compute(TestForkJoin.java:42)拆分…
[2020-10-28 21:13:39,084 下午]:INFO test.MyTask.compute(TestForkJoin.java:44)合并…
[2020-10-28 21:13:39,084 下午]:INFO test.MyTask.compute(TestForkJoin.java:41)对象创建…MyTask{n=1}
[2020-10-28 21:13:39,085 下午]:INFO test.MyTask.compute(TestForkJoin.java:42)拆分…
[2020-10-28 21:13:39,085 下午]:INFO test.MyTask.compute(TestForkJoin.java:44)合并…
[2020-10-28 21:13:39,085 下午]:INFO test.MyTask.compute(TestForkJoin.java:37)终止…n=1
[2020-10-28 21:13:39,086 下午]:INFO test.MyTask.compute(TestForkJoin.java:46)join() 2 + MyTask{n=1} = 3
[2020-10-28 21:13:39,086 下午]:INFO test.MyTask.compute(TestForkJoin.java:46)join() 3 + MyTask{n=2} = 6
[2020-10-28 21:13:39,087 下午]:INFO test.MyTask.compute(TestForkJoin.java:46)join() 4 + MyTask{n=3} = 10
[2020-10-28 21:13:39,089 下午]:INFO test.MyTask.compute(TestForkJoin.java:46)join() 5 + MyTask{n=4} = 15
[2020-10-28 21:13:39,089 下午]:DEBUG test.TestForkJoin.main(TestForkJoin.java:14)结果:15Process finished with exit code 0
7 JUC 工具类
7.1 AQS原理
全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步工具的框架
特点
- 用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
- getState - 获取 state 状态
- setState - 设置 state 状态
- compareAdnSetState - cas 机制设置 state 状态
- 独占模式是只要一个线程能够访问资源,而共享模式可以允许多个线程访问资源
- 提供了基于FIFO的等待队列,类似于Monitor的EntityList
- 条件变量来实现等待,唤醒机制,支持多个条件变量,类似于Monitor 的WaitSet
子类主要实现这样一些方法(默认抛出UnsupportedOperationExcetion)
- tryAcquire
- tryRelease
- tryAcquireShared
- tryReleaseShared
- isHeldExclusively
获取锁的姿势
//如果获取锁失败
if(!tryAcquire(arg)){
//入队,可以选择阻塞但前线程 park unpark
}
释放锁的姿势
//如果释放锁成功
if(tryRelease(arg)){
//让阻塞线程恢复运行
}
自定义锁
@Slf4j
public class MyAqs {
public static void main(String[] args) {
MyLock look = new MyLock();
new Thread(()->{
look.lock();
try {
log.info("{} - start..",Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
log.info("{} - unlooking..",Thread.currentThread().getName());
look.unlock();
}
},"t1").start();
new Thread(()->{
look.lock();
try {
log.info("{} - start..",Thread.currentThread().getName());
}finally {
log.info("{} - unlooking..",Thread.currentThread().getName());
look.unlock();
}
},"t1").start();
}
}
//不可重入锁
class MyLock implements Lock {
//独占锁
static class MySync extends AbstractQueuedLongSynchronizer{
@Override //尝试获得锁
protected boolean tryAcquire(long arg) {
if (compareAndSetState(0, 1)){
//加上锁,并设置 owner 为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override //尝试释放锁
protected boolean tryRelease(long arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override //是否持有独占锁
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private MySync sync = new MySync();
/**
* 加锁
*/
@Override
public void lock() {
sync.acquire(1);
}
/**
* 加锁,可打断
* @throws InterruptedException
*/
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
/**
* 尝试加锁(一次)
* @return
*/
@Override
public boolean tryLock() {
return sync.tryRelease(1);
}
/**
*尝试加锁,带超时时间
* @param time
* @param unit
* @return
* @throws InterruptedException
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
/**
*解锁
*/
@Override
public void unlock() {
sync.release(1);
}
/**
* 创建条件变量
* @return
*/
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
[2020-10-29 19:26:48,042 下午]:INFO demo.MyAqs.lambda$main$0(MyAqs.java:18)t1 - start.. [2020-10-29 19:26:50,079 下午]:INFO demo.MyAqs.lambda$main$0(MyAqs.java:25)t1 - unlooking.. [2020-10-29 19:26:50,080 下午]:INFO demo.MyAqs.lambda$main$1(MyAqs.java:33)t1 - start.. [2020-10-29 19:26:50,082 下午]:INFO demo.MyAqs.lambda$main$1(MyAqs.java:35)t1 - unlooking..
7.2 ReentrantLock 原理
1 非公平锁的实现原理
加锁解锁流程
先从构造器开始看,默认为非公平锁实现
public ReentrantLock() {
sync = new NonfairSync();
}
NonfairSync 继承自AQS
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
没有竞争时
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
第一个竞争出现时
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryRelease()
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
2 可重入锁原理
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//继承自Sync的方法 放在此处方便阅读
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程对象 current
final Thread current = Thread.currentThread();
//获取锁的状态(0没有锁,1有锁)
int c = getState();
if (c == 0) {
//没有锁就cas上锁
if (compareAndSetState(0, acquires)) {
//上锁成功将锁的拥有者设置成当前线程对象
setExclusiveOwnerThread(current);
//上锁成功返回true
return true;
}
}
//如果已经获得了锁 线程还是当前线程,表示发生了锁重入
else if (current == getExclusiveOwnerThread()) {
//state++
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//继承自Sync的方法 放在此处方便阅读
protected final boolean tryRelease(int releases) {
//state--
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
//调用这个方法的对象线程不是锁的拥有线程抛异常
throw new IllegalMonitorStateException();
boolean free = false;
//支持锁重入,只有state减为0,才释放成功
if (c == 0) {
//设置空闲状态为true
free = true;
//设置锁的拥有线程为null,释放成功
setExclusiveOwnerThread(null);
}
//设置当前的加锁的状态,加了多少次
setState(c);
//返回当前加锁的状态
return free;
}
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
3 可打断锁原理
不可打断模式
private final boolean parkAndCheckInterrupt() {
//如果打断标记已经是true,则 park 会失效
LockSupport.park(this);
//interrupted 会清除打断标记
return Thread.interrupted();
}
//没办法获得锁的时候进入acquireQueued尝试,尝试失败就park
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
//还是需要获得锁后,才能返回打断标记
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果因为 interrupt 被唤醒,返回打断状态为 true
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//上面的结果返回到 acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//结果为true 执行 selfInterrupt()方法
selfInterrupt();
}
static void selfInterrupt() {
//重新产生一次中断
Thread.currentThread().interrupt();
}
所以只有在获得到锁之后才能得知过程期间有人打断我了。
可打断模式
//获得锁时可以被打断
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//如果没有获得锁进入doAcquireInterruptibly(arg);
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//尝试获得锁失败后,进入doAcquireInterruptibly(int arg)方法可打断的尝试获取锁
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
//可打断的获得锁流程
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//在 park 的过程中如果被 interrupt 会进入这里
//这时候直接抛出异常,不会再次进入循环去尝试获得锁
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4 公平锁的实现原理
对比之前的非公平锁
//继承自Sync的方法 放在此处方便阅读
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程对象 current
final Thread current = Thread.currentThread();
//获取锁的状态(0没有锁,1有锁)
int c = getState();
if (c == 0) {
//尝试用cas获得,这里体现了非公平性:不去检查AQS队列(这里体现非公平锁)
if (compareAndSetState(0, acquires)) {
//上锁成功将锁的拥有者设置成当前线程对象
setExclusiveOwnerThread(current);
//上锁成功返回true
return true;
}
}
//如果已经获得了锁 线程还是当前线程,表示发生了锁重入
else if (current == getExclusiveOwnerThread()) {
//state++
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//与非公平锁主要区别在于tryAcquire方法的实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//先检查AQS队列中是否有前驱节点,没有采取竞争
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//AQS继承过来的方法,放标阅读,放在此处
//判断是否有前驱
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
// h != t 时表示队列中有Node
return h != t &&
(
//(s =h,next()) == null 表示队列中还没有老二
(s = h.next) == null ||
// 或者队列中老二线程不是此线程
s.thread != Thread.currentThread()
);
}
5 条件变量实现原理
每一个条件变量其实就对应着一个等待队列,其实现类是ConditionObject
await流程
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//将线程加入到条件变量的双向链表里去 ①
Node node = addConditionWaiter();
//将节点上面所有的锁全释放掉
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
// ①
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 创建一个节点 关联当前线程 Node.CONDITION:-2
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
//如果是第一个,设置firstWaiter为自己
firstWaiter = node;
else
//如果不是第一个,就加到尾部
t.nextWaiter = node;
//lastWaiter指向这最后一个Node
lastWaiter = node;
return node;
}
// ②
//不直接Release要fullRelease,获取锁重入的次数记录在saveState中,然后传递参数给release达到全部释放的效果
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
// ③
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
signal流程
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 总是去调队首的元素
Node first = firstWaiter;
//如果队首不是null就调用doSigned(first)
if (first != null)
doSignal(first);
}
//
private void doSignal(Node first) {
do {
//从条件变量的链表中断开
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//执行transferForSignal将Node加入AQS队尾
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
7.3 读写锁
1 ReentrantReadWriteLock
当读操作远大于写操作时,这时候使用**读写锁
让读-读
**可以并发,提高性能。
类似于数据库中的select … from … lock in share mode
提供一个**数据容器类
**内部分别使用读锁保护 read() 方法,写锁保护数据的 write() 方法。
测试
DataContainer data = new DataContainer(1);
new Thread(data::read,"t1").start();
new Thread(data::read,"t2").start();
结果:
[2020-10-30 11:02:40,654 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:31)获取读锁..
[2020-10-30 11:02:40,654 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:31)获取读锁..
[2020-10-30 11:02:40,657 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:34)读取..
[2020-10-30 11:02:40,658 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:34)读取..
[2020-10-30 11:02:42,661 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:39)释放读锁..
[2020-10-30 11:02:42,661 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:39)释放读锁..
结论:
读写与读锁之间可以并发。
DataContainer data = new DataContainer(1);
new Thread(data::read,"t1").start();
Thread.sleep(100);
new Thread(data::write,"t2").start();
结果:
[2020-10-30 11:04:43,224 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:32)获取读锁..
[2020-10-30 11:04:43,227 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:35)读取..
[2020-10-30 11:04:43,336 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:47)获取写锁..
[2020-10-30 11:04:45,231 上午]:INFO test.DataContainer.read(TestReadWriteLock.java:40)释放读锁..
[2020-10-30 11:04:45,231 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:50)写入..
[2020-10-30 11:04:45,231 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:52)释放写锁..
结论:
读锁和写锁之间会竞争锁,竞争失败会等待。
DataContainer data = new DataContainer(1);
new Thread(data::write,"t1").start();
new Thread(data::write,"t2").start();
结果:
[2020-10-30 11:06:36,080 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:47)获取写锁..
[2020-10-30 11:06:36,080 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:47)获取写锁..
[2020-10-30 11:06:36,082 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:50)写入..
[2020-10-30 11:06:36,083 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:52)释放写锁..
[2020-10-30 11:06:36,083 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:50)写入..
[2020-10-30 11:06:36,083 上午]:INFO test.DataContainer.write(TestReadWriteLock.java:52)释放写锁..
结论:
写锁和写锁之间会竞争,竞争失败会等待。
总结:读-读
可以并发,读-写
和写-写
是互斥的。
注意
-
读锁不支持条件变量
-
重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock r = rw.readLock(); ReentrantReadWriteLock.WriteLock w = rw.writeLock(); //演示重入时升级不支持 log.info("获取读锁.."); r.lock(); try { log.info("获取写锁.."); w.lock(); try { log.debug("---test---"); } finally { log.info("释放写锁.."); w.unlock(); } } finally { log.info("释放读锁.."); r.unlock(); } [2020-10-30 11:17:41,796 上午]:INFO test.TestReadWriteLock.main(TestReadWriteLock.java:18)获取读锁.. [2020-10-30 11:17:41,798 上午]:INFO test.TestReadWriteLock.main(TestReadWriteLock.java:21)获取写锁..
-
重入时降级支持:即持有写锁的情况下或获取读锁
2 ReentrantReadWriteLock原理
读写锁用的是同一个Sycn同步器,因此等待队列,state等也是同一个。
WriteLock
lock流程
public void lock() {
sync.acquire(1);
}
//
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
//获取锁的状态 高16位表示读锁,低16位表示写锁
int c = getState();
// 读取写锁的状态
int w = exclusiveCount(c);
if (c != 0) {
// (NotF:\stu\资料 if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
//w == 0 说明是读锁,现在要加写锁,互斥,所以返回false 加锁失败
return false;
// 重入次数太多,多到超过最大值就抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
// 发生重入时 state+1
setState(c + acquires);
return true;
}
// 这里说明没加锁
//writerShouldBlock()非公平锁返回false
//compareAndSetState(c, c + acquires)写锁+1
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//将线程对象设置为owner
setExclusiveOwnerThread(current);
return true;
}
unlock流程
public void unlock() {
sync.release(1);
}
// 同步器的release方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 读写锁中写锁对tryRelease的是实现
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
// 查看写锁状态有没有减成0,没有的话说明有锁重入
boolean free = exclusiveCount(nextc) == 0;
if (free)
// 解锁成功把锁的持有者设置成null
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 返回1表示成功
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 被锁住的线程在这里park住的,解锁从这继续运行
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 去竞争锁
protected final int tryAcquireShared(int unused) {
//..
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//..
return 1;
}
return fullTryAcquireShared(current);
}
ReadLock
public void lock() {
// 调用同步器的acquireShared方法
sync.acquireShared(1);
}
//
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//
tryAcquireShared返回值表示
- -1表示失败
- 0表示成功,但后续节点不会继续唤醒
- 正数表示成功,而且数值是还有几个后续节点需要唤醒,读写锁返回1
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
// 获得状态
int c = getState();
// 查看有没有加写锁 && 看加写锁的是不是他自己
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
// 有写锁/上写锁的不是自己 -> 加锁失败
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
// doAcquireShared 加读锁失败后执行
private void doAcquireShared(int arg) {
// 创建节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 看一下有没有前驱节点
final Node p = node.predecessor();
if (p == head) {
// 前驱节点是头 说明本节点有资格争夺锁,再次tryAcquireShared(arg)争夺锁
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3 StampedLock
该类自JDK8加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用
加解读锁
long stamp = lock.readLock();
lock.unlockRead(stamp);
加解写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
乐观读,StampedLock 支持tryOptimisticRead()方法(乐观读),读取完毕后需要做一次**戳校验
如果戳校验
**通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没有通过,需要重新获取读锁,保证数据安全。
long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
// 锁升级
}
public class TestDataContainerStamped {
public static void main(String[] args) {
DataContainerStamped d = new DataContainerStamped(1);
new Thread(()->{
try {
int read = d.read();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()-> d.write(2),"t2").start();
}
}
@Slf4j
class DataContainerStamped{
private int data;
private final StampedLock lock = new StampedLock();
public DataContainerStamped(int data) {
this.data = data;
}
public int read() throws InterruptedException {
long stamp = lock.tryOptimisticRead();
log.info("读锁乐观加锁 - {}",stamp);
Thread.sleep(1000);
if (lock.validate(stamp)){
log.info("读完成 - {}",stamp);
return data;
}
log.info("读失败 - {}",stamp);
// 乐观锁失败上读锁
try {
log.info("乐观锁失败,上读锁");
stamp = lock.readLock();
Thread.sleep(1000);
return data;
} finally {
log.info("读锁解锁 - {}",stamp);
lock.unlockRead(stamp);
}
}
public void write(int newData){
long stamp = lock.writeLock();
log.info("写锁加锁 stamp - {}",stamp);
try {
Thread.sleep(1000);
this.data = newData;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.info("写锁解锁 stamp - {}",stamp);
lock.unlockWrite(stamp);
}
}
}
[2020-10-30 17:47:58,936 下午]:INFO demo.DataContainerStamped.read(TestDataContainerStamped.java:36)读锁乐观加锁 - 256 [2020-10-30 17:47:58,936 下午]:INFO demo.DataContainerStamped.write(TestDataContainerStamped.java:59)写锁加锁 stamp - 384 [2020-10-30 17:47:59,950 下午]:INFO demo.DataContainerStamped.read(TestDataContainerStamped.java:44)读失败 - 256 [2020-10-30 17:47:59,950 下午]:INFO demo.DataContainerStamped.write(TestDataContainerStamped.java:66)写锁解锁 stamp - 384 [2020-10-30 17:47:59,950 下午]:INFO demo.DataContainerStamped.read(TestDataContainerStamped.java:47)乐观锁失败,上读锁 [2020-10-30 17:48:00,961 下午]:INFO demo.DataContainerStamped.read(TestDataContainerStamped.java:52)读锁解锁 - 513
7.4 Semaphore
信号量,用来限制能通是访问共享资源的线程上限。
@Slf4j
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5,true);
for (int i = 0; i < 10; i++) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
log.info("running...");
try {
Thread.sleep(1000);
log.info("end...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
[2020-10-30 18:07:43,876 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:43,876 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:43,876 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:43,876 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:43,876 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:44,884 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:44,884 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:44,884 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:44,884 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:44,884 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:44,887 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:44,886 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:44,886 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:44,886 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:44,887 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:20)running... [2020-10-30 18:07:45,901 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:45,901 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:45,901 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:45,901 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end... [2020-10-30 18:07:45,901 下午]:INFO test.TestSemaphore.lambda$main$0(TestSemaphore.java:23)end...
Semaphore应用
数据库连接池
Semaphore原理
//构造器
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
NonfairSync(int permits) {
super(permits);
}
//最终调用同步器AQS的构造器
Sync(int permits) {
//把 permits 传给同步器的 state
setState(permits);
}
// acquire方法
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 获取state
int available = getState();
// state-1
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
// 返回剩余的资源数
return remaining;
}
}
//竞争失败进入doAcquireSharedInterruptibly方法去等待
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//release
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//tryReleaseShared
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 拿到state(0)
int current = getState();
// next = 0+1 = 1
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
private void doReleaseShared() {
for (;;) {
// 拿到头结点
Node h = head;
if (h != null && h != tail) {
// ws =-1 就说明有后继节点需要唤醒
int ws = h.waitStatus;
// Node.SIGNAL= -1
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// unparkSuccessor(h);此方法唤醒后继节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
7.5 CountdownLatch
倒计时锁,用来进行线程同步协作,等待所有线程完成倒计时。
其中构造参数用来初始化等待技术值,await()用来等待计数归零,countDown()用来让计数减
应用:主线程等待所有线程运行完毕,不过有返回值的就用Future get到再往下执行,香。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
demo
@Slf4j
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
try {
log.info("task run...");
Thread.sleep(1000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
log.info("task run...");
Thread.sleep(2000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
log.info("task run...");
Thread.sleep(1500);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
log.info("waiting...");
latch.await();
log.info("waiting end...");
}
}
[2020-10-31 15:35:02,570 下午]:INFO test.TestCountDownLatch.main(TestCountDownLatch.java:44)waiting... [2020-10-31 15:35:02,570 下午]:INFO test.TestCountDownLatch.lambda$main$0(TestCountDownLatch.java:17)task run... [2020-10-31 15:35:02,570 下午]:INFO test.TestCountDownLatch.lambda$main$2(TestCountDownLatch.java:35)task run... [2020-10-31 15:35:02,570 下午]:INFO test.TestCountDownLatch.lambda$main$1(TestCountDownLatch.java:26)task run... [2020-10-31 15:35:04,578 下午]:INFO test.TestCountDownLatch.main(TestCountDownLatch.java:46)waiting end...
王者荣耀读条
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(10);
Random random = new Random();
String[] players = new String[10];
String[] relos ={"虞姬","安其拉","瑶瑶公主","韩信","猪八戒","小鲁班","小貂蝉","泥鳅","吕布大人","李白"};
System.out.println("等待玩家读条完毕...");
for (int i = 0; i < 10; i++) {
int n = i;
pool.submit(()->{
Thread.currentThread().setName(relos[n]);
for (int j = 0; j <= 100; j++) {
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
players[n] = Thread.currentThread().getName()+":"+j+"%";
System.out.print("\r"+Arrays.toString(players));
}
latch.countDown();
});
}
latch.await();
System.err.print("\n欢迎来到王者荣耀!");
pool.shutdown();
}
}
等待玩家读条完毕... [虞姬:100%, 安其拉:100%, 瑶瑶公主:100%, 韩信:100%, 猪八戒:100%, 小鲁班:100%, 小貂蝉:100%, 泥鳅:100%, 吕布大人:100%, 李白:100%] 欢迎来到王者荣耀!
7.6 CyclicBarrier
循环珊栏,用来进行线程协作,等待线程满足某个计数。构造时设置计数个数,每个线程执行到某个需要“同步”的时刻调用await()方法进行等待,当等待的线程数满足计数个数时,继续运行。
小demo,忘掉了就拿去运行一家伙就ok了。
@Slf4j
public class TestCyclicBarrier {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
CyclicBarrier cyclicBarrier = new CyclicBarrier(2,()->{
log.info("计数结束运行...");
});
for (int i = 0; i < 3; i++) {
service.submit(()->{
log.info("task1 run..");
try {
Thread.sleep(2000);
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
service.submit(()->{
log.info("task2 run..");
try {
Thread.sleep(2000);
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
service.shutdown();
}
}
[2020-10-31 16:59:30,335 下午]:INFO demo.TestCyclicBarrier.lambda$main$1(TestCyclicBarrier.java:22)task1 run.. [2020-10-31 16:59:30,335 下午]:INFO demo.TestCyclicBarrier.lambda$main$2(TestCyclicBarrier.java:31)task2 run.. [2020-10-31 16:59:32,355 下午]:INFO demo.TestCyclicBarrier.lambda$main$0(TestCyclicBarrier.java:17)计数结束运行... [2020-10-31 16:59:32,357 下午]:INFO demo.TestCyclicBarrier.lambda$main$1(TestCyclicBarrier.java:22)task1 run.. [2020-10-31 16:59:32,357 下午]:INFO demo.TestCyclicBarrier.lambda$main$2(TestCyclicBarrier.java:31)task2 run.. [2020-10-31 16:59:34,368 下午]:INFO demo.TestCyclicBarrier.lambda$main$0(TestCyclicBarrier.java:17)计数结束运行... [2020-10-31 16:59:34,368 下午]:INFO demo.TestCyclicBarrier.lambda$main$1(TestCyclicBarrier.java:22)task1 run.. [2020-10-31 16:59:34,368 下午]:INFO demo.TestCyclicBarrier.lambda$main$2(TestCyclicBarrier.java:31)task2 run.. [2020-10-31 16:59:36,373 下午]:INFO demo.TestCyclicBarrier.lambda$main$0(TestCyclicBarrier.java:17)计数结束运行...
7.7 线程安全集合类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ihBLmzP5-1680968420732)(C:\Users\Mr.tan\OneDrive\桌面\线程安全集合类.png)]
线程集合类可以分为三大类:
- 遗留的线程安全集合如 HashTable,Vector
- 使用 Collections 装饰的线程安全集合,如:
- Collections.sychronizedCollection
- Collections.synchronizedList
- Collections.synchronizedMap
- Collections.synchronizedSet
- Collections.synchronizedNavigableMap
- Collections.synchronizedNavigableSet
- Collections.synchronizedSortedMap
- Collections.synchronizedSortedSet
- java.util.concurrent.*
重点介绍 java.util.concurrent 包下的线程安全集合类,可以发现他们有规律,里面包括三个关键词:
-
Blocking类
-
CopyOnWrite类
-
Concurrent类
-
内部很多操作使用cas优化,一般可以提供较高吞吐量
-
弱一致性
- 遍历时弱一致性,例如,当利用迭代器遍历时如果容器发生修改,迭代器仍然可以继续进行遍历,这时内容是旧的
- 求大小弱一致性,size操作未必是100%准确
- 读取弱一致性
遍历时如果发生了修改,对于非安全容器来讲,使用fail-fast机制也就是让遍历立刻失败,抛出ConcurrentModificationException,不再继续遍历
-
ConcurrentHashMap重要内部类
//默认为0
//当初始化时,为-1
//当扩容时,为-( 1 + 扩容线程数 )
//当初始化或扩容后,为 下一次的扩容的阈值大小
private transient volatile int sizeCtl;
//整个ConcurrentHashMap 就是一个 Node[]
static class Node<K,V> implements Map.Entry<K,V> {}
// hash 表
transient volatile Node<K,V>[] nextTable;
// 扩容时如果某个 bin 迁移完毕,用 ForwardingNode 作为旧 table bin 的头结点
static final class ForwardingNode<K,V> extends Node<K,V> {}
// 用在 cpmpute 以及 computeIfAbsent 时,用来占位,计算完成后替换成为普通 Node
static final class ReservationNode<K,V> extends Node<K,V> {}
//作为 treebin 的头节点,存储 root 和 first
static final class TreeBin<K,V> extends Node<K,V> {}
// 作为 treebin 的节点,存储 parent,left,right
static final class TreeNode<K,V> extends Node<K,V> {}
重要方法
//获取 Node[] 中第 i 个 Node
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab,int)
// cas 修改 Node[] 中第 i 个 Node 的值,c 为旧值,v 为新值
static final <K,V> boolean casTabAt(Node<K,V>[] tab,int i,Node<K,V> c,Node<K,V> v)
// 直接修改 Node[] 中第 i 个 Node 的值,v为新值
static final <K,V> void setTabAt(Node<K,V>[] tab,int i, Node<K,V> v)
* ConcurrentHashMap 原理
构造器分析
可以看到实现了懒惰初始化,在构造器方法中仅仅计算了table的大小,以后再第一次使用时才会真正创建
// initialCapacity 初始容量
// loadFactor 负载因子
// concurrencyLevel 并发度
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
// tableSizeFor 仍然是保证计算的大小是 2^n,即 16,32,64……
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
get流程
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// spread 方法能确保返回的及结果是正数
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 如果头节点已经是要查找的 key
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// hash 为负数表示该 bin 在扩容中或是 treebin,这时调用 find 方法来查找
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 正常遍历链表,用 equals 比较
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
put流程
以下数组简称(table),链表简称(bin)
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// 其中 spread 方法会综合高位低位,具有更好的 hash 性
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
// f 是链表头节点
// fh 是链表头节点的 hash
// i 是链表在 table 中的下标
Node<K,V> f; int n, i, fh;
// 要创建 table
if (tab == null || (n = tab.length) == 0)
// 初始化 table 使用了 cas,无需 synchronized 创建成功,进入下一轮循环
tab = initTable();
// 要创建链表头节点
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 添加表头使用了 cas,无需 synchronized
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 帮忙扩容
else if ((fh = f.hash) == MOVED)
// 帮忙扩容,进入下一轮循环
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 锁住链表头节点
synchronized (f) {
// 再次确认链表头有没有被移动
if (tabAt(tab, i) == f) {
// 链表
if (fh >= 0) {
binCount = 1;
// 遍历链表
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 找到相同的 key
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
// 更新
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
// 已经是最后的节点了,新增 Node,追加至链表尾
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 红黑树
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
7.8 ConcurrentLinkedQueue
7.9 BlockingQueue
static class Node<E> {
E item;
/*
下列三种情况之一
- 真正的后继节点
- 自己,发生在出队时
- null,表示是没有后续节点,是最后了
*/
Node<E> next;
Node(E x) { item = x; }
}
CopyOnwriteArrayList 读写分离