热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java并发编程之一张图理解ReentrantLock

原标题:Java并发编程之一张图理解ReentrantLock一张图理解ReentrantLock

原标题:Java并发编程之一张图理解ReentrantLock


一张图理解ReentrantLock



  • 1.lock()跟踪源码


    • 1.1.非公平锁实现


      • 1.1.1.tryAcquire(arg)

      • 1.1.2.acquireQueued(addWaiter(Node.EXCLUSIVE), arg)


    • 1.2.公平锁实现

      • 1.2.1.tryAcquire(arg)



首先看图。
在这里插入图片描述


1.lock()跟踪源码

在这里插入图片描述
这里对公平锁和非公平锁做了不同实现,由构造方法参数决定是否公平。

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


1.1.非公平锁实现

static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
文章来源地址42184.html else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

代码量很少。首先compareAndSetState(0, 1)通过CAS(期望值0,新值1,内存值stateOffset)


  • 如果修改成功,即抢占到锁,setExclusiveOwnerThread(Thread.currentThread());将AQS中的变量exclusiveOwnerThread设置为当前抢占到锁的线程,也就是图中的ThreadA。

  • 若没有抢占成功,证明此时锁被占用,执行方法acquire(1);

public final void acquire(int arg) {
文章来源地址42184.html if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupwww.yii666.comt();
}

这里主要看两个方法tryAcquire(arg)acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。当满足if条件后,会给当前线程标记一个interrupt状态。


1.1.1.tryAcquire(arg)

这个方法又有多个实现。这里看NonfairSync非公平锁。
在这里插入图片描述

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(文章来源站点https://www.yii666.com/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;
}

在这个方法中,还不死心,首先会判断下AQS中的state是否为0,为0也就是说距离上次尝试获取锁到现在准备进入队列(双向链表)中这段时间内,锁已经被释放,可以重新CAS尝试获取锁。

如果当前锁还是被持有状态,就是state!=0,就会判断,当前线程是不是当前持有锁的线程exclusiveOwnerThread,如果是,则state+1,从这里可以看出state表示的是重入次数。

全部不满足,返回false。


1.1.2.acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

addWaiter

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

tryAcquire(arg)返回false,证明当前线程还是没有获取到锁。那么就要进入队列等待了,首先addWaiter方法,将当前线程封装成一个Node,如果pred不为空,则将当前节点做链表的尾部插入,同时为了防止在此期间前序节点已经不在队列中了,也会运用CAS操作来执行(期望值pred,新值node,内存值tailOffset)。

如果前序节点为空,或者在CAS时发现前序节点已经不存在了,则重新构建链表,将当前节点封装的Node,加入到链表当中。

private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

加入完成后,返回当前node节点,进入acquireQueued方法。

acquireQueued

final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取到当前node节点的上一个节点
final Node p = node.predecessor();
//如www.yii666.com果当前的上个节点就是头节点,会再次尝试获取锁
if (p == head && tryAcquire(arg)) {
//获取成功,将当前节点置空,并成为新的头节点
setHead(node);
//这个p已经没用了,防止内存泄漏,直接指向null,下次GC时回收
p.next = null; // help GC
//不需要取消
failed = false;
//return false,不需要中断当前线程
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

这里是一个自旋操作,首先拿到当前线程封装节点的上一个节点,如果满足第一个if条件if (p == head && tryAcquire(arg)),证明上个节点为头节点,则此时当前线程也会再次尝试获取锁,获取锁成功,证明此时没有别的线程在队列中了,则将当前node清空并设置为头节点,返回不需要中断当前线程。

在第二个if条件中if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())。走到这里证明当前线程不是第一个线程节点,或者没有抢占到锁,shouldParkAfterFailedAcquire这个方法见名知意,在抢占失败后是否需要park阻塞,里面主要是用于清理双向链表中被取消的节点线程和未被阻塞的节点线程。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//获取前置节点的等待状态
if (ws == Node.SIGNAL)
//前置节点的等待状态为-1,表示前置节点在队列中阻塞,那么当前节点也需要被阻塞在队列中
return true;
if (ws > 0) {
//前置节点等待状态大于0,此前置节点已经被取消,循环遍历清除所有已被取消的节点。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//前置节点等待状态小于等于0,且不等于-1,也就是没有被阻塞也没有被取消
//则将前置节点设置为阻塞状态。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}


  • 前置节点的等待状态为-1,表示前置节点在队列中阻塞,那么当前节点也需要被阻塞在队列中

  • 前置节点等待状态大于0,此前置节点已经被取消,循环遍历清除所有已被取消的节点。

  • 前置节点等待状态小于等于0,且不等于-1,也就是没有被阻塞也没有被取消。则将前置节点设置为阻塞状态。

到这里,基于非公平锁的实现结束。


1.2.公平锁实现

公平锁和乐观锁的区别就在于,非公平锁acquire(1)前会先尝试获取锁,公平锁直接acquire(1)

static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
}


1.2.1.tryAcquire(arg)

在tryAcquire中也和非公平锁有一定的区别。在当前锁没有被占有时。非公平锁不用考虑目前AQS队列中的排队情况,直接通过CAS尝试获取锁。公平锁会看目前队列的状态,再来决定是尝试占有锁还是在队列中等待。

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;
}

未完待续…

来源于:Java并发编程之一张图理解ReentrantLock


推荐阅读
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • Linux线程的同步和互斥
    目录1、线程的互斥2、可重入VS线程安全3、线程的同步1、线程的互斥 ... [详细]
  • 原标题:Python中numpy.power()函数介绍Python中numpy.power()函数介绍power(x,y)函数, ... [详细]
  • clickhouse 二(springboot+mybatis配置clickhouse,实现插入查询)
    原标题:clickhouse二(springboot+mybatis配置clickhouse,实现插入查询)开发步骤 ... [详细]
  • 直击热门考点——结构体内存对齐
    原标题:直击热门考点——结构体内存对齐文章目录前言一、引例 ... [详细]
  • 20210921c++ 继承,虚继承(内存结构)
    原标题:2021-09-21c++继承,虚继承(内存结构)普通的公有继承 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 给出一群女孩的重量和颜值和她们的朋友关系现在有一个舞台ab是朋友bc是朋友ac就是朋友给出最大承重可以邀请这些女孩来玩对于每一个朋友团体全邀请or邀请一个or不邀请问能邀请的女孩的 ... [详细]
  • [翻译]PyCairo指南裁剪和masking
    裁剪和masking在PyCairo指南的这个部分,我么将讨论裁剪和masking操作。裁剪裁剪就是将图形的绘制限定在一定的区域内。这样做有一些效率的因素࿰ ... [详细]
  • java线程池的实现原理源码分析
    这篇文章主要介绍“java线程池的实现原理源码分析”,在日常操作中,相信很多人在java线程池的实现原理源码分析问题上存在疑惑,小编查阅了各式资 ... [详细]
  • QuestionThereareatotalofncoursesyouhavetotake,labeledfrom0ton-1.Somecoursesmayhaveprerequi ... [详细]
  • const限定符全解一、const修饰普通变量  intconsta500;  constinta600;  上述两种情况相同,都是声明一个const型的变量,它们 ... [详细]
author-avatar
隔壁老吴
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有