ReentrantLock源码分析(一)基于ConditionObject实现线程挂起和唤醒

一、ConditionObject的介绍&应用


synchronized提供了wait和notify的方法实现线程在持有锁时,可以实现挂起,唤醒的操作。同样我们在ReentrantLock也拥有类似的功能,
ReentrantLock提供了await和signal方法去实现类似wait和notify的功能。想执行await或者是signal就必须先持有lock锁的资源,
ReentrantLock实现等待和唤醒的示例:

public static void main(String[] args) throws InterruptedException, IOException {
    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    new Thread(() -> {
        lock.lock();
        System.out.println("子线程获取锁资源并await挂起线程");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程挂起后被唤醒!持有锁资源");

    }).start();
    Thread.sleep(100);
    // =================main======================
    lock.lock();
    System.out.println("主线程等待5s拿到锁资源,子线程执行了await方法");
    condition.signal();
    System.out.println("主线程唤醒了await挂起的子线程");
    lock.unlock();
}

 二、Condition的构建方式&核心属性

调用lock.newCondition()方法本质就是new一个AQS提供的ConditionObject对象。

final ConditionObject newCondition() {
    return new ConditionObject();
}

 在一个lock锁中有多个Condition对象,

在对Condition1进行操作时,不会影响到Condition2的单向链表。

其次可以发现ConditionObject中,只有两个核心属性:

/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;

 虽然Node对象有prev和next,但是在ConditionObject中是不会使用这两个属性的,只要在Condition队列中,这两个属性都是null。在ConditionObject中只会使用nextWaiter的属性实现单向链表的效果。

三、Condition的await方法分析(前半截分析)

持有锁的线程在执行await方法后会做几个操作:

1、判断线程是否中断,如果中断了,什么都不做。
2、 没有中断,就讲当前线程封装为Node添加到Condition的单向链表中
3、 一次性释放掉锁资源
4、如果当前线程没有在AQS队列,就正常执行LockSupport.park(this)挂起线程

1、await()方法分析

// await方法的前置分析,只分析到线程挂起
public final void await() throws InterruptedException {
    // 先判断线程的中断标记位是否是true
    if (Thread.interrupted())
        // 如果是true,就没必要执行后续操作挂起了。
        throw new InterruptedException();
    // 在线程挂起之前,先将当前线程封装为Node,并且添加到Condition队列中
    Node node = addConditionWaiter();
    // fullyRelease在释放锁资源,一次性将锁资源全部释放,并且保留重入的次数
    int savedState = fullyRelease(node);
    // 省略一行代码……
    // 当前Node是否在AQS队列中?
    // 执行fullyRelease方法后,线程就释放锁资源了,如果线程刚刚释放锁资源,其他线程就立即执行了signal方法,
    // 此时当前线程就被放到了AQS的队列中,这样一来线程就不需要执行LockSupport.park(this);去挂起线程了
    while (!isOnSyncQueue(node)) {
        // 如果没有在AQS队列中,正常在Condition单向链表里,正常挂起线程。
        LockSupport.park(this);
        // 省略部分代码……
    }
    // 省略部分代码……
}

2、addConditionWaiter方法分析 

// 线程挂起先,添加到Condition单向链表的业务~~
private Node addConditionWaiter() {
    // 拿到尾节点。
    Node t = lastWaiter;
    // 如果尾节点有值,并且尾节点的状态不正常,不是-2,尾节点可能要拜拜了~
    if (t != null && t.waitStatus != Node.CONDITION) {
        // 如果尾节点已经取消了,需要干掉取消的尾节点~
        unlinkCancelledWaiters();
        // 重新获取lastWaiter
        t = lastWaiter;
    }
    // 构建当前线程的Node,并且状态设置为-2
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 如果last节点为null。直接将当前节点设置为firstWaiter
    if (t == null)
        firstWaiter = node;
    else
        // 如果last节点不为null,说明有值,就排在lastWaiter的后面
        t.nextWaiter = node;
    // 把当前节点设置为最后一个节点
    lastWaiter = node;
    // 返回当前节点
    return node;
}

 3、干掉已经取消的尾节点

// 干掉取消的节点。
private void unlinkCancelledWaiters() {
    // 拿到头节点
    Node t = firstWaiter;
    // 声明一个节点,爱啥啥~~~
    Node trail = null;
    // 如果t不为null,就正常执行~~
    while (t != null) {
        // 拿到t的next节点
        Node next = t.nextWaiter;
        // 如果t的状态不为-2,说明有问题
        if (t.waitStatus != Node.CONDITION) {
            // t节点的next为null
            t.nextWaiter = null;
            // 如果trail为null,代表头结点状态就是1,
            if (trail == null)
                // 将头结点指向next节点
                firstWaiter = next;
            else
                // 如果trail有值,说明不是头结点位置
                trail.nextWaiter = next;
            // 如果next为null,说明单向链表遍历到最后了,直接结束
            if (next == null)
                lastWaiter = trail;
        }
        // 如果t的状态是-2,一切正常
        else {
            // 临时存储t
            trail = t;
        }
        // t指向之前的next
        t = next;
    }
}

4、一次性释放所有的锁资源 

final int fullyRelease(Node node) {
    // 标记位,释放锁资源默认失败!
    boolean failed = true;
    try {
        // 拿到现在state的值
        int savedState = getState();
        // 一次性释放干净全部锁资源
        if (release(savedState)) {
            // 释放锁资源失败了么? 没有!
            failed = false;
            // 返回对应的锁资源信息
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            // 如果释放锁资源失败,将节点状态设置为取消
            node.waitStatus = Node.CANCELLED;
    }
}

四、 Condition的signal方法

主要执行了以下几个部分:

  1. 确保执行sigal方法的线程持有锁
  2. 脱离condition队列
  3. 将Node的状态由-2修改为0
  4. 将node添加到AQS
  5. 为了避免当前Node无法再AQS队列中正常唤醒做一些判断和操作

1、signal方法分析

public final void signal() {
    // 在ReentrantLock中,如果执行signal的线程没有持有锁资源,直接扔异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 拿到排在Condition首位的Node
    Node first = firstWaiter;
    // 有Node在排队,才需要唤醒,如果没有,直接告辞~~
    if (first != null)
        doSignal(first);
}

上面的方法主要校验了执行signal的线程是否持有锁,如果没有持有直接抛异常,然后再Condition队列不为空的时候,执行唤醒操作。

2、doSignal方法分析

你可能感兴趣的:(java,开发语言)