JDK7中ReentrantLock源码解析(2)
来源:互联网 发布:mac六国循环重启 恢复 编辑:程序博客网 时间:2024/06/07 16:38
在文章http://ericchunli.iteye.com/blog/2393145中简单的介绍了锁的获取,可知道公平锁对于锁的获取需要借助AQS中CLH队列来共同实现。Node是CLH队列的节点,也是等待锁的线程队列,Node源码如下:
static final class Node {
// 当前节点是分享模型还是独占模型
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// 线程的状态
static final int CANCELLED = 1; // 超时或者中断导致线程取消
static final int SIGNAL= -1; // 当前线程唤醒后继线程
static final int CONDITION = -2; // 处在condition休眠状态需要被唤醒
// 下个释放的共享锁应该无条件的传播给其它节点,只存在头节点中
static final int PROPAGATE = -3;
volatile int waitStatus; // 保存线程的等待状态
volatile Node prev;
volatile Node next; // 等待线程节点
volatile Thread thread; //每个节点都会有线程对应
Node nextWaiter; // 独占锁or共享锁线程
final boolean isShared(){return nextWaiter == SHARED;}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null) throw new NullPointerException();
else return p;
}
Node() { }
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
CLH队列的头部和尾部,都通过懒加载初始化,分别通过setHead和enq方法修改
private transient volatile Node head;
private transient volatile Node tail;
接下来接着介绍公平锁的acquire源码实现:
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter(Node.EXCLUSIVE)源码解析:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 若CLH队列不为空则将新创建的Node添加到队列尾部
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 若CLH队列为空则新创建队列,然后把节点添加到队列中
enq(node);
return node;
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
// 将节点压入队列中
private Node enq(final Node node) {
for (;;) { // CAS存在,自旋直到成功
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;
}
}
}
}
以上AQS中的实现源码比较简单,不存在则初始化创建,存在则添加到队列尾部。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)源码解析:
// 获取在队列中已经存在的独占不可中断模式的线程,然后被Condition的wait()和acquire()使用。获取CLH队列中的未中断的独占模式的线程后执行,acquireQueued在这里获取锁,如果当前线程获取到了锁则返回;否则当前线程进行休眠,直到唤醒并重新获取锁了才返回(注意这里的未被中断过以及公平性原则表示)。
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)) {
// 前继节点释放锁后,当前节点通过tryAcquire获得锁
setHead(node); // 成功获取设置当前节点为头结点
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
// 如果当前线程需要被阻塞,则调用parkAndCheckInterrupt阻塞当前线程
interrupted = true;
}
}finally{
if (failed) cancelAcquire(node);
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
检查和更新一个未获得锁的节点的状态,如果线程阻塞则返回true,这是所有获取循环中的主唤醒控制,要求pred == node.prev。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // 前继节点的线程为SIGNAL的线程的后继线程需要被唤醒
// 节点已经设置了状态,请求释放来唤醒它,这样它就可以安全地阻塞了
return true;
if (ws > 0) { // 取消,当前线程不需要被唤醒
// 前继节点已经取消则跳过前继结点并且进行重试
do {
node.prev = pred = pred.prev;
}while(pred.waitStatus > 0);
pred.next = node;
}else{
// waitStatus必须为0或PROPAGATE,这表明需要唤醒但不要阻塞。调用者需要重试,以确保在阻塞前不能获得。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);
}
private static final boolean compareAndSetNext(Node node,Node expect,Node update){
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
// 阻塞当前线程,返回中断状态(应该是被唤醒后的中断状态)
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
Note:当线程被解除阻塞(被中断或调用unpark方法,前继节点的线程唤醒)的时候会返回中断的状态。
// 取消正在进行的获取的尝试
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是unsplice的明显节点,不是这样的话CAS将会失败,这种情况会失去竞争vs其它取消或唤醒,所以没有必要采取进一步的行动
Node predNext = pred.next;
// 可用无条件的写代替CAS,当前原子步骤之后,其它节点可以跳过,在此之前不受其它线程的干扰。
node.waitStatus = Node.CANCELLED;
// 尾部节点直接移除
if(node == tail && compareAndSetTail(node, pred)){
compareAndSetNext(pred, predNext, null);
}else{
// 如果后置节点需要被唤醒,尝试设置pred的下一个链接,这样就会得到一个,否则把它唤醒以传播。
int ws;
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{
unparkSuccessor(node); // 唤醒后继节点
}
node.next = node; // help GC
}
}
// 如果存在后继节点则进行唤醒
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) // 需要被唤醒
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);
}
selfInterrupt()在AQS中实现源码如下:
private static void selfInterrupt() {
// parkAndCheckInterrupt不但返回中断状态,而且还清除中断状态
Thread.currentThread().interrupt(); // 重新产生中断
}
利用CLH尝试获得锁实现总结【head[h],tail[t],Node[s]】:
(1)公平锁在尝试获得锁的时候会判断队列中存不存在等待时间更长的节点,判断标准实现如下:h!=t&&((s =h.next)== null||s.thread!=Thread.currentThread())。尝试获取失败则会把当前节点压入CLH队列;
(2)若CLH队列不为空则将新创建的Node添加到队列尾部,若CLH队列为空则新创建队列后把节点添加到队列中【CAS内存对象的设置】,此处是公平性的保证;
(3)Node节点压入队列后会重新尝试获得锁,可能会存在休眠或者取消时阻塞并返回中断状态【会导致自我中断的实现】;
(4)如果失败获取锁则会进行取消正在进行的获取的尝试
Note:Node中提供了waitStatus来判断当前节点的状态,通过判断Node的waitStatus的值来进行前继节点,当前节点,后继节点以及尾部节点的处理,处理完后会通过LockSupport实现对当前线程的park()和unpark()。
- JDK7中ReentrantLock源码解析(2)
- JDK7中ReentrantLock源码解析(1)
- JDK7中ReentrantLock源码解析(3)
- JDK7中ReentrantReadWriteLock源码解析(2)
- JDK7中LockSupport源码解析
- JDK7中AtomicInteger源码解析
- JDK7中ArrayBlockingQueue源码解析
- JDK7中LinkedBlockingQueue源码解析
- JDK7中SynchronousQueue源码解析
- JDK7中StringBuffer/StringBuilder源码解析
- JDK7中ReentrantReadWriteLock源码解析(1)
- JDK7中ReentrantReadWriteLock源码解析(3)
- Reentrantlock源码解析
- ReentrantLock源码解析
- 深度解析ReentrantLock源码
- ReentrantLock源码解析
- ReentrantLock源码解析
- 源码解析 ReentrantLock
- python3中的错误和异常种类
- spring cloud微服务分布式云架构集成项目简介
- 梯度下降法-最速下降法
- 进阶攻略|前端最全的框架总结
- 深入理解include预编译原理
- JDK7中ReentrantLock源码解析(2)
- MyBatis知识简介
- 子序列的个数
- dpdk 调试(log)小结(终端打印或是输出到文件)
- (五)Java设计模式之建造模式
- 聊聊TCP四次挥手中的timewait
- Linux命令简写和全称—方便记忆
- JDK7中ReentrantLock源码解析(3)
- 用webpack工具打包echarts项目