重入锁:ReentrantLock

1. 简介

ReentrantLock,可重入锁,是一种递归无阻塞的同步机制。它可以等同于 synchronized 的使用,但是 ReentrantLock 提供了比 synchronized 更强大、灵活的锁机制,可以减少死锁发生的概率。

一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。

ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 #isHeldByCurrentThread()#getHoldCount() 方法来检查此情况是否发生。

ReentrantLock 还提供了公平锁非公平锁的选择,通过构造方法接受一个可选的 fair 参数(默认非公平锁):当设置为 true 时,表示公平锁;否则为非公平锁。

公平锁与非公平锁的区别在于,公平锁的锁获取是有顺序的。但是公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。

ReentrantLock 整体结构如下图:

  • ReentrantLock 实现 Lock 接口,基于内部的 Sync 实现。
  • Sync 实现 AQS ,提供了 FairSync 和 NonFairSync 两种实现。

2. Sync 抽象类

Sync 是 ReentrantLock 的内部静态类,实现 AbstractQueuedSynchronizer 抽象类,同步器抽象类。它使用 AQS 的 state 字段,来表示当前锁的持有数量,从而实现可重入的特性。

2.1 lock

/*** Performs {@link Lock#lock}. The main reason for subclassing* is to allow fast path for nonfair version.*/
abstract void lock();
  • 执行锁。抽象了该方法的原因是,允许子类实现快速获得非公平锁的逻辑。

2.2 nonfairTryAcquire

#nonfairTryAcquire(int acquires) 方法,非公平锁的方式获得锁。代码如下:

final boolean nonfairTryAcquire(int acquires) {//当前线程final Thread current = Thread.currentThread();//获取同步状态int c = getState();//state == 0,表示没有该锁处于空闲状态if (c == 0) {//获取锁成功,设置为当前线程所有if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//线程重入//判断锁持有的线程是否为当前线程else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}
  • 该方法主要逻辑:首先判断同步状态 state == 0 ?如果,表示该锁还没有被线程持有,直接通过CAS获取同步状态。如果成功,返回 true 。否则,返回 false 。如果不是,则判断当前线程是否为获取锁的线程?如果是,则获取锁,成功返回 true 。成功获取锁的线程,再次获取锁,这是增加了同步状态 state 。通过这里的实现,我们可以看到上面提到的 “它使用 AQS 的 state 字段,来表示当前锁的持有数量,从而实现可重入的特性”。否则,返回 false 。
  • 理论来说,这个方法应该在子类 FairSync 中实现,但是为什么会在这里呢?在下文的 ReentrantLock.tryLock() 中,详细解析。

2.3 tryRelease

#tryRelease(int releases) 实现方法,释放锁。代码如下:

protected final boolean tryRelease(int releases) {// 减掉releasesint c = getState() - releases;// 如果释放的不是持有锁的线程,抛出异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// state == 0 表示已经释放完全了,其他线程可以获取同步状态了if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}
  • 通过判断判断是否为获得到锁的线程,保证该方法线程安全。
  • 只有当同步状态彻底释放后,该方法才会返回 true 。当 state == 0 时,则将锁持有线程设置为 null ,free= true,表示释放成功。

2.4 其他实现方法

其他实现方法比较简单,胖友自己看。

// 是否当前线程独占
@Override
protected final boolean isHeldExclusively() {// While we must in general read state before owner,// we don't need to do so to check if current thread is ownerreturn getExclusiveOwnerThread() == Thread.currentThread();
}// 新生成条件
final ConditionObject newCondition() {return new ConditionObject();
}// Methods relayed from outer class// 获得占用同步状态的线程
final Thread getOwner() {return getState() == 0 ? null : getExclusiveOwnerThread();
}// 获得当前线程持有锁的数量
final int getHoldCount() {return isHeldExclusively() ? getState() : 0;
}// 是否被锁定
final boolean isLocked() {return getState() != 0;
}/*** Reconstitutes the instance from a stream (that is, deserializes it).* 自定义反序列化逻辑*/
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();setState(0); // reset to unlocked state
}
  • 从这些方法中,我们可以看到,ReentrantLock 是独占获取同步状态的模式。

3. Sync 实现类

3.1 NonfairSync

NonfairSync 是 ReentrantLock 的内部静态类,实现 Sync 抽象类,公平锁实现类。

3.1.1 lock

#lock() 实现方法,首先基于 AQS state 进行 CAS 操作,将 0 => 1 。若成功,则获取锁成功。若失败,执行 AQS 的正常的同步状态获取逻辑。代码如下:

@Override
final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);
}
  • 优先基于 AQS state 进行 CAS 操作,已经能体现出非公平锁的特点。因为,此时有可能有 N + 1 个线程正在获得锁,其中 1 个线程已经获得到锁,释放的瞬间,恰好被的线程抢夺到,而不是排队的 N 个线程。

3.1.2 tryAcquire

#tryAcquire(int acquires) 实现方法,非公平的方式,获得同步状态。代码如下:

protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}
  • 直接调用 #nonfairTryAcquire(int acquires) 方法,非公平锁的方式获得锁。

3.2 FairSync

FairSync 是 ReentrantLock 的内部静态类,实现 Sync 抽象类,公平锁实现类。

3.2.1 lock

#lock() 实现方法,代码如下:

final void lock() {acquire(1);
}
  • 直接执行 AQS 的正常的同步状态获取逻辑。

3.2.2 tryAcquire

#tryAcquire(int acquires) 实现方法,公平的方式,获得同步状态。代码如下:

protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() && // <1>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;
}

比较非公平锁和公平锁获取同步状态的过程,会发现两者唯一的区别就在于,公平锁在获取同步状态时多了一个限制条件 <1> 处的 #hasQueuedPredecessors() 方法,是否有前序节点,即自己不是首个等待获取同步状态的节点。代码如下:

// AbstractQueuedSynchronizer.java
public final boolean hasQueuedPredecessors() {Node t = tail;  //尾节点Node h = head;  //头节点Node s;//头节点 != 尾节点//同步队列第一个节点不为null//当前线程是同步队列第一个节点return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}
  • 该方法主要做一件事情:主要是判断当前线程是否位于 CLH 同步队列中的第一个。如果是则返回 true ,否则返回 false 。

3. Lock 接口

java.util.concurrent.locks.Lock 接口,定义方法如下:

void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition();
  • FROM 《Java并发编程的艺术》的 「5.1 Lock 接口」 章节。

4. ReentrantLock

java.util.concurrent.locks.ReentrantLock ,实现 Lock 接口,重入锁。

ReentrantLock 的实现方法,基本是对 Sync 的调用。

4.1 构造方法

public ReentrantLock() {sync = new NonfairSync();
}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}
  • 基于 fair 参数,创建 FairSync 还是 NonfairSync 对象。

4.2 lock

@Override
public void lock() {sync.lock();
}

4.3 lockInterruptibly

@Override
public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);
}

4.4 tryLock

/*** Acquires the lock only if it is not held by another thread at the time* of invocation.** <p>Acquires the lock if it is not held by another thread and* returns immediately with the value {@code true}, setting the* lock hold count to one. Even when this lock has been set to use a* fair ordering policy, a call to {@code tryLock()} <em>will</em>* immediately acquire the lock if it is available, whether or not* other threads are currently waiting for the lock.* This &quot;barging&quot; behavior can be useful in certain* circumstances, even though it breaks fairness. If you want to honor* the fairness setting for this lock, then use* {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) }* which is almost equivalent (it also detects interruption).** <p>If the current thread already holds this lock then the hold* count is incremented by one and the method returns {@code true}.** <p>If the lock is held by another thread then this method will return* immediately with the value {@code false}.** @return {@code true} if the lock was free and was acquired by the*         current thread, or the lock was already held by the current*         thread; and {@code false} otherwise*/
@Override
public boolean tryLock() {return sync.nonfairTryAcquire(1);
}
  • 详细的说明,胖友可以看上面的英文注释。
  • 老艿艿的简单理解是:
    • #tryLock() 实现方法,在实现时,希望能快速的获得是否能够获得到锁,因此即使在设置为 fair = true ( 使用公平锁 ),依然调用 Sync#nonfairTryAcquire(int acquires) 方法。
    • 如果真的希望 #tryLock() 还是按照是否公平锁的方式来,可以调用 #tryLock(0, TimeUnit) 方法来实现。

4.5 tryLock

@Override
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

4.6 unlock

@Override
public void unlock() {sync.release(1);
}

4.7 newCondition

@Override
public Condition newCondition() {return sync.newCondition();
}

4.8 其他实现方法

其他实现方法比较简单,胖友自己看。

public int getHoldCount() {return sync.getHoldCount();
}
public boolean isHeldByCurrentThread() {return sync.isHeldExclusively();
}
public boolean isLocked() {return sync.isLocked();
}public final boolean isFair() {return sync instanceof FairSync;
}protected Thread getOwner() {return sync.getOwner();
}
public final boolean hasQueuedThreads() {return sync.hasQueuedThreads();
}
public final boolean hasQueuedThread(Thread thread) {return sync.isQueued(thread);
}
public final int getQueueLength() {return sync.getQueueLength();
}
protected Collection<Thread> getQueuedThreads() {return sync.getQueuedThreads();
}public boolean hasWaiters(Condition condition) {if (condition == null)throw new NullPointerException();if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))throw new IllegalArgumentException("not owner");return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}
public int getWaitQueueLength(Condition condition) {if (condition == null)throw new NullPointerException();if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))throw new IllegalArgumentException("not owner");return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}
protected Collection<Thread> getWaitingThreads(Condition condition) {if (condition == null)throw new NullPointerException();if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))throw new IllegalArgumentException("not owner");return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}

5. ReentrantLock 与 synchronized 的区别

前面提到 ReentrantLock 提供了比 synchronized 更加灵活和强大的锁机制,那么它的灵活和强大之处在哪里呢?他们之间又有什么相异之处呢?

首先他们肯定具有相同的功能和内存语义。

  1. synchronized 相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。
  2. ReentrantLock 还提供了条件 Condition ,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock 更加适合(以后会阐述Condition)。
  3. ReentrantLock 提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而 synchronized 则一旦进入锁请求要么成功要么阻塞,所以相比 synchronized 而言,ReentrantLock会不容易产生死锁些。
  4. ReentrantLock 支持更加灵活的同步代码块,但是使用 synchronized 时,只能在同一个 synchronized 块结构中获取和释放。注意,ReentrantLock 的锁释放一定要在 finally 中处理,否则可能会产生严重的后果。
  5. ReentrantLock 支持中断处理,且性能较 synchronized 会好些。

参考资料

  1. Doug Lea:《Java并发编程实战》
  2. 方腾飞:《Java并发编程的艺术》的 「5.1 Lock 接口」 和 「5.3 重入锁」 章节
  3. 《【JUC】JDK 1.8 源码分析之 ReentrantLock(三)》

666. 彩蛋

如果你对 Java 并发感兴趣,欢迎加入我的知识星球一起交流。

重入锁:ReentrantLock相关推荐

  1. 并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition

    文章目录 J.U.C脑图 ReentrantLock概述 ReentrantLock 常用方法 synchronized 和 ReentrantLock的比较 ReentrantLock示例 读写锁R ...

  2. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  3. java中的账户冻结原理_java可重入锁(ReentrantLock)的实现原理

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  4. java多线程---重入锁ReentrantLock

    1.定义 重入锁ReentrantLock,支持重入的锁,表示一个线程对资源的重复加锁. 2.底层实现 每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获 ...

  5. 重入锁ReentrantLock详解

    重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁.除此之外,该锁的还支持获取锁时的公平和非公平性选择. 在AQS实现中,当一个线程调用Mute ...

  6. Java多线程系列——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为"独占锁". ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychr ...

  7. java lock可重入_Java源码解析之可重入锁ReentrantLock

    本文基于jdk1.8进行分析. ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock. 首先看一下源码中对ReentrantLock的介绍. ...

  8. Java多线程——重入锁ReentrantLock源码阅读

    上一章<AQS源码阅读>讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲). ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解 ...

  9. Java 并发编程之可重入锁 ReentrantLock

    Java 提供了另外一个可重入锁,ReentrantLock,位于 java.util.concurrent.locks 包下,可以替代 synchronized,并且提供了比 synchronize ...

  10. JDK1.8源码分析:可重入锁ReentrantLock和Condition的实现原理

    synchronized的用法和实现原理 synchronized实现线程同步的用法和实现原理 不足 synchronized在线程同步的使用方面,优点是使用简单,可以自动加锁和解锁,但是也存在一些不 ...

最新文章

  1. CVPR2020:点云弱监督三维语义分割的多路径区域挖掘
  2. Scala学习(八)练习
  3. matlab gui设计实例_S-Funciton应用实例
  4. Python学习 Day 046 - DOM 操作 二
  5. Nacos-Nacos和Eureka的对比
  6. 制作html弹窗,js制作一个简单的div弹窗:
  7. php5.4配置gd库,php配置GD库
  8. 墙裂推荐 | 漫画解读Elasticsearch原理,看完你就懂
  9. html编辑器后怎么使用,html在线编辑器怎么用
  10. docker toolbox在win7下的安装
  11. python自动化常用数字_Python全栈自动化系列之Python编程基础(基本数据类型)
  12. dao-service-servlet-jsp构建简易web通讯录(三层开发)软件安装
  13. OpenGL第三方库:glad初始了解与下载
  14. 简单理解:类目、SPU、SKU
  15. Python爬虫系列之poizon爬虫newSign、sign、data加解密算法
  16. 两台电脑直接使用一根网线传输文件
  17. linux 删除swp文件,linux E325: 注意 发现交换文件 *.swp 解决方法
  18. Energy-aware scheduling(Linux Kernel Summit 2013 )(待续)
  19. 半监督学习之伪标签(pseudo label,entropy minimization,self-training)
  20. Allegro中 板框 尺寸标注

热门文章

  1. TECH数字中国2021技术年会 | 神州控股、神州信息、神州数码集团合力打造 “神州信创云”
  2. Android 天气APP(一)开发准备
  3. 经线、纬线、本初子午线、南北东西半球
  4. 二十岁的男人(应该需要做什么)
  5. Docker之介绍与安装
  6. 微信公众号服务器瘫痪的现象,微信出现大范围故障瘫痪30分钟 现已恢复正常
  7. Android:ViewPager详细解释(异步网络负载图片,有图片缓存,)并与导航点
  8. 如何在macOS中重置字体集
  9. Unable to find libthread_db matching inferior‘s thread library, thread debugging will not be availab
  10. 微信小程序上传图片 预览 删除