JUC源码分析-重入锁-Reetrantlock

概述

Reetrantlock是Java代码层面提供的锁机制,可做为Synchronized(jvm内置)的替代物,和Synchronized一样都是可重入的。

与Synchronized相比较而言,ReentrantLock有以下优势:支持公平/非公平锁、支持可中断的锁、支持非阻塞的tryLock(可超时)、支持锁条件(Condition)、可跨代码块使用(一个地方加锁,另一个地方解锁),总之比Synchronized更加灵活。但也有缺点,比如锁需要显示解锁、无法充分享用JVM内部性能提升带来的好处等等。

源码分析

Reetrantlock的实现有非公平锁和公平锁两种实现方式,下面逐个分析。

非公平锁

NonfairSync-lock

final void lock() {

//第一次尝试获取锁

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

acquire(1);

}

 

public final void acquire(int arg) {//arg==1

//注意:addWaiter将新创建的node加入了链表尾部。

//第二尝试获取锁

if (!tryAcquire(arg) &&

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

selfInterrupt();//A

}

上述红字代码A保留了打断状态

非公平锁和公平锁都重度依赖了AbstractQueuedSynchronizer,简称AQS。

AbstractQueuedSynchronizer-addWaiter

/**

* 用传入的mode和当前线程创建node,并入队

// 注意: 新创建的node加入了链表尾部。

* @param 当前线程

* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared 互斥和共享两种

* @return 返回新添加的node

*/

private Node addWaiter(Node mode) {

//这里 head tail 的等待队列是双向链表

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

// Try the fast path of enq; backup to full enq on failure

Node pred = tail;

//初始化时 tail==null

if (pred != null) {

node.prev = pred;

//compareAndSetTail 的作用是将node设置为tail

//并发状态下有可能set不成功

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

//初始化node链表。循环set node为tail直到成功

enq(node);

return node;

}

 

//初始化node链表。循环set node为tail直到成功

private Node enq(final Node node) {

for (;;) {

Node t = tail;

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

//初始化head和tail

if (compareAndSetHead(new Node()))

tail = head;

}

else {

//循环set node为tail直到成功

node.prev = t;

if (compareAndSetTail(t, node)) {

t.next = node;

return t;

}

}

}

}

这个队列是双向队列,有head和tail 初始化后 head==tail== new Node(), 插入后的节点挂在尾部。 更新 Head 和tail 这2个共享变量都使用了CAS更新。

 

NonfairSync-tryAcquire

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

Sync-nonfairTryAcquire

//试图获得锁权限

final boolean nonfairTryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {//状态等于0,可以获取锁

//尝试cas赋值,成功则获取锁

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

//当前线程多次执行lock。state 增加acquires,这种情况下 state的值比1大

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;

}

 

排队获取锁方法-acquireQueued

//排队的方式获取锁,如果当前节点的前置节点是头节点,证明可以出队,先尝试获取锁,获取不到,进行一次自旋获取锁,这次获取不到后进入阻塞,等待被唤醒

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

try {

boolean interrupted = false;

for (;;) {

//取出当前节点的prev节点

final Node p = node.predecessor();

//如果p是头节点且CAS可以执行成功,则获得锁。

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

setHead(node);//注意 这里出队的是 node 的pred节点,就是head节点。

// setHead 执行了 node.prev = null; 加上这段代码 GC回收 p;

p.next = null; // help GC

return interrupted;

}

//A&&B A成功了 B才能执行,一种精简的多个函数执行关系的写法

// 如果应该进入阻塞,设置等待唤醒,进入阻塞。

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} catch (RuntimeException ex) {

cancelAcquire(node);

throw ex;

}

}

//如果应该进入阻塞,设置等待唤醒。

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

int ws = pred.waitStatus;

 

if (ws == Node.SIGNAL)

//node 节点节点已经设置了等待唤醒。可以安全的阻塞

return true;

if (ws > 0) {

 

//跳过被取消的节点

do {

node.prev = pred = pred.prev;

} while (pred.waitStatus > 0);

pred.next = node;

} else {

 

//设置等待唤醒状态

//初始化的node 的waitStatus为 0 or PROPAGATE,标识将要进入被唤醒状态,

//需要重试一次以保障在阻塞前不会获取到锁

//为什么状态的改变被放在前一个节点??

//这样做的原因 自旋? 重试一次

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

}

return false;

}

 

这里有个问题: why set前一个节点的状态为等待唤醒?

目前等待队列的结构是 前面的节点的状态是 SIGNAL,表示需要唤醒后面节点代表的线程。不知道为啥如此设计。

 

//阻塞 并返回当前线程是否被打断

private final boolean parkAndCheckInterrupt() {

LockSupport.park(this);

return Thread.interrupted();

}

 

 

释放锁方法unlock

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

//检查锁队列头部结点,释放 队列中阻塞的线程。

//注意这里是 队列中 设置前一个节点的状态值的关键。分析 head节点

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

unparkSuccessor(h);

return true;

}

return false;

}

//释放 node下个节点中阻塞的线程。

private void unparkSuccessor(Node node) {

 

int ws = node.waitStatus;

//被释放了状态需要改变。

if (ws < 0) // 当被打断 ws值 会发生改变。共享锁会改成什么值

compareAndSetWaitStatus(node, ws, 0);

 

//释放正常的下个节点中阻塞的线程。如果不正常则跳过。

Node s = node.next;

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

s = null;

//从后向前找到最接近头部的正常节点

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

if (t.waitStatus <= 0)

s = t;

}

if (s != null)

LockSupport.unpark(s.thread);

}

阻塞后被打断执行了取消处理cancelAcquire,节点状态被修改为CANCELLED,CANCELLED==1

why 从后向前找??

考虑极限情况,执行了多个取消处理,取消的顺序是从head开始,如果跳过取消节点获取节点释放,有可能这个节点将要改成取消状态,造成 要被取消的节点被释放,如果从尾部向前查找,发生问题的概率小。

//在发生异常时调用 cancelAcquire

private void cancelAcquire(Node node) {

// 忽略,如果节点不存在

if (node == null)

return;

node.thread = null;

//跳过被取消的前驱节点。

Node pred = node.prev;

while (pred.waitStatus > 0)

node.prev = pred = pred.prev;

 

//predNext 是需要断开的节点(predNext有可能是node或)

Node predNext = pred.next;

 

// Can use unconditional write instead of CAS here.

// After this atomic step, other Nodes can skip past us.

// Before, we are free of interference from other threads.

node.waitStatus = Node.CANCELLED;

 

//如果node是tail 移除node

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

compareAndSetNext(pred, predNext, null);

} else {

//如果是阻塞或即将进入阻塞的节点,移除这个节点。

if (pred != head &&

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

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

pred.thread != null) {

Node next = node.next;

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

compareAndSetNext(pred, predNext, next);

} else {//此时pred==head 如果是刚加入的节点, 唤醒后续节点,这时节点还没验证能否获得锁就被取消,后续节点有很大可能获取锁,需要唤醒传递。

unparkSuccessor(node);

}

 

node.next = node; // help GC

}

}

公平锁

public void lock() {

sync.lock();

}

 

final void lock() {

acquire(1);

}

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

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

selfInterrupt();

}

公平锁与非公平锁都调用acquireQueued 进入等待队列,它们tryAcquire实现不同。

protected final boolean tryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {

if (!hasQueuedPredecessors() &&//code 1

compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

//。。。

return false;

}

}

Code1 就是公平锁实现不同的地方,公平锁获取锁的时候会校验等待锁队列中是否有等待的线程,如果有则排队。

 

公平锁和非公平锁都有互斥模式,Reetrantlock是互斥模式的典型类,Semaphore是共享模式的典型类。

锁从大的分类上来讲有互斥模式和共享模式的区别,公平锁和非公平锁是实现方式的不同。

小结: 非公平锁加锁的过程,经过两次的直接获取锁,如果不成功,进入队列,经过自检,进入阻塞,等待唤醒。

 

公平锁和非公平锁的区别:非公平锁有两次直接获取锁的机会,而公平锁获取锁的时候会校验等待锁队列中是否有等待的线程,如果有则排队。使用非公平锁,后执行的线程很有可能抢到锁。使用 公平锁,后执行的线程会排队等待先执行的线程。

 

 

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值