上节,我们讲了AQS的阻塞与释放实现原理,线程间通信(Condition)的原理。这次,我们就讲讲基于AQS实现的ReentrantLock(重入锁)。

1. 介绍



结合上面的ReentrantLock类图,ReentrantLock实现了Lock接口,它的内部类Sync继承自AQS,绝大部分使用AQS的子类需要自定义的方法存在Sync中。而ReentrantLock有公平与非公平的区别,即'是否先阻塞就先获取资源',它的主要实现就是FairSyncNonfairSync,后面会从源码角度看看它们的区别。

2. 源码剖析

SyncReentrantLock控制同步的基础。它的子类分为了公平与非公平。使用AQSstate代表获取锁的数量

    abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L; /**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock(); ...
}

我们可以看出内部类Sync是一个抽象类,继承它的子类(FairSyncNonfairSync)需要实现抽象方法lock

下面我们先从非公平锁的角度来看看获取资源与释放资源的原理

故事就从就两个变量开始:

    // 获取一个非公平的独占锁
/**
* public ReentrantLock() {
* sync = new ReentrantLock.NonfairSync();
* }
*/
private Lock lock = new ReentrantLock();
// 获取条件变量
private Condition condition = lock.newCondition();

2.1 上锁(获取资源)

   lock.lock()
    public void lock() {
sync.lock();
}
    static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L; // 获取资源
final void lock() {
// 若此时没有线程获取到资源,直接设置当前线程独占访问资源。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// AQS的方法
acquire(1);
} protected final boolean tryAcquire(int acquires) {
// 实现在父类Sync中
return nonfairTryAcquire(acquires);
}
}

AQSacquire

    public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

上一节已经剖析过AQSacquire的整个流程了,就差子类如何去实现tryAcquire了。

  // Sync实现的非公平的tryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 此时若没有线程获取到资源,当前线程就直接占用该资源
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 若当前线程已经占用了该资源,可以再次获取该资源 ->这个行为就是可重入锁的支撑
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

尝试获取资源的过程是非常简单的,这里再贴一下acquire的流程图

2.2 释放资源

    lock.unlock();
    public void unlock() {
// AQS的方法
sync.release(1);
}

AQSrelease

    public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

release的流程已经剖析过了,接下来看看tryRelease的实现

        protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 这里可以看出若没有持有锁,就释放资源,就会报错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

tryRelease的实现也很简单,这里再贴一下release的流程图

2.3 公平锁与非公平锁的区别

公平锁与非公平锁,即'是否先阻塞就先获取资源', ReentrantLock公平与否的控制就在tryAcquire中。下面我们看看,公平锁的tryAcquire

static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L; final void lock() {
acquire(1);
} protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// (2.3.1)
// sync queue中是否存在前驱结点
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;
}
}

区别在代码(2.3.1)

hasQueuedPredecessors

判断当前线程的前面有无其他线程排队;若当前线程在队列头部或者队列为空返回false

    public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

3. 总结

本文介绍了利用AQS实现的锁ReetrantLock

  • 讲解了tryReleasetryAcquire的实现原理
  • 说了说锁的公平与否的实现,是否在意当前线程是否有其他线程排队

JAVA并发(2)-ReentrantLock的见解的更多相关文章

  1. Java并发编程-ReentrantLock

    代码示例: Lock lock = new ReentrantLock(); lock.lock(); try { // update object state } finally { lock.un ...

  2. Java并发编程-ReentrantLock源码分析

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

  3. Java并发编程 ReentrantLock 源码分析

    ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(Abst ...

  4. Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)

    AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...

  5. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析

    前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...

  6. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介

    注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...

  7. Java并发编程总结3——AQS、ReentrantLock、ReentrantReadWriteLock(转)

    本文内容主要总结自<Java并发编程的艺术>第5章——Java中的锁. 一.AQS AbstractQueuedSynchronizer(简称AQS),队列同步器,是用来构建锁或者其他同步 ...

  8. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  9. java并发编程——通过ReentrantLock,Condition实现银行存取款

         java.util.concurrent.locks包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器.该框架允许更灵活地使用锁和条件,但以更难用的语法为代价. Lock 接口 ...

随机推荐

  1. 错误提示:Access denied for user 'GC'@'localhost' (using password: YES)

    错误描述:使用的是C3P0连接池 Spring整合Mybatis时出现错误 java.sql.SQLException: Access denied for user 'GC'@'localhost' ...

  2. 国产mcu理论数据评估工作记录

    目录 前言 简要工作记录 前言 时间:20210315 主要记录这两天对国内各IC厂商的 MCU 了解记录. 大环境,ST 厂商 MCU 疯狂涨价,国内 MCU 也越来越完善,还便宜.同时,全球缺晶圆 ...

  3. Maven项目中resources配置总结

    目录 背景 第一部分 基本配置介绍 第二部分 具体配置和注意事项 第三部分 读取resources资源 参考文献及资料 背景 通常Maven项目的文件目录结构如下: # Maven项目的标准目录结构 ...

  4. Android 之 使用 Intent 在活动间传递数据

    •前言 继上次学习了<通过 Intent 完成点击按钮实现页面跳转>后,我们知道了如何通过 Intent 实现页面跳转: Intent 除了可以实现页面跳转外,还可以在跳转的时候传递数据: ...

  5. MyBatis-Plus Generator自定义模板

    相信大家在开发过程中,应该都用过Mybatis-Plus的Generator,但是可能没有自定义过模板并使用. 每个项目都应该有一个从Controller层到Mapper层的通用模板,来去掉哪些简单的 ...

  6. [leetcode] 单调栈

    本文总结单调栈算法. 原问题 学习一个算法,我们需要清楚的是:这个算法最原始的问题背景是什么样的? 下一个更小元素 给定一个数组 nums,返回每个元素的下一个更小的元素的下标 res,即 res[i ...

  7. Python使用sql语句对mysql数据库多条件模糊查询

    1 def find_worldByName(c_name,continent): 2 print(c_name) 3 print(continent) 4 sql = " SELECT * ...

  8. 【JVM进阶之路】十:JVM调优总结

    1.调优原则 JVM调优听起来很高大上,但是要认识到,JVM调优应该是Java性能优化的最后一颗子弹. 比较认可廖雪峰老师的观点,要认识到JVM调优不是常规手段,性能问题一般第一选择是优化程序,最后的 ...

  9. linux下Mysql 8.0.19 编译安装

    1 前言 linux下安装MySQL的方式有很多种,包括以仓库的方式安装(yum,apt,zypper),以包的方式安装(rpm,deb),以docker方式安装,从压缩包解压安装,从源码编译安装,这 ...

  10. 7.bug生命周期

    new:测试发现并提交bug,状态为new/active; open: 分配bug到开发人员,状态为open: fixed:开发人员处理完bug,将状态改为fixed: closed/reopen:测 ...