指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取锁的代码,同一个线程在外层获取锁的时候,在进入内层方法会自动获取锁。 也就是说,线程可以进入任何一个 已经拥有的锁的所同步着的代码块。 ReentrantLock/Synchronized就是一个典型的可重入锁(非公平的可重入锁)
有点:避免 死锁
一、代码演示
证明 synchronized 是一个典型的可重入锁
public class Phone {
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + "---sendSMS()");
sendEmail(); // 内部同步方法
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getId() + "------sendEmail()");
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
结果:
t1---sendSMS()
t1------sendEmail()
t2---sendSMS()
t2------sendEmail()
分析:
t1线程在外层方法获取锁的时候,t1在进入内层方法会自动获取锁
再次证明,同一个线程可以多次获得自己的同一把锁
public class ReentrantLockDemo3 {
static Object object = new Object();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
m1();
}
}
public static void m1() {
new Thread(() -> {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "\t" + "-------外层调用");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "\t" + "--------中层调用");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "\t----------内层调用");
}
}
}
}, "t1").start();
}
}
输出:
t1 -------外层调用
t1 --------中层调用
t1 ----------内层调用
t1 -------外层调用
t1 --------中层调用
t1 ----------内层调用
t1 -------外层调用
t1 --------中层调用
t1 ----------内层调用
t1 -------外层调用
t1 --------中层调用
t1 ----------内层调用
t1 -------外层调用
t1 --------中层调用
t1 ----------内层调用
证明 ReentrantLock 是一个可重入锁
public class Phone2 implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "---get()");
set();
} finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "---set()");
} finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
Thread thread1 = new Thread(phone);
Thread thread2 = new Thread(phone);
thread1.start();
thread2.start();
}
}
结果:
Thread-0---get()
Thread-0---set()
Thread-1---get()
Thread-1---set()
分析:
Thread-0线程在外层方法获取锁的时候,Thread-0在进入内层方法会自动获取锁
扩展:多加几把锁行不行?
private void get() {
lock.lock();
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "---get()");
set();
} finally {
lock.unlock();
lock.unlock();
lock.unlock();
}
}
二、小练习
消费者模式
另外一种实现方式,参考二十二章总结的部分
1、一个初始值为零的变量,两个线程对其交替操作,一个+1,一个-1,来5轮
首先定义一个资源类
// 注意:多线程的判断不能用if,要用while,因为有虚假唤醒
public class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() {
lock.lock();
try {
// 1、判断
while (number != 0) {
condition.await();
}
// 2、干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3、通知唤醒
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
// 1、判断
while (number == 0) {
condition.await();
}
// 2、干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3、通知唤醒
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
测试
一个初始值为零的变量,两个线程对其交替操作,一个+1,一个-1,来5轮
public class ProducerAndConsumer {
public static void main(String[] args) {
final ShareData shareData = new ShareData();
new Thread(()->{
for (int i = 0; i < 5; i++) {
shareData.increment();
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
shareData.decrement();
}
},"BB").start();
}
}
另外一种消费者写法,参考阻塞队列章节 https://www.yuque.com/wangchao-volk4/fdw9ek/kkxnfq#YpSty
三、总结
优点:避免死锁
怎么避免死锁的,只要开一道锁,就能一马平川(因为只要是同一把锁,拿到锁之后不需要释放,能接着继续拿锁),一个线程可以多次获得自己的同一把锁。