Java并发编程-ReentrantLock源码分析

一、前言

在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到过ReentrantLock,ReentrantLock表示下面具体分析ReentrantLock源码。

二、ReentrantLock数据结构

ReentrantLock的底层是借助AbstractQueuedSynchronizer实现,所以其数据结构依附于AbstractQueuedSynchronizer的数据结构,关于AQS的数据结构,在前一篇已经介绍过,不再累赘。

三、ReentrantLock源码分析

3.1 类的继承关系

public class ReentrantLock implements Lock, java.io.Serializable
说明:ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。

3.2 类的内部类

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

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

1. 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) { // 表示没有线程正在竞争该锁if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用// 设置当前线程独占
                    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;}// 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它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类存在如下方法和作用如下。

2. NonfairSync类

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

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

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

3. FairSyn类

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.*/// 尝试公平获取锁protected final boolean tryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();// 获取状态int c = getState();if (c == 0) { // 状态为0if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功// 设置当前线程独占
                    setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据// 下一个状态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的方法调用如下,只给出了主要的方法。

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

3.3 类的属性

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

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

3.4 类的构造函数

1. ReentrantLock()型构造函数

public ReentrantLock() {// 默认非公平策略sync = new NonfairSync();}

说明:可以看到默认是采用的非公平策略获取锁。

2. ReentrantLock(boolean)型构造函数

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

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

3.5 类的核心函数分析

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

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

四、示例分析

4.1 公平锁

package com.hust.grid.leesf.abstractqueuedsynchronizer;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 AbstractQueuedSynchonizerDemo {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 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

说明:最后的状态和之前的状态是一样的,队列中的一个结点会被GC,最后队列会为空。

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

五、总结

再掌握了AQS后,再来分析ReentrantLock的源码,就会非常简单,因为ReentrantLock的绝大部分操作都是基于AQS类的。所以,进行分析时要找准方向,就会事半功倍。谢谢各位园友观看~

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

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

相关文章

绿色vmware 安装后看不到虚拟的网卡

绿色vmware 安装后看不到虚拟的网卡&#xff0c;按下面方法就可以添加。 1. 到Vmware 安装目录&#xff0c;运行 vmnetcfg,运行后出现虚拟网络编辑器 2. 转到主机虚拟适配器&#xff0c;单击添加 3. 转到网络连接就可以看到新增的vmware 虚拟网卡 转载于:https://www.cnblogs.c…

C语言strtok函数的用法

strtok是字符串切割函数 定义 参数一&#xff1a;待切割字符串参数二&#xff1a;分隔符集合首次调用时传递参数为&#xff1a; strtok(str,seps);之后再次调用时传递参数为: strtok(NULL,seps);​ 若可以切割&#xff0c;函数返回值为字符指针&#xff0c;各段切割好的字符串…

51单片机C语言led流水灯及数码管实现秒表

51单片机C语言练习题 单片机型号 普中科技的 led练习题 led小灯闪烁 #include<reg52.h> sbit LED P0^0; void main() {unsigned int i0;while(1){LED 1;for(i0;i<60000;i);LED 0;for(i0;i<60000;i);} }led流水灯 #include<reg52.h>void main() {un…

内聚和耦合(自己的理解)

网上对于内聚和耦合的资料太多&#xff0c;这里结合我的感受和其他人的答案http://blog.csdn.net/zhiquan/article/details/4255161谈谈自己的理解 以下是我对内聚和耦合的理解&#xff08;例子来源于生活&#xff09;。 1.内聚&#xff1a; i.偶然内聚&#xff1a;如果一个模块…

(转)交换机攻击方法描述

利用交换机漏洞的攻击方法如下&#xff1a;一、生成树攻击生成树协议(STP)可以防止冗余的交换环境出现回路。要是网络有回路&#xff0c;就会变得拥塞不堪&#xff0c;从而出现广播风暴&#xff0c;引起MAC表不一致&#xff0c;最终使网络崩溃。使用STP的所有交换机都通过网桥协…

无用的设计模式之装饰者模式

为什么80%的码农都做不了架构师&#xff1f;>>> 前言 装饰者设计模式本来是很常用的模式&#xff0c;常用到随处可见&#xff0c;jdk的bio设计都是遵循这个模式的&#xff0c;偶然的机会发现&#xff0c;貌似jdk中bio的装饰者模式和设计模式中的装饰者设计模式却有…

转-HTC 手机生产日期,产地查询 以及 SN码 IMEI码 查询等问题汇总

问&#xff1a;什么是IMEI码&#xff1f;什么是SN码&#xff1f; 答&#xff1a;IMEI码&#xff0c;国际移动装备辨识码&#xff08;International Mobile Equipment Identity number&#xff0c;IMEI&#xff09;。SN码即Serial Number&#xff0c;产品序列号。 问&#xff1a…

C#生成带Logo二维码

1.下载ThoughtWorks.QRCode引用并添加在工程中 2.在实现类QRCodeEncoderDemo中引入Dll&#xff0c;添加方法 using System;using System.Collections.Generic;using System.Drawing;using System.Drawing.Drawing2D;using System.Linq;using System.Web;using ThoughtWorks.QRC…

51单片机实现三位十进制数加减乘除运算

51单片机实现三位十进制数加减乘除运算 一.题目 51单片机IO接口作业 请将附件给出的Proteus图用51单片机完成一个计算器功能。 1、显示采用动态分时8位共阳数码管输出。 2、采用4*4矩阵键盘输入&#xff0c;键盘上已经标识对应键。 3、完成三位十进制数的加、减、乘、除运…

未能加载文件或程序集“Microsoft.Office.Interop.Owc11, Version=11.0.0.0, ...”或它的某一个依赖项~[解决方案]...

配置错误 说明: 在处理向该请求提供服务所需的配置文件时出错。请检查下面的特定错误详细信息并适当地修改配置文件。 分析器错误信息: 未能加载文件或程序集“Microsoft.Office.Interop.Owc11, Version11.0.0.0, Cultureneutral, PublicKeyToken71e9bce111e9429c”或它的某一个…

方法javaJVM学习笔记-内存处理

本文是一篇关于方法java的帖子 大多数JVM将内存区域分离为Method Area&#xff08;Non-Heap&#xff09;&#xff0c;Heap&#xff0c;Program Counter Register&#xff0c;Java Method Statck&#xff0c;Native Method Stack和Direct Memomry&#xff08;备注&#xff1a;Di…

数据结构单向不循环链表实现多项式合并

多项式合并 思路 多项式合并 P1 5 2x 8x ^8 3x^16 P2 6x 16x^6 - 8x^8 P P1 P2 5 8x 16x^6 3x^16 使用带头结点的单向不循环链表 每个节点分为三个部分&#xff0c;系数项&#xff0c;指数项&#xff0c;指针域 结构体表示为 struct node_st {int exponent;int …

下 终端_Linux/UNIX 下终端复用利器 tmux

tmux 是一个终端复用器(terminal multiplexer)。简介tmux 是一个终端复用器类自由软件&#xff0c;功能类似 GNU Screen&#xff0c;但使用 BSD 许可发布。用户可以通过 tmux 在一个终端内管理多个分离的会话&#xff0c;窗口及面板&#xff0c;对于同时使用多个命令行&#xf…

消失的字符串——c语言函数中的数据存储方式以及字符串实现

2019独角兽企业重金招聘Python工程师标准>>> 事情是这样的&#xff0c;最近在学习翁老师的Mooc之C语言程序设计&#xff0c;插一句&#xff0c;真的是好老师&#xff0c;讲的真的太好了&#xff0c;在c里的字符串这里&#xff0c;翁老师演示了一个小程序&#xff0c…

JQuery 文本框高亮显示插件

以前使用Mootools Js框架&#xff0c;文本框有个$(id).highlight()高亮方法&#xff0c;使其高亮显示&#xff0c;效果如下&#xff1a; JQuery 中没有这个方法&#xff0c;自己今天写了一个Plugin&#xff0c;把代码贴出来分享一下&#xff1b; 代码如下&#xff1a; jquery-h…

链方法[C# 基础知识系列]专题三:如何用委托包装多个方法——委托链

最近研究链方法&#xff0c;稍微总结一下&#xff0c;以后继续补充&#xff1a; 弁言&#xff1a; 上一专题分析了下编译器是如何来翻译委托的&#xff0c;从中间语言的角度去看委托&#xff0c;希望可以帮助大家进一步的理解委托&#xff0c;然而之前的分析都是委托只是封装一…

51单片机lcd1602的简单实用

基础知识准备 lcd1602简介 图片来自b站斌哥单片机 需要关注的有以下几点&#xff1a; RS : 高数据低命令&#xff1b;RS 1时&#xff0c;lcd1602读写数据&#xff0c;RS0时&#xff0c;lcd1602读写命令R/W&#xff1a;高读低写&#xff1b;RW1时&#xff0c;单片机读取lcd160…

firedebug调试Jquery

不了解的同学先“点这里”看看什么是Firebug。简单来说&#xff0c;Firebug是Firefox上用来监视、编辑和调试站点的CSS、HTML、DOM和JavaScript的扩展工具。 我们先到Firebug的主页上下载最新的版本装上&#xff0c;下面开始调试&#xff1a; 1、查看、编辑HTML元素及其(转) 安…

51单片机数字钟的实现

作业 用51单片机完成一个数字钟的计时及校时功能。 1、设置8个数码管的显示缓冲区为DISPBUFF[8],并初始化为02,02,-,04,08,-,03,06. 2、定时器0每隔5ms刷新一次数码管显示缓冲区&#xff0c;并显示时间(中断来做&#xff09; 3、定时器1负责精确定时&#xff0c;计时1s,每隔…

Google Map API 学习三

转载于:https://www.cnblogs.com/MyFlora/p/3154066.html