Day11 多线程进阶 10 各种锁 排查死锁
uwupu 啦啦啦啦啦

Java的各种锁

公平锁 不公平锁

公平锁:非常公平,不能插队,必须先来后到。

不公平锁:非常不公平,可以插队。(默认都是非公平锁)

ReentrantLock下的锁

1
2
3
public ReentrantLock() {
sync = new NonfairSync();//非公平锁
}

类ReentrantLock创建的锁默认是非公平锁。

1
2
3
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

可以通过改变参数改为公平锁。

可重入锁

所有的锁都是可重入锁

可重入锁又称递归锁。是指同一个线程在外层方法获取锁的时候,再进入线程的内层方法会自动获取锁(前提:锁的对象是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。

可重入锁指的是某个线程已经获得某个锁,可以再次获取而不会出现死锁。

synchronized的可重入锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Demo36_ReentrantLock {
public static void main(String[] args) {
Phone5 phone5 = new Phone5();
new Thread(()->{
phone5.sendSms();
},"A").start();
new Thread(()->{
phone5.sendSms();
},"B").start();

// Thread.yield();
}
}


class Phone5{
public synchronized void sendSms(){
System.out.println(Thread.currentThread().getName()+" Sms");
call();
}

public synchronized void call(){
System.out.println(Thread.currentThread().getName()+" Call");
}
}

在代码中,线程A使用了phone的同步方法sendSms(),锁住了对象phone,然后sendSms()方法调用了同步方法call(),此时本应等待对象phone解锁后才能执行,因为可重入锁的原因,该操作继续执行。

ReentrantLock可重入锁

可以记录同一线程的锁的个数。

一个线程(A)可以多次使用lock()获得锁,ReentrantLock对象会记录这个线程(A)锁的数量,在全部锁被解锁之前,另一个线程(B)进行lock()会被阻塞,直到这个线程(A)将锁全部解锁。

使用lock()方法为当前线程获得一把锁并锁住这把锁,用unlock()解锁。lock()可以重复使用,使用几次,则获得几把锁,解锁时也需要解锁相应的次数(也就是相同数量的unlock())。

业务代码结束后,一定要解锁,以防下个线程不能执行业务代码。

API

API 说明
lock() 为当前线程获得一把锁并锁住这把锁。若ReentrantLock已被另一个线程锁定,则进入阻塞状态,直到锁被完全解锁。同一线程可以多次使用,多次使用会获得多个锁。必须在同一线程中有相同数量的unlock()被执行才能完全解锁。
unlock() 解锁一把锁。在同一线程中,与lock()配对使用,使用lock()后可以使用unlock()。若锁并没有进行lock(),使用unlock()会抛出异常:java.lang.IllegalMonitorStateException

使用

正常使用的ReentrantLock可重入锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Demo37_ReentrantLock可重入锁 {
public static void main(String[] args) {
Phone6 phone = new Phone6();
new Thread(()->{
System.out.println("线程A开始-----------------");
phone.sendSms();
System.out.println("线程A结束-----------------");
},"A").start();
new Thread(()->{
System.out.println("线程B开始-----------------");
phone.sendSms();
System.out.println("线程B结束-----------------");
},"B").start();
}
}


class Phone6{
Lock lock = new ReentrantLock();
public void sendSms(){
lock.lock(); //lock锁住了一把锁 1
try {
//业务代码
System.out.println(Thread.currentThread().getName()+" Sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁一把锁 1
}
}

public void call(){
lock.lock();//获得并锁住一把锁 2
try {
//业务代码
System.out.println(Thread.currentThread().getName()+" Call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁一把锁 2
}
}
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
线程A开始-----------------
A Sms
A Call
线程A结束-----------------
线程B开始-----------------
B Sms
B Call
线程B结束-----------------

Process finished with exit code 0
//“线程结束”的提示输出可能会错乱,因为多线程导致顺序不同,因仅测试学习提示用,所以不是重点。

分析一个线程,这里分析线程A:

  1. sendSms()方法使用了lock.lock()获得并锁住了一把锁,
  2. 到call()方法里,方法lock.lock()为线程A再次获得了一把锁,然后进行业务代码,
  3. 之后lock.unlock()解锁,
  4. call()方法结束回到sendSms()方法,lock.unlock()再次解锁,
  5. lock中线程A的锁被完全解锁。

缺少一个unlock()的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Demo37_ReentrantLock可重入锁 {
public static void main(String[] args) {
Phone6 phone = new Phone6();
new Thread(()->{
System.out.println("线程A开始-----------------");
phone.sendSms();
System.out.println("线程A结束-----------------");
},"A").start();
new Thread(()->{
System.out.println("线程B开始-----------------");
phone.sendSms();
System.out.println("线程B结束-----------------");
},"B").start();
}
}


class Phone6{
Lock lock = new ReentrantLock();
public void sendSms(){
lock.lock(); //lock锁住了一把锁 1
try {
System.out.println(Thread.currentThread().getName()+" Sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
// lock.unlock();//解锁一把锁 1
}
}

public void call(){
lock.lock();//获得并锁住一把锁 2
try {
System.out.println(Thread.currentThread().getName()+" Call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁一把锁 2
}
}
}

输出结果:

1
2
3
4
5
6
7
线程A开始-----------------
A Sms
A Call
线程A结束-----------------
线程B开始-----------------

//程序没有结束
  • 在这个代码中去掉了sendSms()方法中的lock.unlock(),在线程A执行结束后lock并没有被完全解锁,
  • 到线程B时,由于lock锁被锁定,线程被阻塞,暂时不能正常执行。

lock()和unlock()必须配对使用。

自旋锁 SpinLock

自旋锁与其他锁类似,不同的是让线程暂停运行的实现方式不同。

其他锁让线程暂停运行的方式是挂起,挂起后线程的资源会被切换,线程不会消耗CPU资源。

自旋锁让线程暂停运行的方式是无限循环,线程会消耗CPU时间。

特点

  1. 线程没有挂起,一旦线程获得锁,并且获得CPU时间,会立即执行,效率相对其他锁高;
  2. 线程自旋锁期间会消耗CPU时间,影响性能。

使用场景

锁被占用的时间很短,其他线程能较早地获得锁。

要求

自旋等待的时间必须有一定的限度,如果自旋超过了限定次数没有成功获得锁,就应当挂起线程

实现原理

例1:getAndAddInt()是一个自旋锁

1
2
3
4
5
6
7
8
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}

例2:手动实现自旋锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Demo38_SpinLock自旋锁 {

public static void main(String[] args) {
MySpinLock mySpinLock = new MySpinLock();

new Thread(()->{
mySpinLock.lock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
mySpinLock.unlock();
},"A").start();
new Thread(()->{
mySpinLock.lock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
mySpinLock.unlock();
},"B").start();

}
}
class MySpinLock {
AtomicReference<Thread> atomicReference = new AtomicReference<>();

public void lock(){
Thread thread = Thread.currentThread();
//自旋锁
//若不为null,则表示锁已被锁定 //若赋值成功则锁定成功
while (!atomicReference.compareAndSet(null,thread)){

}

System.out.println(Thread.currentThread().getName()+" ==> lock");
}

public void unlock(){
Thread thread = Thread.currentThread();
//尝试解锁
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+" ==> unlock");
}
1
2
3
4
5
6
A ==> lock
A ==> unlock
B ==> lock
B ==> unlock

Process finished with exit code 0

死锁(如何排查死锁)

两个线程之间互相拥有对方需要的资源,且在得到需要的资源你之前不会释放已拥有的资源。

简介

image

如何排查死锁

先放一个死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Demo39_死锁1 {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread2(lockA,lockB),"T1").start();
new Thread(new MyThread2(lockB,lockA),"T2 ").start();

}
}


class MyThread2 implements Runnable{
private String lockA;
private String lockB;

public MyThread2(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}

@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+" lock"+lockA+" =>get "+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+" lock"+lockB+" =>get "+lockA);
}
}
}
}

排查死锁1 查看日志

1
2
3
T2  locklockB =>get lockA
T1 locklockA =>get lockB
//程序没有结束

排查死锁2 查看堆栈信息

  1. 使用jps -l定位进程号

    image

  2. 使用jstack 进程号 查看进程信息 ( 这里是jstack 11452 )

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    略...
    Java stack information for the threads listed above:
    ===================================================
    "T2 ":
    at com.yn.MyThread2.run(Demo39_死锁1.java:35)
    - waiting to lock <0x000000076c0149d0> (a java.lang.String)
    - locked <0x000000076c014a08> (a java.lang.String)
    at java.lang.Thread.run(Thread.java:748)
    "T1":
    at com.yn.MyThread2.run(Demo39_死锁1.java:35)
    - waiting to lock <0x000000076c014a08> (a java.lang.String)
    - locked <0x000000076c0149d0> (a java.lang.String)
    at java.lang.Thread.run(Thread.java:748)

    Found 1 deadlock.

    找到一个死锁,

    T2等待0x000000076c0149d0,锁定了0x000000076c014a08

    T1等待0x000000076c014a08,锁定了0x000000076c0149d0

其他

Java查看堆栈信息

jps -l 查看进程列表

image

jstack 进程号 查看进程堆栈信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2022-09-04 10:35:00
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):

//略...

Found one Java-level deadlock:
=============================
"T2 ":
waiting to lock monitor 0x000000001cac0c28 (object 0x000000076c0149d0, a java.lang.String),
which is held by "T1"
"T1":
waiting to lock monitor 0x000000001cac3408 (object 0x000000076c014a08, a java.lang.String),
which is held by "T2 "

Java stack information for the threads listed above:
===================================================
"T2 ":
at com.yn.MyThread2.run(Demo39_死锁1.java:35)
- waiting to lock <0x000000076c0149d0> (a java.lang.String)
- locked <0x000000076c014a08> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"T1":
at com.yn.MyThread2.run(Demo39_死锁1.java:35)
- waiting to lock <0x000000076c014a08> (a java.lang.String)
- locked <0x000000076c0149d0> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

 评论