JUC第十三讲:JUC锁: ReentrantLock详解

JUC第十三讲:JUC锁: ReentrantLock详解

本文是JUC第十三讲,JUC锁:ReentrantLock详解。可重入锁 ReentrantLock 的底层是通过 AbstractQueuedSynchronizer 实现,所以先要学习上一章节 AbstractQueuedSynchronizer 详解。

文章目录

  • JUC第十三讲:JUC锁: ReentrantLock详解
    • 1、带着BAT大厂的面试问题去理解
    • 2、ReentrantLock源码分析
      • 2.1、类的继承关系
      • 2.2、类的内部类
      • 2.3、类的属性
      • 2.4、类的构造函数
      • 2.5、核心函数分析
    • 3、示例分析
      • 3.1、公平锁
    • 4、参考文章

1、带着BAT大厂的面试问题去理解

请带着这些问题继续后文,会很大程度上帮助你更好的理解相关知识点。

  • 什么是可重入,什么是可重入锁? 它用来解决什么问题? 一定程度避免死锁
  • ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。独占模式
  • ReentrantLock是如何实现公平锁的?
  • ReentrantLock是如何实现非公平锁的?
  • ReentrantLock默认实现的是公平还是非公平锁? 非公平
  • 使用ReentrantLock实现公平和非公平锁的示例?
  • ReentrantLock和Synchronized的对比?

2、ReentrantLock源码分析

2.1、类的继承关系

ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。

public class ReentrantLock implements Lock, java.io.Serializable

2.2、类的内部类

ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。
image

说明:ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自 AbstractQueuedSynchronizer 抽象类。下面逐个进行分析。

  • Sync类

Sync类的源码如下:

abstract static class Sync extends AbstractQueuedSynchronizer {// 序列号private static final long serialVersionUID = -5179523762034025860L;// 获取锁abstract void lock();// 非公平方式获取final boolean nonfairTryAcquire(int acquires) {// 当前线程final Thread current = Thread.currentThread();// 获取状态int c = getState();// 表示没有线程正在竞争该锁if (c == 0) {// 比较并设置状态成功,状态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;}// 实现AQS提供的拓展点// 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它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; }// 判断资源是否被当前线程占有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}
}  

Sync类存在如下方法和作用如下。

image

  • NonfairSync类

NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法,源码如下:

// 非公平锁
static final class NonfairSync extends Sync {// 版本号private static final long serialVersionUID = 7316153563782823691L;// 获得锁final void lock() {// 比较并设置状态成功,状态0表示锁没有被占用if (compareAndSetState(0, 1))// 把当前线程设置独占了锁setExclusiveOwnerThread(Thread.currentThread());else // 锁已经被占用,或者set失败// 以独占模式获取对象,忽略中断acquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}

说明:从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。

  • FairSync类

FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法,源码如下:

// 公平锁
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.*/// 尝试公平获取锁  AQS抽象类提供的拓展点protected final boolean tryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();// 获取状态int c = getState();if (c == 0) { // 状态为0// 不存在已经等待更久的线程 并且比较并且设置状态成功if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {// 设置当前线程独占setExclusiveOwnerThread(current);return true;}}// 状态不为0,即资源已经被线程占据else if (current == getExclusiveOwnerThread()) {// 下一个状态int nextc = c + acquires;if (nextc < 0) // 超过了int的表示范围throw new Error("Maximum lock count exceeded");// 设置状态setState(nextc);return true;}return false;}
}

说明:跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。
image

说明:可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。这也是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。

2.3、类的属性

ReentrantLock类的sync非常重要,对 ReentrantLock 类的操作大部分都直接转化为对Sync和 AbstractQueuedSynchronizer 类的操作。

public class ReentrantLock implements Lock, java.io.Serializable {// 序列号private static final long serialVersionUID = 7373984872572414699L;    // 同步队列private final Sync sync;
}

2.4、类的构造函数

  • ReentrantLock() 型构造函数

默认是采用的非公平策略获取锁

public ReentrantLock() {// 默认非公平策略sync = new NonfairSync();
}
  • ReentrantLock(boolean) 型构造函数

可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略:

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

2.5、核心函数分析

通过分析ReentrantLock的源码,可知对其操作都转化为对Sync对象的操作,由于Sync继承了AQS,所以基本上都可以转化为对AQS的操作。如将ReentrantLock的lock函数转化为对Sync的lock函数的调用,而具体会根据采用的策略(如公平策略或者非公平策略)的不同而调用到Sync的不同子类。

所以可知,在ReentrantLock的背后,是AQS对其服务提供了支持,由于之前我们分析AQS的核心源码,遂不再累赘。下面还是通过例子来更进一步分析源码。

3、示例分析

3.1、公平锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class MyThread extends Thread {private Lock lock;public MyThread(String name, Lock lock) {super(name);this.lock = lock;}public void run () {lock.lock();try {System.out.println(Thread.currentThread() + " running");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}
}public class AbstractQueuedSynchronizerDemo {public static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock(true);MyThread t1 = new MyThread("t1", lock);        MyThread t2 = new MyThread("t2", lock);MyThread t3 = new MyThread("t3", lock);t1.start();t2.start();    t3.start();}
}

运行结果(某一次):

Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running

说明: 该示例使用的是公平策略,由结果可知,可能会存在如下一种时序。

image

说明: 首先,t1线程的lock操作 -> t2线程的lock操作 -> t3线程的lock操作 -> t1线程的unlock操作 -> t2线程的unlock操作 -> t3线程的unlock操作。根据这个时序图来进一步分析源码的工作流程。

  • t1线程执行lock.lock,下图给出了方法调用中的主要方法。
    image

说明: 由调用流程可知,t1线程成功获取了资源,可以继续执行。

  • t2线程执行 lock.lock,下图给出了方法调用中的主要方法。

image

说明: 由上图可知,最后的结果是t2线程会被禁止,因为调用了LockSupport.park。

  • t3线程执行lock.lock,下图给出了方法调用中的主要方法。

image
说明:由上图可知,最后的结果是t3线程会被禁止,因为调用了LockSupport.park。

  • t1线程调用了lock.unlock,下图给出了方法调用中的主要方法。
    image

说明:如上图所示,最后,head的状态会变为0,t2线程会被unpark,即t2线程可以继续运行。此时t3线程还是被禁止。

  • t2获得cpu资源,继续运行,由于t2之前被park了,现在需要恢复之前的状态,下图给出了方法调用中的主要方法。

image

说明:在setHead函数中会将head设置为之前head的下一个结点,并且将pre域与thread域都设置为null,在acquireQueued返回之前,sync queue就只有两个结点了。

  • t2执行lock.unlock,下图给出了方法调用中的主要方法。
    image

说明: 由上图可知,最终unpark t3线程,让t3线程可以继续运行。

  • t3线程获取cpu资源,恢复之前的状态,继续运行。

image

说明: 最终达到的状态是sync queue中只剩下了一个结点,并且该节点除了状态为0外,其余均为null。

  • t3执行lock.unlock,下图给出了方法调用中的主要方法。

image

说明: 最后的状态和之前的状态是一样的,队列中有一个空节点,头节点为尾节点均指向它。

使用公平策略和Condition的情况可以参考上一篇关于AQS的源码示例分析部分,不再累赘。

4、参考文章

  • 【JUC】JDK1.8源码分析之ReentrantLock(三)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/146778.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java并发编程 守护线程 用户线程 main

经常使用线程&#xff0c;没有对守护线程和用户线程的区别做彻底了解 下面写4个例子来验证一下 源码如下 /* Whether or not the thread is a daemon thread. */ private boolean daemon false;/*** Marks this thread as either a {linkplain #isDaemon daemon} thread*…

【Java 进阶篇】使用 JDBC 更新数据详解

在关系型数据库中&#xff0c;更新数据是一项常见的任务。通过Java JDBC&#xff08;Java Database Connectivity&#xff09;&#xff0c;我们可以使用Java编程语言来执行更新操作&#xff0c;例如修改、删除或插入数据。本文将详细介绍如何使用JDBC来进行数据更新操作&#x…

第 365 场 LeetCode 周赛题解

A 有序三元组中的最大值 I 参考 B B B 题做法… class Solution { public:using ll long long;long long maximumTripletValue(vector<int> &nums) {int n nums.size();vector<int> suf(n);partial_sum(nums.rbegin(), nums.rend(), suf.rbegin(), [](int x…

Python|OpenCV-如何给目标图像添加边框(7)

前言 本文是该专栏的第7篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在使用opencv处理图像的时候,会不可避免的对图像的一些具体区域进行一些操作。比如说,想要给目标图像创建一个围绕图像的边框。简单的来说,就是在图片的周围再填充一个粗线框。具体效果,…

笔记二:odoo搜索、筛选和分组

一、搜索 1、xml代码 <!--搜索和筛选--><record id"view_search_book_message" model"ir.ui.view"><field name"name">book_message</field><field name"model">book_message</field><field…

【Java 进阶篇】JDBC ResultSet 类详解

在Java应用程序中&#xff0c;与数据库交互通常涉及执行SQL查询以检索数据。一旦执行查询&#xff0c;您将获得一个ResultSet对象&#xff0c;该对象包含查询结果的数据。本文将深入介绍ResultSet类&#xff0c;它是Java JDBC编程中的一个核心类&#xff0c;用于处理查询结果。…

几个推荐程序员养成的好习惯

本文框架 前言case1 不想当然case2 不为了解决问题而解决问题case3 不留问题死角case4 重视测试环节 前言 中秋国庆双节至&#xff0c;旅行or回乡探亲基本是大家的选择&#xff0c;看看风景或陪陪家人确实是个难得的机会。不过我的这次假期选择了闭关&#xff0c;不探亲&#…

layuiselect设置为不可下拉选取

$("#exam").siblings(".layui-form-select").find("dl").remove(); 或 layuiSelectDisable($("#exam")); // 设置selet元素不可下拉选择function layuiSelectDisable(selectElem) {try {var dlElem selectElem.siblings(".layu…

消息队列-RabbitMQ(二)

接上文《消息队列-RabbitMQ&#xff08;一&#xff09;》 Configuration public class RabbitMqConfig {// 消息的消费方json数据的反序列化Beanpublic RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){Simple…

如何用画图将另一个图片中的成分复制粘贴?

一、画图是什么&#xff1f; 画图是Windows自带的一个附件&#xff0c;可于菜单中的Windows附件文件夹中找到&#xff08;自带的为2D画图&#xff0c;有需要的可以下载3D画图&#xff09;&#xff0c;可以用来编辑或查看图片&#xff0c;也可以用来绘制图片&#xff0c;并将图…

扫地机器人经营商城小程序的作用是什么

扫地机器人对人们生活大有帮助&#xff0c;近些年也有不少企业开创品牌&#xff0c;在电商平台每年销量也非常高&#xff0c;同行竞争激烈及私域化程度加深情况下&#xff0c;虽然第三方平台或线下方式也有生意&#xff0c;但互联网电商发展也为商家们带来了诸多痛点。 那么通…

Ant-Design-Vue:a-range-picker组件国际化配置

在使用Ant-Design-Vue中的时间范围选择器开发个人项目时&#xff0c;发现默认显示为英文。如何解决呢&#xff1f; date-picker分类 Antd-Vue提供了DatePicker、MonthPicker、RangePicker、WeekPicker 几种类型的时间选择器&#xff0c;分别用于选择日期、月份、日期范围、周范…

TensorFlow入门(一、环境搭建)

一、下载安装Anaconda 下载地址:http://www.anaconda.comhttp://www.anaconda.com 下载完成后运行exe进行安装 二、下载cuda 下载地址:http://developer.nvidia.com/cuda-downloadshttp://developer.nvidia.com/cuda-downloads 下载完成后运行exe进行安装 安装后winR cmd进…

快速开发微信小程序之一登录认证

一、背景 记得11、12年的时候大家一窝蜂的开始做客户端Android、IOS开发&#xff0c;我是14年才开始做Andoird开发&#xff0c;干了两年多&#xff0c;然后18年左右微信小程序火了&#xff0c;我也做了两个小程序&#xff0c;一个是将原有牛奶公众号的功能迁移到小程序&#x…

CSS基础语法第二天

目录 一、复合选择器 1.1 后代选择器 1.2 子代选择器 1.3 并集选择器 1.4 交集选择器 1.4.1超链接伪类 二、CSS特性 2.1 继承性 2.2 层叠性 2.3 优先级 基础选择器 复合选择器-叠加 三、Emmet 写法 3.1HTML标签 3.2CSS 四、背景属性 4.1 背景图 4.2 平铺方式 …

(Note)机器学习面试题

机器学习 1.两位同事从上海出发前往深圳出差&#xff0c;他们在不同时间出发&#xff0c;搭乘的交通工具也不同&#xff0c;能准确描述两者“上海到深圳”距离差别的是&#xff1a; A.欧式距离 B.余弦距离 C.曼哈顿距离 D.切比雪夫距离 S:D 1. 欧几里得距离 计算公式&#x…

2023.10.02 win7x64sp1下Navicat_Premium15_x86连接Oracle_10g(安装在win2003x86)

Oracle_10g安装在这个版本的系统里: Microsoft Windows [版本 5.2.3790] 这个win2003_x86(分配内存1G)安装在vmware虚拟机里. 安装包文件名为:oracle 10g_win32.zip 大小约624 MB (655,025,354 字节) 安装完毕后,tcp1521端口应该开放: Microsoft Windows [版本 5.2.3790]…

网络工程师就业和身高有关系吗

大家好&#xff0c;我是网络工程师成长日记实验室的郑老师&#xff0c;您现在正在查看的是网络工程师成长日记专栏&#xff0c;记录网络工程师日常生活的点点滴滴 一个哥们问我说&#xff0c;他说他现在准备学网络工程师&#xff0c;但是他男生&#xff0c;他说他个子不是太高。…

IDEA的使用

文章目录 1.IDEA配置1.1 idea界面说明1.2 git1.3 JDK1.4 maven1.5 Tomcat1.6 idea设置编码格式1.7 vscodenodejs1.8 windows下安装redis 2. IDEA问题2.1 setAttribute方法爆红2.2 idea cannot download sources解决办法2.3 springboot项目跑起来不停run 3. vscode3.1 vscode显示…

ThreeJS - 封装一个GLB模型展示组件(TypeScript)

一、引言 最近基于Three.JS&#xff0c;使用class封装了一个GLB模型展示&#xff0c;支持TypeScript、支持不同框架使用&#xff0c;具有多种功能。 &#xff08;下图展示一些基础的功能&#xff0c;可以自行扩展&#xff0c;比如光源等&#xff09; 二、主要代码 本模块依赖…