指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取锁的代码,同一个线程在外层获取锁的时候,在进入内层方法会自动获取锁。 也就是说,线程可以进入任何一个 已经拥有的锁的所同步着的代码块。 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();@Overridepublic 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
三、总结
优点:避免死锁
怎么避免死锁的,只要开一道锁,就能一马平川(因为只要是同一把锁,拿到锁之后不需要释放,能接着继续拿锁),一个线程可以多次获得自己的同一把锁。
