java中密码修改核心代码_java中ReentrantLock核心源码详解

ReentrantLock简介

ReentrantLock是一个可重入且独占式的锁,它具有与使用synchronized监视器锁相同的基本行为和语义,但与synchronized关键字相比,它更灵活、更强大,增加了轮询、超时、中断等高级功能。ReentrantLock,顾名思义,它是支持可重入锁的锁,是一种递归无阻塞的同步机制。除此之外,该锁还支持获取锁时的公平和非公平选择。

ReentrantLock的类图如下:

9de55260326ad28ed52979b1d4d2d255.png

ReentrantLock的内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync。如果在绝对时间上,先对锁进行获取的请求你一定先被满足,那么这个锁是公平的,反之,是不公平的。公平锁的获取,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。ReentrantLock的公平与否,可以通过它的构造函数来决定。

事实上,公平锁往往没有非公平锁的效率高,但是,并不是任何场景都是以TPS作为唯一指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越能够得到优先满足。

下面我们看一下ReentrantLock在java中是如何实现的

1、介绍ReentrantLock中加锁的逻辑(以公平锁为例,非公平锁类似,而且比非公平锁简单)

//创建一个公平锁

ReentrantLock lock = new ReentrantLock(true);

lock.lock();

//调用ReentrantLock中的lock方法

public void lock() {

sync.lock();

}

//syn的lock方法位抽象方法abstract void lock();

//调用公平锁的lock方法

final void lock() {

acquire(1);

}

//调用AQS的acquire方法

//尝试获取锁,如果获取锁失败,则把他加入到等待队列中,然后park自己

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

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

//做一些与中断的逻辑

selfInterrupt();

}

//tryAcquire中tryAcquire方法

/**

* 尝试获取锁,获取锁成功,返回true,反之返回false

*/

protected final boolean tryAcquire(int acquires) {

//获取当前线程

final Thread current = Thread.currentThread();

int c = getState();

//c=0 表示现在正在执行的线程数为0个,则重试获取锁

if (c == 0) {

//条件一成功:说明当前阻塞队列中没有等待者线程

//条件二成立:说明当前线程竞争锁成功

if (!hasQueuedPredecessors() &&

compareAndSetState(0, acquires)) {

//将当前线程设置为独占者线程,返回true

setExclusiveOwnerThread(current);

return true;

}

}

//c!=0 判断当前是否为重入状态

else if (current == getExclusiveOwnerThread()) {

//这边不需要加锁,因为只有线程可以到达这儿

int nextc = c + acquires;

//线程数目最大为Integer>MAX_VALUE

if (nextc < 0)

throw new Error("Maximum lock count exceeded");

setState(nextc);

//返回true

return true;

}

return false;

}

}

/**

* 调用AQS的hasQueuedPredecessors方法

* 如果当前线程前面有等待者线程,返回true,反之返回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.

//返回false的3种情况

//case1 当前阻塞队列中没有元素 head = tail == Null

//case2 当前阻塞队列中只有一个元素 head = tail

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());

}

/**

* 当前线程入队

* 返回当前线程对应的Node节点

* addWaiter方法执行完毕后,保证当前线程已经入队成功!

*/

private Node addWaiter(Node mode) {

Node node = new Node(Thread.currentThread(), mode);

//尝试把他加到尾节点之后

Node pred = tail;

if (pred != null) {

node.prev = pred;

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

enq(node);

return node;

}

/**

* 完整入队

* 返回当前线程的前置节点

* 什么时候会执行到这里呢?

* 1.当前队列是空队列 tail == null

* 2.CAS竞争入队失败..会来到这里..

*/

private Node enq(final Node node) {

for (;;) {

Node t = tail;

//1.当前队列是空队列 tail == null

//说明当前 锁被占用,且当前线程 有可能是第一个获取锁失败的线程(当前时刻可能存在一批获取锁失败的线程...)

if (t == null) { // Must initialize

if (compareAndSetHead(new Node()))

tail = head;

} else {

node.prev = t;

if (compareAndSetTail(t, node)) {

t.next = node;

return t;

}

}

}

}

/**

* acquireQueued 需要做什么呢?

* 1.当前节点有没有被park? 挂起? 没有 ==> 挂起的操作

* 2.唤醒之后的逻辑在哪呢? ==> 唤醒之后的逻辑。

*

* @param node 就是当前线程包装出来的node,且当前时刻 已经入队成功了..

* @param arg 当前线程抢占资源成功后,设置state值时 会用到。

* @return

*/

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

boolean failed = true;

try {

boolean interrupted = false;

for (;;) {

//找到当前节点的前驱节点

final Node p = node.predecessor();

//如果当前节点的前驱节点为空,并且尝试获取锁成功

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

setHead(node);

p.next = null; // help GC

failed = false;

return interrupted;

}

//park的逻辑

// shouldParkAfterFailedAcquire(p, node):判断当前线程是否要挂起,有可能会访问2次才挂起

//parkAndCheckInterrupt:挂起的方法 判断是由于中断唤醒还是普通唤醒

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} finally {

//取消正在尝试获取锁的线程

if (failed)

cancelAcquire(node);

}

}

/**

* 总结:

* 1.当前节点的前置节点是 取消状态 ,第一次来到这个方法时 会越过 取消状态的节点, 第二次 会返回true 然后park当前线程

* 2.当前节点的前置节点状态是0,当前线程会设置前置节点的状态为 -1 ,第二次自旋来到这个方法时 会返回true 然后park当前线程.

*

* 参数一:pred 当前线程node的前置节点

* 参数二:node 当前线程对应node

* 返回值:boolean true 表示当前线程需要挂起..

*/

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

int ws = pred.waitStatus;

//当前节点是可以正常的被park,说明当前线程的thread需要被唤醒

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;

}

/**

* 将当前线程挂起,并且返回中断信号

*/

private final boolean parkAndCheckInterrupt() {

LockSupport.park(this);

return Thread.interrupted();

}

/**

* 可以做一些中断响应

*/

static void selfInterrupt() {

Thread.currentThread().interrupt();

}

2、介绍ReentrantLock中退出锁的逻辑(以公平锁为例,非公平锁逻辑一模一样)

//调用ReentrantLock的unlock方法

lock.unlock();

public void unlock() {

sync.release(1);

}

//调用aqs的release方法public final boolean release(int arg) {

//head什么情况下会被创建出来?

//当持锁线程未释放线程时,且持锁期间 有其它线程想要获取锁时,其它线程发现获取不了锁,而且队列是空队列,此时后续线程会为当前持锁中的

//线程 构建出来一个head节点,然后后续线程 会追加到 head 节点后面。

if (tryRelease(arg)) {

Node h = head;

//条件一:成立,说明队列中的head节点已经初始化过了,ReentrantLock 在使用期间 发生过 多线程竞争了...

//条件二:条件成立,说明当前head后面一定插入过node节点。

if (h != null && h.waitStatus != 0)

//唤醒后继节点..

unparkSuccessor(h);

return true;

}

return false;

}

//ReentrantLock的tryRelease方法

protected final boolean tryRelease(int releases) {

//减去释放的值

int c = getState() - releases;

//条件成立,说明当前线程并没有获取到锁,直接抛出异常

if (Thread.currentThread() != getExclusiveOwnerThread())

throw new IllegalMonitorStateException();

//是否已经完全释放锁..默认false

boolean free = false;

//代表了当前线程已经完全释放锁了

if (c == 0) {

//将全局的exclusiveOwnerThread设置

free = true;

setExclusiveOwnerThread(null);

}

setState(c);

return free;

}

/**

* 唤醒当前节点的下一个节点。

*/

private void unparkSuccessor(Node node) {

int ws = node.waitStatus;

//改成零的原因:因为当前节点已经完成喊后继节点的任务了..

if (ws < 0)

compareAndSetWaitStatus(node, ws, 0);

//s是当前节点 的第一个后继节点。

//条件一:

//s 什么时候等于null?

//1.当前节点就是tail节点时 s == null。

//2.当新节点入队未完成时(1.设置新节点的prev 指向pred 2.cas设置新节点为tail 3.(未完成)pred.next -> 新节点 )

//需要找到可以被唤醒的节点..

//条件二:s.waitStatus > 0 前提:s != null

//成立:说明 当前node节点的后继节点是 取消状态... 需要找一个合适的可以被唤醒的节点..

Node s = node.next;

if (s == null || s.waitStatus > 0) {

s = null;

//上面循环,会找到一个离当前node最近的一个可以被唤醒的node。 node 可能找不到 node 有可能是null、、

for (Node t = tail; t != null && t != node; t = t.prev)

if (t.waitStatus <= 0)

s = t;

}

//如果找到合适的可以被唤醒的node,则唤醒.. 找不到 啥也不做。

if (s != null)

LockSupport.unpark(s.thread);

}

3、介绍ReentrantLock中中断加锁的逻辑(以公平锁为例,非公平锁类似,只是tryAcquire方法不同)

中断加锁和普通加锁的区别:

线程尝试获取锁操作失败后,在等待过程中,如果该线程被其他线程中断了,它是如何响应中断请求的。lock方法会忽略中断请求,

继续获取锁直到成功;而lockInterruptibly则直接抛出中断异常来立即响应中断,由上层调用者处理中断。

//响应中断的加锁方式

public void lockInterruptibly() throws InterruptedException {

sync.acquireInterruptibly(1);

}

//响应中断的获取方式

public final void acquireInterruptibly(int arg)

throws InterruptedException {

//如果方式中断,不进行锁获取

if (Thread.interrupted())

throw new InterruptedException();

//如果获取锁失败,会尝试进入等待队列,然后将自己挂起,等待唤醒,如果有中断则抛出异常,不再等待唤醒

if (!tryAcquire(arg))

doAcquireInterruptibly(arg);

}

//如果有中断直接抛出异常(逻辑与上面lock方法类似,不写注释了)private void doAcquireInterruptibly(int arg)

throws InterruptedException {

final Node node = addWaiter(Node.EXCLUSIVE);

boolean failed = true;

try {

for (;;) {

final Node p = node.predecessor();

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

setHead(node);

p.next = null; // help GC

failed = false;

return;

}

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

throw new InterruptedException();

}

} finally {

if (failed)

cancelAcquire(node);

}

}

/**

* 取消指定node参与竞争。

*/

private void cancelAcquire(Node node) {

// 空判断

if (node == null)

return;

//因为已经取消排队了..所以node内部关联的当前线程,置为Null就好了。。

node.thread = null;

//获取当前取消排队node的前驱。

Node pred = node.prev;

while (pred.waitStatus > 0)

node.prev = pred = pred.prev;

//拿到前驱的后继节点。

//1.当前node

//2.可能是其他 ws > 0 的节点。

Node predNext = pred.next;

//将当前node状态设置为 取消状态 1

node.waitStatus = Node.CANCELLED;

/**

* 当前取消排队的node所在 队列的位置不同,执行的出队策略是不一样的,一共分为三种情况:

* 1.当前node是队尾 tail -> node

* 2.当前node 不是 head.next 节点,也不是 tail

* 3.当前node 是 head.next节点。

*/

// 如果当前节点为队尾节点,cas设置当前节点的前驱节点为队尾节点

if (node == tail && compareAndSetTail(node, pred)) {

//修改pred.next -> null. 完成node出队。

compareAndSetNext(pred, predNext, null);

} else {

// If successor needs signal, try to set pred's next-link

// so it will get one. Otherwise wake it up to propagate.

//第二种情况:当前node 不是 head.next 节点,也不是 tail

//条件一:pred != head 成立, 说明当前node 不是 head.next 节点,也不是 tail

//条件二: ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)))

//条件2.1:(ws = pred.waitStatus) == Node.SIGNAL

// 成立:说明node的前驱状态是 Signal 状态 不成立:前驱状态可能是0 ,极端情况下:前驱也取消排队了..

//条件2.2:(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))

// 假设前驱状态是 <= 0 则设置前驱状态为 Signal状态..表示要唤醒后继节点。

//if里面做的事情,就是让pred.next -> node.next ,所以需要保证pred节点状态为 Signal状态。

int ws;

if (pred != head &&

((ws = pred.waitStatus) == Node.SIGNAL ||

(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&

pred.thread != null) {

Node next = node.next;

//情况2:当前node 不是 head.next 节点,也不是 tail

//出队:pred.next -> node.next 节点后,当node.next节点 被唤醒后

//调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点

//完成真正出队。

if (next != null && next.waitStatus <= 0)

compareAndSetNext(pred, predNext, next);

} else {

//当前node 是 head.next节点。 更迷了...

//类似情况2,后继节点唤醒后,会调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点

//队列的第三个节点 会 直接 与 head 建立 双重指向的关系:

//head.next -> 第三个node 中间就是被出队的head.next 第三个node.prev -> head

unparkSuccessor(node);

}

node.next = node; // help GC

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值