一篇文章让你彻底了解Java可重入锁和不可重入锁
作者:Loser_Keep. 发布时间:2023-12-06 11:57:26
可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。
我的理解就是,某个线程已经获得某个锁,可以无需等待而再次获取锁,并且不会出现死锁(不同线程当然不能多次获得锁,需要等待)。
简单的说,就是某个线程获得某个锁,之后可以不用等待而再次获取锁且不会出现死锁。
常见的可重入锁
Synchronized和ReentrantLock 都是可重入锁。
可重入锁的释放
同一个线程获取同一个锁,状态值state会累加,假设state累加到了2,每释放一次锁会减1,只有当状态值state减到0了,其他线程才有机会获取锁。也就是说,state归零才是已释放锁的标致。
可重入锁示例
public class ReentrantTest implements Runnable {
@Override
public void run() {
get();
}
public synchronized void get() {
System.out.println(Thread.currentThread().getName());
set();
}
/**
* 递归方法
*/
public synchronized void set() {
System.out.println(Thread.currentThread().getName());
}
/**
* 这里的对象锁只有一个,就是rt对象的锁。
* 当执行rt的get方法时,该线程获得rt对象的锁。在get方法内执行set方法时再次请求rt对象的锁,因为synchronized是可重入锁,所以又可以得到该锁。循环这个过程。
* 假设不是可重入锁的话,那么请求的过程中会出现阻塞,从而导致死锁。
* @param args
*/
public static void main(String[] args) {
ReentrantTest rt = new ReentrantTest();
// for(;;)模拟无限循环
for(;;){
new Thread(rt).start();
}
}
}
分析:这里的对象锁只有一个,就是rt对象的锁。当执行rt的get方法时,该线程获得rt对象的锁。在get方法内执行set方法时再次请求rt对象的锁,因为synchronized是可重入锁,所以又可以得到该锁。循环这个过程。假设不是可重入锁的话,那么请求的过程中会出现阻塞,从而导致死锁。
死锁
多线程中,不同的线程都在等待其它线程释放锁,而其它线程由于一些原因迟迟没有释放锁。程序的运行处于阻塞状态,不能正常运行也不能正常终止。
运行结果
set()和get()同时输出了相同的线程名称,也就是说某个线程执行的时候,不仅进入了set同步方法,还进入了get同步方法。递归使用synchronized也没有发生死锁,证明其是可重入的。
可重入锁的实现原理?
每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增1;当线程退出同步代码块时,计数器会递减1,如果计数器为 0,则释放该锁。
再分析一下上面可重入锁的例子
递归调用一次同步代码块,计数器会变为2,整个递归调用执行完,先退出内层执行减1,再退出外层执行减1。然后释放锁。
不可重入锁
就是某个线程已经获得某个锁,之后不可以再次获取锁,会被阻塞。
设计一个不可重入锁
public class Lock {
private boolean isLocked = false;
/**
* 加锁
*/
public synchronized void lock() throws Exception{
while(isLocked){
//当前线程释放锁,让出CPU,进入等待状态,直到被唤醒,才继续执行15行
wait();
System.out.println("wait");
}
isLocked = true;
}
/**
* 解锁
*/
public synchronized void unlock(){
isLocked = false;
//唤醒一个等待的线程继续执行
notify();
}
}
测试
public class Test {
Lock lock = new Lock();
public void print() throws Exception{
//加锁 标记为true
lock.lock();
//释放锁->等待 阻塞在16行
doAdd();
lock.unlock();
}
public void doAdd() throws Exception{
lock.lock();
System.out.println("doAdd");
lock.unlock();
}
public static void main(String[] args)throws Exception {
Test test=new Test();
test.print();
}
}
结果:这里,虽然模拟的是不可重入锁,实际还是在单线程环境中的。当前线程执行print()方法首先加锁 标记为true,接下来释放锁->等待 阻塞在16行内部的14行。整个过程中,第一次进入lock同步方法,执行完毕,第二次进入lock同步方法,阻塞等待。这个例子很好的说明了不可重入锁。
来源:https://blog.csdn.net/wxd772113786/article/details/117383221


猜你喜欢
- 看代码~ //创建socket对象 //第一个参数:设置网
- 一般要做正圆形图片,只能是正方形的基础上才能实现,否则就变成椭圆了,下面说说如何使长方形的图片生成正圆形图片废话不多说,没图没真相,先上图吧
- itextpdf解决PDF合并的问题本文章是我在项目开发过程中解决了一个关于PDF显示的需求而记录的。需求是这样的,需要将两个PDF进行合并
- InputStreamReader 类1、概述转换流 java.io.InputStreamReader ,是Reader的子类,是从字节流
- 在 Java 中,方法调用一般通过 Virtual Call 还有 Classic Call。Classic Call 就是直接指向方法的地
- 最近碰到个需要下载zip压缩包的需求,于是我在网上找了下别人写好的zip工具类。但找了好多篇博客,总是发现有bug。因此就自己来写了个工具类
- 实现效果:先看下效果:需求是 滑动列表 ,其中一部分视图(粉丝数,关注数这一部分)在滑动到顶端的时候不消失,而是停留在整个界面头部。我们先分
- 如果我们遇到把excel表格中的数据导入到数据库,首先我们要做的是:将excel中的数据先读取出来。因此,今天就给大家分享一个读取Excel
- 前面照着android系统的裁剪图片的功能自己写了一个相似的工具。功能是大体上实现了,但留下了一个调用的问题:如何从我的程序调用这个裁剪工具
- 说明SpringBoot版本:2.1.4.RELEASEjava版本:1.8文中所说JPA皆指spring-boot-starter-dat
- 现象说明maven的java项目,测试用例和main所在的源码文件均符合缺省写法和格式,但是在使用mvn clean sonar:sonar
- session超时退到登录页面最近发现使用的工程居然没有session超时机制,功能太欠缺了,现在把追加方法分享出来,里面有一些坑,大家自由
- 本文以一个简单的实例来说明C#策略模式的实现方法,分享给大家供大家参考。具体实现方法如下:一般来说,当一个动作有多种实现方法,在实际使用时,
- Java中的Runnable,Callable,Future,FutureTask的比较Java中存在Runnable、Callable、F
- 实现原理在之前的文章中,我们介绍了普通的帐号密码登录的方式: SpringBoot + Spring Security 基本使用及个性化登录
- 在JAVA中通过synchronized语句可以实现多线程并发。使用同步代码块,JVM保证同一时间只有一个线程可以拥有某一对象的锁。锁机制实
- 实现效果:列出某个目录下的特定后缀名文件(如,列出D盘根目录下txt后缀的文件)import java.io.File;import jav
- 刚开始学习C#的时候,就听说CLR对于String类有一种特别的内存管理机制:有时候,明明声明了两个String类的对象,但是他们偏偏却指向
- TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链
- 在项目中,经常会遇到页面分割,最常见的系统或网站的主界面。主页面分为,上面系统简介、下面作者简介、左边系统功能菜单、右边则是菜单真正展示的界