目录

  • 前言
  • 1、什么是可重入锁呢?
  • 2、自己写代码验证下可重入和不可重入
  • 3、自己如何实现一个可重入和不可重入锁呢
  • 4、ReentrantLock如何实现可重入的
  • 5、可重入锁的特点

前言

面试题:synchronized是可重入锁吗?

答案:synchronized是可重入锁。ReentrantLock也是的。


1、什么是可重入锁呢?

关于什么是可重入锁,我们先来看一段维基百科的定义。

若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,执行线程可以再次进入并执行它,仍然获得符合设计时预期的结果。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。

通俗来说:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

再换句话说:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。


2、自己写代码验证下可重入和不可重入

我们启动一个线程t1,调用addOne()方法来执行加1操作。在addOne方法里面t1会获得rtl锁,然后调用get()方法,在get()方法里再次请求获取trl锁。

因为最终能打印value=1,说明t1在第二次获取锁的时候并没有阻塞。说明ReentrantLock是可重入锁。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ReentrantTest {private final Lock rtl = new ReentrantLock();int value = 0;public static void main(String[] args) throws InterruptedException {ReentrantTest test = new ReentrantTest();// 新建一个线程 进行加1操作Thread t1 = new Thread(() -> test.addOne());t1.start();// main线程等待t1线程执行完t1.join();System.out.println(test.value);}public int get() {// 获取锁rtl.lock();try {return value;} finally {// 保证锁能释放rtl.unlock();}}public void addOne() {// 获取锁rtl.lock();try {value = 1 + get();} finally {// 保证锁能释放rtl.unlock();}}
}

换成synchronized的加锁方式,同样能打印value的值。证明synchronized也是可重入锁。

public class ReentrantTest {private final Object object = new Object();int value = 0;public static void main(String[] args) throws InterruptedException {ReentrantTest test = new ReentrantTest();// 新建一个线程 进行加1操作Thread t1 = new Thread(() -> test.addOne());t1.start();t1.join();System.out.println(test.value);}public int get() {// 再此获取锁synchronized (object) {return value;}}public void addOne() {// 获取锁synchronized (object) {value = 1 + get();}}
}

3、自己如何实现一个可重入和不可重入锁呢

不可重入:

public class Lock{private boolean isLocked = false;public synchronized void lock()throws InterruptedException{while(isLocked){wait();}isLocked = true;}public synchronized void unlock(){isLocked = false;notify();}
}

可重入:

public class Lock{boolean isLocked = false;Thread  lockedBy = null;int lockedCount = 0;public synchronized void lock() throws InterruptedException{Thread callingThread = Thread.currentThread();while(isLocked && lockedBy != callingThread){wait();}isLocked = true;lockedCount++;lockedBy = callingThread;}public synchronized void unlock(){if(Thread.curentThread() == this.lockedBy){lockedCount--;if(lockedCount == 0){isLocked = false;notify();}}}
}

从代码实现来看,可重入锁增加了两个状态,锁的计数器和被锁的线程,实现基本上和不可重入的实现一样,如果不同的线程进来,这个锁是没有问题的,但是如果进行递归计算的时候,如果加锁,不可重入锁就会出现死锁的问题。


4、ReentrantLock如何实现可重入的

使用ReentrantLock你要知道:
ReentrantLock支持公平非公平2种创建方式,默认创建的是非公平模式的锁。

看下它的构造方法:

public ReentrantLock() {sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}

看下非公平锁,它是继承抽象类Sync的:

static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}

看下公平锁,它也是继承抽象类Sync的:

static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}/*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
}

NonfairSync、FairSync 和抽象类Sync 都是ReentrantLock的内部类。

Sync的定义,它是继承AbstractQueuedSynchronizer的,AbstractQueuedSynchronizer既是我们常说的AQS(后面我也会整理一篇)

abstract static class Sync extends AbstractQueuedSynchronizer {
}

好了,继承关系清楚了 ,现在我们看下ReentrantLock是如何实现可重入的

我们在addOne()和get()两个方法加锁的地方都打上断点。然后开始调式:

  • addOne方法获取锁的时候走到NonfairSync的“compareAndSetState(0, 1)”,通过CAS设置state的值为1,调用成功,并设置当前锁被持有的线程为当前线程t1;
  • 继续调试,get方法获取锁的时候走到NonfairSync的“compareAndSetState(0, 1)”,通过CAS设置state的值为1,调用失败(因为已经被当前线程t1锁占有),走到else里面,继续往里看;
  • 走到NonfairSync的tryAcquire方法,再往里走;
  • 会调用Sync抽象类里面的nonfairTryAcquire方法。源码解释我都写在下面了。
final boolean nonfairTryAcquire(int acquires) {// 当前线程final Thread current = Thread.currentThread();
// state变量的值int c = getState();
// 因为c当前值为1,所以走else里面if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}
// 判断当前线程 是不是 当前锁被持有的线程 ,判断为 trueelse if (current == getExclusiveOwnerThread()) {
// c + acquires = 1 + 1 = 2int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);// 将state的值赋值为2return true;}return false;
}

到此,可重入锁加锁的过程分析完毕。解锁的过程一样,希望你能自己debug下【调用的是Sync抽象类里面的tryRelease方法】

我这里总结一下:

  • 当线程尝试获取锁时,可重入锁先尝试获取并更新state值
    如果state == 0表示没有其他线程在执行同步代码,则通过CAS把state置为1 会成功,当前线程继续执行。
    如果status != 0,通过CAS把state置为1 会失败,然后判断当前线程是否是获取到这个锁的线程,如果是的话执行state+1,且当前线程可以再次获取锁。

  • 释放锁时,可重入锁同样先获取当前state的值,在当前线程是持有锁的线程的前提下。
    如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。

你需要注意的是state变量的定义,其实AQS的实现类都是通过控制state的值来控制锁的状态的。它被volatile所修饰,能保证可见性

private volatile int state;

扩展:如果要通过AQS的state来实现非可重入锁怎么实现呢?明确这两点就可以了:

  • 获取锁时:去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。
  • 释放锁时:在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。

5、可重入锁的特点

可重入锁的一个优点是可一定程度避免死锁
    可重入锁能避免一定线程的等待,可想而知可重入锁性能会高于非可重入锁。你可以写程序测试一下哦!!!

推荐阅读(系列文章按顺序阅读哦):
Java内存模型-volatile的应用(实例讲解)
synchronized的三种应用方式(实例讲解)
大彻大悟synchronized原理,锁的升级
一文弄懂Java的线程池

可重入锁-synchronized是可重入锁吗?相关推荐

  1. Java锁-Synchronized深层剖析

    Java锁-Synchronized深层剖析 前言 Java锁的问题,可以说是每个JavaCoder绕不开的一道坎.如果只是粗浅地了解Synchronized等锁的简单应用,那么就没什么谈的了,也不建 ...

  2. 【并发编程】线程锁--Synchronized、ReentrantLock(可重入锁)

    在说锁之前,我们要明白为什么要加锁,不加锁会怎样? 在并发编程中,很容易出现线程安全问题,接下来我们看个很经典的例子--银行取钱,来看一下有关线程安全的问题. 取钱的流程可以分为一下几个步骤: 1.用 ...

  3. 【代码】synchronized是可重入锁并且多个sync代码块顺序执行

    synchronized锁是可以重入的: 本例中由于m1锁定this,只有m1执行完毕的时候,m2才能执行. import java.util.concurrent.TimeUnit;/*** * 本 ...

  4. 【转载】Java多线程编程2--同步锁定--synchronized同步方法、脏读、锁重入

        线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 1.方法内的变量为线程安全   "非线程安全"问题存在于"实例变量"中,如果是方法内 ...

  5. synchronized的可重入锁

    1. synchronized锁重入 锁重入的概念:在使用synchronized时,当一个线程得到一个对象锁后再次请求此对象锁时是可以得到该对象锁的.即在一个synchronized方法/块的内部调 ...

  6. 谈谈java并发锁(重入锁、读写锁、公平锁)

    目录 重入锁 简单重入锁 重入锁的等待通知(Condition) 多Condition 公平锁和非公平锁 读写锁ReentrantReadWriteLock 锁优化总结: 重入锁和读写锁,他们具有比s ...

  7. synchronized的可重入怎么实现的

    ● 请说明一下synchronized的可重入怎么实现. 考察点:锁 参考回答: 每个锁关联一个线程持有者和一个计数器.当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应 ...

  8. java 并发锁_Java并发教程–重入锁

    java 并发锁 Java的synced关键字是一个很棒的工具–它使我们可以通过一种简单可靠的方式来同步对关键部分的访问,而且也不难理解. 但是有时我们需要对同步进行更多控制. 我们要么需要分别控制访 ...

  9. zbb20180929 thread 自旋锁、阻塞锁、可重入锁、悲观锁、乐观锁、读写锁、对象锁和类锁...

    1.自旋锁 自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行.若线程依然不能获得锁,才会被挂 ...

最新文章

  1. 联邦学习首个国际标准正式发布
  2. AE 各分析适用数据
  3. 雷达篇(九)雷达中的“快采样”和“慢采样”
  4. DockerONE 干货 深入理解Docker容器和镜像
  5. python解析http数据包_如何在python中嗅探HTTP数据包?
  6. eclipse改变默认的编码格式(UTF-8)
  7. 提示账户不被允许使用docker的情况
  8. 还敢吹「毫无PS痕迹」?小心被Adobe官方AI打脸
  9. sqlplus 执行sql文件_详解sqlplus设定行大小、页大小、字符列格式、数字列格式、清屏...
  10. C# 多线程六 事件 AutoResetEvent/ManualResetEvent 的简单理解与运用
  11. 下载Youtube单个视频和播放列表的方法详细教程
  12. 全球最大的搜索引擎排名~~~~~~~~!!!!
  13. Vue项目实现web端第三方分享(qq、qq空间、微博、微信)
  14. w7系统怎么开启打印机服务器,W7系统如何开启打印机服务
  15. cds云服务器_云探CDS拨测服务全面上线
  16. 海康威视摄像头web二次开发(angular)
  17. asp.net后台代码如何通过动态的id给aspx中的html控件赋值
  18. js判断字符串是不是一个纯数字
  19. 墨画子卿第一章第7节: “刀马旦”
  20. Android APP性能及专项测试(个人整理)

热门文章

  1. 一屋不扫何以扫天下?
  2. [无线]433M天线、2.4G天线、5G天线长度设计
  3. linux平台实现USB虚拟总线驱动一(原理以及开发流程)
  4. Unity快手上手【熟悉unity编辑器,C#脚本控制组件一些属性之类的】
  5. 基于单片机的智能窗控制系统设计(电路+流程)
  6. android studio jar包管理,AndroidStudio下的依赖管理
  7. 去除txt文件中的空行
  8. MySQL查询语句练习网站
  9. “山河无恙,网络清朗”之金刚钻
  10. 根据当期日期计算,农历日期的类