摘要:举例证明 synchronized锁 是可重入锁,并描述可重入锁的实现原理。

综述

  先给大家一个结论:synchronized锁 是可重入锁

  关于什么是可重入锁,通俗来说,当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。或者说,可重入锁是同一个线程重复请求由自己持有的锁对象时,可以请求成功而不会发生死锁。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。可重入锁又称递归锁。

验证可重入

  假设我们现在不知道它是不是一个可重入锁,那我们就应该想方设法来验证它是不是可重入锁?怎么验证呢?看下面的代码!

public class SuperSalesman {

    public int ticketNum = 10;

    public synchronized void superSaleTickets()  {
ticketNum --;
System.out.println("父类售票后,剩余票数:" + ticketNum
+ " " + Thread.currentThread().getName());
try {
Thread.sleep(30);
} catch (InterruptedException e) {
System.out.println("error, " + e);
}
} }

  创建子类:

public class ChildSalesman extends SuperSalesman {

    public static void main(String[] args) {
ChildSalesman child = new ChildSalesman();
child.childSaleTickets();
} public synchronized void childSaleTickets() {
while (ticketNum > 0) {
ticketNum --;
System.out.println("子类售票后,余票为:" + ticketNum
+ " " + Thread.currentThread().getName());
superSaleTickets(); //允许进入,synchronized的可重入性
}
} @Override
public synchronized void superSaleTickets() {
System.out.println("I am working");
super.superSaleTickets();
}
}

  现在运行一下上面的父子类继承代码,我们看一下结果:

子类售票后,余票为:9 main
I am working
父类售票后,剩余票数:8 main
子类售票后,余票为:7 main
I am working
父类售票后,剩余票数:6 main
子类售票后,余票为:5 main
I am working
父类售票后,剩余票数:4 main
子类售票后,余票为:3 main
I am working
父类售票后,剩余票数:2 main
子类售票后,余票为:1 main
I am working
父类售票后,剩余票数:0 main Process finished with exit code 0

  现在可以验证出 synchronized 是可重入锁了吧!因为这些方法输出了相同的线程名称,表明即使递归调用synchronized修饰的方法,也没有发生死锁,证明其是可重入的。

  下面是多个方法嵌套调用的例子:

public class SyncTest {

    public static void main(String[] args) {
LockTest lock = new LockTest();
lock.method1();
}
} public class LockTest {
public synchronized void method1() {
System.out.println("method1");
method2();
} public synchronized void method2() {
System.out.println("method2");
method3();
} public synchronized void method3() {
System.out.println("method3");
}
}

  执行main方法,控制台打印信息如下,说明不会因为之前已经获取过锁还没释放而发生阻塞。即同一线程可执行多个持有同一把锁的方法。

/Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk ...
method1
method2
method3

  可以看到调用的三个方法均得到了执行。我们知道synchronized修饰普通方法时,使用的是对象锁,也就是SuperSalesman对象。三个方法的锁都是SuperSalesman对象。我们在子类中执行childSaleTickets方法时,获取了SuperSalesman对象锁,然后在childSomeString时调用了重写父类的superSaleTickets方法,该方法的锁也是SuperSalesman对象锁,然后在其中调用父类的superSaleTickets方法,该方法的锁也是SuperSalesman对象锁。一个锁多次请求,而且都成功了,所以synchronized是可重入锁。

  所以在 java 内部,同一线程在调用自己类中其它 synchronized 方法/块或调用父类的 synchronized 方法/块都不会阻碍该线程的执行。就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。因为java线程是基于“每个线程(per-thread)”,而不是基于“每次调用(per-invocation)”的(java中线程获得对象锁的操作是以线程为粒度的,per-invocation 互斥体获得对象锁的操作是以每次调用作为粒度的)。

可重入锁的实现原理

  看到这里,你终于明白了 synchronized 是一个可重入锁。但是面试官要再问你,可重入锁的原理是什么?

解释一

  可重入锁实现可重入性原理或机制是:每一把锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这把锁,就可以再次拿到这把锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

解释二

  通过javap -c SynchronizedLock.class 反编译,来解析synchronized可重入锁原理:synchronized通过monitor计数器实现,当执行monitorenter命令时:判断当前monitor计数器值是否为0,如果为0,则说明当前线程可直接获取当前锁对象;否则,判断当前线程是否和获取锁对象线程是同一个线程。若是同一个线程,则monitor计数器累加1,当前线程能再次获取到锁;若不是同一个线程,则只能等待其它线程释放锁资源。当执行完synchronized锁对象的代码后,就会执行monitorexit命令,此时monitor计数器就减1,直至monitor计数器为0时,说明锁被释放了。

结束语

  如果您觉得本文对您有帮助,请点一下“推荐”按钮,您的【推荐】将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接;否则,楼兰胡杨保留追究法律责任的权利。

Reference

synchronized 锁是可重入锁吗?如何验证?的更多相关文章

  1. JAVA锁机制-可重入锁,可中断锁,公平锁,读写锁,自旋锁,

    如果需要查看具体的synchronized和lock的实现原理,请参考:解决多线程安全问题-无非两个方法synchronized和lock 具体原理(百度) 在并发编程中,经常遇到多个线程访问同一个 ...

  2. Java可重入锁与不可重入锁

    可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的. synchronized 和   ReentrantLock 都是可重入锁. 可重入 ...

  3. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等

    Java 中15种锁的介绍 Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等,在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类 ...

  4. 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!

    网上关于Java中锁的话题可以说资料相当丰富,但相关内容总感觉是一大串术语的罗列,让人云里雾里,读完就忘.本文希望能为Java新人做一篇通俗易懂的整合,旨在消除对各种各样锁的术语的恐惧感,对每种锁的底 ...

  5. 探索JAVA并发 - 可重入锁和不可重入锁

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  6. Java 种15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁等等…

    Java 中15种锁的介绍 1,在读很多并发文章中,会提及各种各样的锁,如公平锁,乐观锁,下面是对各种锁的总结归纳: 公平锁/非公平锁 可重入锁/不可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲 ...

  7. 写文章 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!

    网上关于Java中锁的话题可以说资料相当丰富,但相关内容总感觉是一大串术语的罗列,让人云里雾里,读完就忘.本文希望能为Java新人做一篇通俗易懂的整合,旨在消除对各种各样锁的术语的恐惧感,对每种锁的底 ...

  8. Java 多线程 -- 理解锁:手动实现可重入锁和不可重入锁

    JDK提供的大多数内置锁都是可重入的,也就是 说,如果某个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立 刻成功,并且会将这个锁的计数值加1,而当线程退出同步代码块时,计数器 将会递减,当计 ...

  9. Java不可重入锁和可重入锁的简单理解

    基础知识 Java多线程的wait()方法和notify()方法 这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出Ille ...

  10. Java中的常见锁(公平和非公平锁、可重入锁和不可重入锁、自旋锁、独占锁和共享锁)

    公平和非公平锁 公平锁:是指多个线程按照申请的顺序来获取值.在并发环境中,每一个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个就占有锁,否者就会加入到等待队列中,以 ...

随机推荐

  1. FastAPI 查询参数完全指南:从基础到高级用法 🚀

    title: FastAPI 查询参数完全指南:从基础到高级用法 date: 2025/3/6 updated: 2025/3/6 author: cmdragon excerpt: 探讨 FastA ...

  2. 震惊!AI编程正在淘汰这5类人,你在其中吗?

    大家好,我是狂师. 今天在知乎上看到一个关于讨论:"人工智能大爆发,AI编程工具对程序员到底是颠覆还是辅助?'"问题,觉得蛮有意思.的确,AI编程的出现,引发了人们对于程序员职业未 ...

  3. Go是怎么解决包依赖管理问题的?

    我们先来了解一下 Go 构建模式的演化过程,弄清楚 Go 核心开发团队为什么要引入 Go module 构建模式. Go 构建模式时怎么演化的? Go 程序由 Go 包组合而成的,Go 程序的构建过程 ...

  4. MTR工具使用说明与结果分析

    免责声明: 本文档可能包含第三方产品信息,该信息仅供参考.阿里云对第三方产品的性能.可靠性以及操作可能带来的潜在影响,不做任何暗示或其他形式的承诺. 概述 当客户端访问目标服务器或负载均衡,使用pin ...

  5. AI可解释性 I | 对抗样本(Adversarial Sample)论文导读(持续更新)

    AI可解释性 I | 对抗样本(Adversarial Sample)论文导读(持续更新) 导言 本文作为AI可解释性系列的第一部分,旨在以汉语整理并阅读对抗攻击(Adversarial Attack ...

  6. sql server2005的死锁

    select request_session_id spid, OBJECT_NAME(resource_associated_entity_id) tableName from sys.dm_tra ...

  7. 【Guava】BiMap&Multimap&Multiset

    BiMap Map 可以实现 key -> value 的映射,如果想要 value -> key 的映射,就需要定义两个 Map,并且同步更新,很不优雅.Guava 提供了 BiMap ...

  8. FastAPI依赖注入作用域与生命周期控制

    title: FastAPI依赖注入作用域与生命周期控制 date: 2025/04/08 00:02:10 updated: 2025/04/08 00:02:10 author: cmdragon ...

  9. 目前国内可用Docker镜像源汇总(截至2024年11月)

    本文主要讲述了由于特殊原因国内的 Docker 镜像源出现问题,国内许多常见的镜像源如网易.百度等已不可用.文中介绍了中科大镜像源的暂时关闭情况,以及阿里镜像源包括私人阿里镜像加速器的使用方式,并提供 ...

  10. Quill自定义插入视频video实例

    import Quill from 'quill' const BlockEmbed = Quill.import('blots/block/embed') class VideoBlot exten ...