ReentrantLock解析

最近学习Jdk的源代码时,读到了ConcurrentHashMap的源码实现时,发现每一个分段Segment都是ReentrantLock类型,于是顺带对ReentrantLock的源代码进行了学习。。在这里做一个笔记总结。因为只有在工作之余才能有空看看,所以思路有点零散,仅供参考。。。。

1、如何确定哪个线程可重复进入该锁

在获取锁的时候,首先会检查当前同步对象的阻塞状态,如果已经是被某个线程持有,会检查持有的线程是否就是当前线程。同步对象有一个exclusiveOwnerThread属性用来表征占有此同步对象的线程。

如果当前线程就是持有该同步对象的线程,那么就不用阻塞。

具体逻辑通过下面代码可以表明(FairSync类),可以与nonfairTryAcquire来对比去理解公平锁和非公平锁的含义

protected final boolean tryAcquire(int acquires)
{

final Thread
current = Thread.currentThread();

int c
= getState();

if (c
== 0) {

if (isFirst(current)
&&

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、同一线程多次进入临界区

通过检查拥有同步对象是否为当前线程来确认是否可以运行当前线程进入临界区。同步对象有一个state字段来表明,拥有此同步对象的线程,进入临界区的次数。

3、什么时候临界区是没有加锁状态

同步对象的state字段为0,则表示当前没有任何线程拥有此同步对象。

4、实现的原理和逻辑

内部逻辑通过Sync对象来实现加锁和解锁,重点关注AbstractQueuedSynchronizer抽象类的实现。

使用一个非循环的双向链表(FIFO)来维护等待线程队列,一个线程在进行状态获取的时候,如果获取不能马上获取成功,就会加入到这个队列的队尾中。这个队列中的线程能够运行的条件如“队列访问控制管理”的描述

AbstractQueuedSynchronizer的等待(sync)队列访问控制管理:只有一个线程能够在同一时刻运行,其他的进入等待状态。每个线程都是一个独立的个体,它们自省地观察,当自己的前驱节点是头节点并且已经原子性地获取了状态,这个线程才能运行。

线程进入sync队列之后,接下来就是要进行锁的获取,或者说是访问控制了,只有一个线程能够在同一时刻继续的运行,而其他的进入等待状态。而每个线程都是一 个独立的个体,它们自省的观察,当条件满足的时候(自己的前驱是头结点并且原子性的获取了状态),那么这个线程能够继续运行。

AbstractQueuedSynchronizer维护的队列中线程状态比较有意思:SIGNAL是表明当前节点的下一个节点需要unparking(当成解锁来理解),另外两个状态(CANCELLED和CONDITION则都是表示自身的状态)

AbstractQueuedSynchronizer的解析:

state,当前的同步状态,0表示未加锁,非0表示已加锁,同时对于ReentrantLock来说,这个值表示同一个进程加锁的次数

AQS同步器的核心主要是acquireQueued和shouldParkAfterFailedAcquire

acquireQueued用来处理AbstractQueuedSynchronizer的等待队列,同时检查某一个节点是否能够进入临界区。代码如下

final boolean acquireQueued(final Node
node, int arg) {

try {

boolean interrupted
false;

for (;;)
{

final Node
p = node.predecessor();

if (p
== head && tryAcquire(arg)) {

setHead(node);

p. next = null; //
help GC

return interrupted;

}

if (shouldParkAfterFailedAcquire (p,
node) &&

parkAndCheckInterrupt())

interrupted = true;

}

catch (RuntimeException
ex) {

cancelAcquire(node);

throw ex;

}

}

final Node p = node.predecessor();和if (p
== head && tryAcquire(arg)) 用来检查要检查的节点是否满足进入临界区的条件:当前节点是等待队列中的第一个节点(前驱节点是头节点p==head),同时能够成功加锁(tryAcquire返回true)。如果满足状态,就移动头指针。

如果要检查的节点不满足加锁条件,那就执行到shouldParkAfterFailedAcquire 方法来讲线程进行阻塞

private static boolean shouldParkAfterFailedAcquire(Node
pred, Node node) {

int ws
= pred.waitStatus ;

if (ws
== Node.SIGNAL)

/*

* This node has already set status asking a release

* to signal it, so it can safely park

*/

return true ;

if (ws
> 0) {

/*

* Predecessor was cancelled. Skip over predecessors and

* indicate retry.

*/

do {

node. prev =
pred = pred. prev;

while (pred.waitStatus >
0);

pred. next =
node;

else {

/*

* waitStatus must be 0 or PROPAGATE. Indicate that we

* need a signal, but don‘t park yet. Caller will need to

* retry to make sure it cannot acquire before parking.

*/

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

}

return false ;

}

shouldParkAfterFailedAcquire方法用来检查线程是否满足阻塞的条件,同时会清理掉队列中一些已经过期的节点(已取消,节点的waitStatus大于0),检查原则:

  • 规则1:如果前继的节点状态为SIGNAL,表明当前节点需要unpark,则返回成功,此时acquireQueued方法的第12行(parkAndCheckInterrupt)将导致线程阻塞
  • 规则2:如果前继节点状态为CANCELLED(ws>0),说明前置节点已经被放弃,则回溯到一个非取消的前继节点,返回false,acquireQueued方法的无限循环将递归调用该方法,直至规则1返回true,导致线程阻塞
  • 规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,返回false后进入acquireQueued的无限循环,与规则2同

注意对shouldParkAfterFailedAcquire的调用是在acquireQueued的一个无限循环中调用的,但这个循环最终肯定是会有出口的,就在于shouldParkAfterFailedAcquire会修改前驱节点的状态,最后会使得方法调用到parkAndCheckInterrupt中,完成线程的阻塞。即便最终队列中就只剩下头节点(只是作为头节点标记)和当前节点,无限循环也是有出口,因为初始构造的头节点的waitStatus是为0的。所以最后,会把头节点的waitStatus设置为Node.SIGNAL,这样就会导致对当前节点调用parkAndCheckInterrupt方法。

完成线程阻塞:

对线程完成阻塞是在parkAndCheckInterrupt方法中调用的,通过LockSupport类来实现的。

设置当前线程的阻塞对象(每个线程有一个parkBlocker属性),然后通过系统调用实现线程的阻塞。线程恢复运行后,再把线程的阻塞对象设置为null。

下面是基本的加锁流程图

时间: 2024-10-12 02:46:19

ReentrantLock解析的相关文章

ReentrantLock解析,lock与unlock方法分析

介绍ReentrantLock之前,先介绍下背景知识,也就是要用到的知识点.这些知识点包括:比较并交换CAS(Compare And Swap ).ReentrantLock的类结构(其父类,内部类等). 声明: 我主要是通过一种通俗的语言进行内容的总结,帮助大家更好的理解,记忆,更容易去理解书上的讲解.对于一些专业的陈述,大家还是需要去看书. 1.CAS 举例说明.对于如下类 public class Blog { private int count; public int getCount(

条件锁

ReentrantLock类有一个方法newCondition用来生成这个锁对象的一个条件(ConditionObject)对象,它实现了Condition接口.Condition提供了线程通讯的一套机制await和signal等线程间进行通讯的方法.. 1.适用场景 当某线程获取了锁对象,但因为某些条件没有满足,需要在这个条件上等待,直到条件满足才能够往下继续执行时,就需要用到条件锁. 这种情况下,线程主动在某条件上阻塞,当其它线程发现条件发生变化时,就可以唤醒阻塞在此条件上的线程. 2.使用

第六章 ReentrantLock源码解析2--释放锁unlock()

最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final ReentrantLock lock = new ReentrantLock(); lock.lock();//获取锁 try { a++;//业务逻辑 } catch (Exception e) { }finally{ lock.unlock();//释放锁 } 注:关于lock()方法的源码解析,请参照"第五章

JUC中Lock和ReentrantLock介绍及源码解析

Lock框架是jdk1.5新增的,作用和synchronized的作用一样,所以学习的时候可以和synchronized做对比.在这里先和synchronized做一下简单对比,然后分析下Lock接口以及ReentrantLock的源码和说明.具体的其他的Lock实现的分析在后面会慢慢介绍. Lock框架和synchronized 有关synchronized的作用和用法不在具体说明,应该都很熟悉了.而Lock有着和synchronized一样的语意,但是比synchronized多了一些功能,

ReentrantLock 源码解析

1.个人总结和看法: (1).AQS和ReentrantLock的关系? ReentrantLock是基于AQS的实现的,昨天我们说了AQS的tryAcquire()是默认抛出异常的需要子类去重写逻辑,ReentrantLock就重写了tryAcquire().这样就解释了之前的疑问,因为这本来就是留给子类自己去完成的逻辑. (2).ReentrantLock的锁模式? 默认是非公平获取锁,不过可以在构造是设置公平锁模式获取. (3).为什么ReentrantLock的锁模式默认为非公平锁? 我

聊聊高并发(二十七)解析java.util.concurrent各个组件(九) 理解ReentrantLock可重入锁

这篇讲讲ReentrantLock可重入锁,JUC里提供的可重入锁是基于AQS实现的阻塞式可重入锁.这篇 聊聊高并发(十六)实现一个简单的可重入锁 模拟了可重入锁的实现.可重入锁的特点是: 1. 是互斥锁,基于AQS的互斥模式实现,也就是说同时只有一个线程进入临界区,唤醒下一个线程时也只能释放一个等待线程 2. 可重入,通过设置了一个字段exclusiveOwnerThread来标示当前获得锁的线程.获取锁操作是,如果当前线程是已经获得锁的线程,那么获取操作成功.把当前状态作为获得锁次数的计数器

ReentrantLock锁源码解析

ReentrantLock的常用方法,lock.tryLock和unlock. 截图主要分析部分的源码如下: public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; /** Synchronizer providing all implementation mechanics */ pr

java.util.concurrent.locks包中的ReentrantLock之非公平策略解析

简介: ReentrantLock作为一个可重入互斥锁,具有与Synchronized隐式监视器相同的功能,除此之外,还有更强的扩展性. 如果一个线程调用lock(),如果该锁未被另外一个线程持有,则成功获取锁并返回:如果当前线程已经持有该锁,则直接返回.可以通过isHeldByCurrentThread() 和 getHoldCount()查看该线程是否已持有该锁以及次数. Public ReentrantLock(boolean fairness):此构造器,可以通过设置fairness=t

解析ReentrantLock实现原理

在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock.首先最大的不同:synchronized是基于JVM层面实现的,而Lock是基于JDK层面实现的. 对于使用者的直观体验上Lock是比较复杂的,需要lock和realse,如果忘记释放锁就会产生死锁的问题,所以,通常需要在finally中进行锁的释放.但是synchronized的使用十分简单,只需要对自己的方法或者关注的同步对象或类使用synchronized关键字即可.但是对于锁的粒度控制比较粗,同时对