可重入锁

可重入锁又叫递归锁,指的是同一线程外层函数获得锁之后,内存递归函数仍然能获取该锁的代码,同一个线程在外层获取锁的时候,进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有的锁所同步者的代码块。

ReentrantLock/synchronized就是一个典型的可重入锁

代码验证

synchronized

image-20200926153106088

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ReenterLockDemo {

public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSMS();
}, "t1").start();

new Thread(() -> {
phone.sendSMS();
}, "t2").start();
}
}

class Phone {
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName()+"\t invoked sendSMS()");
sendEmail();
}

public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getName()+"\t invoked sendEmail()");
}
}

image-20200926153317986

当t1线程执行sendSMS()方法后,该方法中调用来同步方法sendEmail()。线程t1拿到外层函数sendSMS的锁后,能继续执行内层函数sendEmail。这就是可重入锁。

ReentrantLock

image-20200926153723845

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
public class ReenterLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSMS();
}, "t1").start();

new Thread(() -> {
phone.sendSMS();
}, "t2").start();
}
}

class Phone {
Lock lock = new ReentrantLock();

public void sendSMS() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
sendEmail();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void sendEmail() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

image-20200926153317986

ReentrantLock 和 synchronized 不一样,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样。

image-20200926153851074

sendEmail()方法中加锁了两次,而只解锁了一次。就会导致t1线程执行完sendEmail后死锁,t2线程只能不停等待t1结束!

image-20200926154035177