摘要:举例证明 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. .netCore 使用 Quartz 实例

    一.参考源文链接 1.https://www.likecs.com/show-897836.html 2.https://blog.csdn.net/weixin_43614067/article/d ...

  2. 绝了!k3s (k8s) 安装 ollama 运行 deepseek 全流程揭秘,yaml全公开

    k3s (k8s) 环境搭建与 ollama 相关 yaml 文件部署 在容器编排的世界中,k3s (k8s) 无疑是备受瞩目的存在.此次聚焦在 k3s (k8s) 环境下安装 ollama,并实现运 ...

  3. Web前端入门第 8 问:HTML <!DOCTYPE> 申明有何用处?如果没有此申明有什么问题?

    HELLO,这里是大熊学习前端开发的入门笔记. 本系列笔记基于 windows 系统. 先电脑端浏览器打开任何一个网页,比如百度. 再用 ctrl + u 快捷键即可查看源码,瞅瞅第一行代码,是不是都 ...

  4. Vue3-DeepSeek-Chat流式AI对话|vite6+vant4+deepseek智能ai聊天助手

    原创新作vue3.5+deepseek+vant4+vant4仿DeepSeek-R1流式输出ai聊天对话. deepseek-vue3-chat : 实战2025智能大模型ai会话,基于Vue3+V ...

  5. go context 子Goroutine超时控制

    context使用 Go语言第一形参通常都为context.Context类型,1. 传递上下文 2. 控制子Goroutine超时退出 3. 控制子Goroutine定时退出 package mai ...

  6. docker 中几个节点意外宕机 pxc 无法启动

    docker 意外宕机,PXC启动不了解决方法 由于 意外宕机 docker start pxc 节点后闪退,解决方法如下 从节点中找任意一个数据卷映射目录,修改参数 [root@izuf64gdeg ...

  7. Linux中查看进程状态信息

    一.常用命令总结  ps -l   列出与本次登录有关的进程信息:    ps -aux   查询内存中进程信息:    ps -aux | grep ***   查询***进程的详细信息:    t ...

  8. 可视化|数据可视化文档之 echarts 项目初始化

    数据可视化文档之 echarts 项目初始化 环境搭建 # node 环境 nvm install v11.15.0 # or nvm use 11.15.0 # 查看 node 版本 node -V ...

  9. 张高兴的大模型开发实战:(三)使用 LangGraph 为对话添加历史记录

    目录 基础概念 环境搭建与配置 将对话历史存储至内存 将对话历史存储至 PostgreSQL 在构建聊天机器人时,对话历史记录是提升用户体验的核心功能之一,用户希望机器人能够记住之前的对话内容,从而避 ...

  10. 基于RK3568 + FPGA国产平台的多通道AD实时采集显示方案分享

    在工业控制与数据采集领域,高精度的AD采集和实时显示至关重要.今天,我们就来基于瑞芯微RK3568J + FPGA国产平台深入探讨以下,它是如何实现该功能的.适用开发环境如下: Windows开发环境 ...