指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取锁的代码,同一个线程在外层获取锁的时候,在进入内层方法会自动获取锁。 也就是说,线程可以进入任何一个 已经拥有的锁的所同步着的代码块。 ReentrantLock/Synchronized就是一个典型的可重入锁(非公平的可重入锁)

有点:避免 死锁

一、代码演示

证明 synchronized 是一个典型的可重入锁

  1. public class Phone {
  2. public synchronized void sendSMS() {
  3. System.out.println(Thread.currentThread().getName() + "---sendSMS()");
  4. sendEmail(); // 内部同步方法
  5. }
  6. public synchronized void sendEmail() {
  7. System.out.println(Thread.currentThread().getId() + "------sendEmail()");
  8. }
  9. }
  1. public class ReentrantLockDemo {
  2. public static void main(String[] args) {
  3. Phone phone = new Phone();
  4. new Thread(()->{
  5. try {
  6. phone.sendSMS();
  7. } catch (Exception e) {
  8. e.printStackTrace();
  9. }
  10. },"t1").start();
  11. new Thread(()->{
  12. try {
  13. phone.sendSMS();
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. },"t2").start();
  18. }
  19. }
  20. 结果:
  21. t1---sendSMS()
  22. t1------sendEmail()
  23. t2---sendSMS()
  24. t2------sendEmail()

分析:
t1线程在外层方法获取锁的时候,t1在进入内层方法会自动获取锁

再次证明,同一个线程可以多次获得自己的同一把锁

  1. public class ReentrantLockDemo3 {
  2. static Object object = new Object();
  3. public static void main(String[] args) {
  4. for (int i = 0; i < 10; i++) {
  5. m1();
  6. }
  7. }
  8. public static void m1() {
  9. new Thread(() -> {
  10. synchronized (object) {
  11. System.out.println(Thread.currentThread().getName() + "\t" + "-------外层调用");
  12. synchronized (object) {
  13. System.out.println(Thread.currentThread().getName() + "\t" + "--------中层调用");
  14. synchronized (object) {
  15. System.out.println(Thread.currentThread().getName() + "\t----------内层调用");
  16. }
  17. }
  18. }
  19. }, "t1").start();
  20. }
  21. }
  22. 输出:
  23. t1 -------外层调用
  24. t1 --------中层调用
  25. t1 ----------内层调用
  26. t1 -------外层调用
  27. t1 --------中层调用
  28. t1 ----------内层调用
  29. t1 -------外层调用
  30. t1 --------中层调用
  31. t1 ----------内层调用
  32. t1 -------外层调用
  33. t1 --------中层调用
  34. t1 ----------内层调用
  35. t1 -------外层调用
  36. t1 --------中层调用
  37. t1 ----------内层调用

证明 ReentrantLock 是一个可重入锁

  1. public class Phone2 implements Runnable {
  2. Lock lock = new ReentrantLock();
  3. @Override
  4. public void run() {
  5. get();
  6. }
  7. private void get() {
  8. lock.lock();
  9. try {
  10. System.out.println(Thread.currentThread().getName() + "---get()");
  11. set();
  12. } finally {
  13. lock.unlock();
  14. }
  15. }
  16. private void set() {
  17. lock.lock();
  18. try {
  19. System.out.println(Thread.currentThread().getName() + "---set()");
  20. } finally {
  21. lock.unlock();
  22. }
  23. }
  24. }
  1. public class ReentrantLockDemo2 {
  2. public static void main(String[] args) {
  3. Phone2 phone = new Phone2();
  4. Thread thread1 = new Thread(phone);
  5. Thread thread2 = new Thread(phone);
  6. thread1.start();
  7. thread2.start();
  8. }
  9. }
  10. 结果:
  11. Thread-0---get()
  12. Thread-0---set()
  13. Thread-1---get()
  14. Thread-1---set()

分析:
Thread-0线程在外层方法获取锁的时候,Thread-0在进入内层方法会自动获取锁
扩展:多加几把锁行不行?

  1. private void get() {
  2. lock.lock();
  3. lock.lock();
  4. lock.lock();
  5. try {
  6. System.out.println(Thread.currentThread().getName() + "---get()");
  7. set();
  8. } finally {
  9. lock.unlock();
  10. lock.unlock();
  11. lock.unlock();
  12. }
  13. }

答:没有任何问题,只要lock和unlock数量一致就行。

二、小练习

消费者模式

另外一种实现方式,参考二十二章总结的部分

1、一个初始值为零的变量,两个线程对其交替操作,一个+1,一个-1,来5轮
首先定义一个资源类

  1. // 注意:多线程的判断不能用if,要用while,因为有虚假唤醒
  2. public class ShareData {
  3. private int number = 0;
  4. private Lock lock = new ReentrantLock();
  5. private Condition condition = lock.newCondition();
  6. public void increment() {
  7. lock.lock();
  8. try {
  9. // 1、判断
  10. while (number != 0) {
  11. condition.await();
  12. }
  13. // 2、干活
  14. number++;
  15. System.out.println(Thread.currentThread().getName() + "\t" + number);
  16. // 3、通知唤醒
  17. condition.signalAll();
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. } finally {
  21. lock.unlock();
  22. }
  23. }
  24. public void decrement() {
  25. lock.lock();
  26. try {
  27. // 1、判断
  28. while (number == 0) {
  29. condition.await();
  30. }
  31. // 2、干活
  32. number--;
  33. System.out.println(Thread.currentThread().getName() + "\t" + number);
  34. // 3、通知唤醒
  35. condition.signalAll();
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. } finally {
  39. lock.unlock();
  40. }
  41. }
  42. }

测试
一个初始值为零的变量,两个线程对其交替操作,一个+1,一个-1,来5轮

  1. public class ProducerAndConsumer {
  2. public static void main(String[] args) {
  3. final ShareData shareData = new ShareData();
  4. new Thread(()->{
  5. for (int i = 0; i < 5; i++) {
  6. shareData.increment();
  7. }
  8. },"AA").start();
  9. new Thread(()->{
  10. for (int i = 0; i < 5; i++) {
  11. shareData.decrement();
  12. }
  13. },"BB").start();
  14. }
  15. }

另外一种消费者写法,参考阻塞队列章节 https://www.yuque.com/wangchao-volk4/fdw9ek/kkxnfq#YpSty

三、总结

优点:避免死锁
怎么避免死锁的,只要开一道锁,就能一马平川(因为只要是同一把锁,拿到锁之后不需要释放,能接着继续拿锁),一个线程可以多次获得自己的同一把锁。