AQS原理实现(一)ReentrantLock

ReentrantLock的实现分为公平锁和非公平锁。下面来介绍一下公平锁和非公平锁,两部分。

公平锁:有线程需要加锁,有在加锁执行的线程,直接入队。
非公平锁:有线程需要加锁,直接尝试获取锁,如果获取失败入队。

一、下面来介绍一下公平锁的实现

//默认构造器就是公平锁
ReentrantLock r = new ReentrantLock();
r.lock();
1、这是公平锁尝试加锁入口代码
//内部类:static final class FairSync extends Sync
final void lock() {
     acquire(1);
}
2、执行acquire逻辑。尝试加锁 入队 逻辑
public final void acquire(int arg) {
     //方法详解会在后面给出
     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
}
3、尝试加锁tryAcquire
protected final boolean tryAcquire(int acquires) {
    //获取当前线程
    final Thread current = Thread.currentThread();
    //获取当前state
    int c = getState();
    //如果当前state是初始状态
    if (c == 0) {
        //先尝试获取锁
        //hasQueuedPredecessors后面解释
        //compareAndSetState 尝试进行CAS加锁
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            //设置当前执行线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果当前线程和正在执行线程是同一个线程,则state+1.这也是可重入锁的实现
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
4、入队操作addWaiter
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    //如果pred==null,说明队列还未初始化,则执行下面enq逻辑
    if (pred != null) {
        //将node节点的前指针,指向tail
        node.prev = pred;
        //CAS抢锁
        if (compareAndSetTail(pred, node)) {
            //将原tail节点的next指针,指向当前node
            pred.next = node;
            return node;
        }
    }
    //如果队列是空的,执行初始化操作。
    enq(node);
    return node;
}
5、初始化队列操作enq(node);
//队列的初始化
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        //第一次循环,t==null
        if (t == null) { // Must initialize
            //CAS创建节点。
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //当前node的前驱指针指向队列的尾节点
            node.prev = t;
            //CAS加锁
            if (compareAndSetTail(t, node)) {
                //原来的tail节点的next指向当前节点。
                t.next = node;
                return t;
            }
        }
    }
}
6、acquireQueued方法浅析
//这个方法是在入队之前再次做一次加锁操作,如果加锁成功就执行,加锁失败就设置节点状态为SIGNAL,返回中断标记。进行线程等待
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取当前节点的前驱节点。
            final Node p = node.predecessor();
            //如果前驱节点是head,说明是第一个节点,可以尝试加锁。
            if (p == head && tryAcquire(arg)) {
                //加锁成功,则设置头节点为当前节点。
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            //这个方法是异常情况,取消当前线程进行出队的。
            cancelAcquire(node);
    }
}
7、shouldParkAfterFailedAcquire方法浅析
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    //如果前驱节点的状态是-1,标识前驱节点在等待被唤醒,返回中断标记
    if (ws == Node.SIGNAL)
        return true;
    //说明前驱节点是超时或者取消的。可以做出队操作
    if (ws > 0) {
        //去除中断或者超时的节点。
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
       //标记前驱节点的状态是Node.SIGNAL(-1),下次循环在第一个if判断就会返回
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
以上是加锁入队流程

二、下面来介绍一下解锁流程

1、解锁的入口实现
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    //执行tryRelease,下方有代码注释
    if (tryRelease(arg)) {
        Node h = head;
        //如果第一个节点不为空,并且状态不是0。(个人感觉这里应该是-1)
        if (h != null && h.waitStatus != 0)
            //尝试唤醒线程,下方有这个方法的代码注释
            unparkSuccessor(h);
        return true;
    }
    return false;
}
2、解锁操作:tryRelease
protected final boolean tryRelease(int releases) {
    //加锁是进行+1  ,加多少次锁,就要进行多少次解锁,所以每次解锁都是-1
    int c = getState() - releases;
    //校验当前线程是不是正在执行的线程,不是的会抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //最后一次调用解锁操作
    if (c == 0) {
        free = true;
        //清空正在执行的线程。
        setExclusiveOwnerThread(null);
    }
    //设置state为初始化状态
    setState(c);
    return free;
}
3、线程唤醒:unparkSuccessor
private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;
    //如果head节点的状态是小于0的,直接加锁。
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); 

    Node s = node.next;
    //获取next节点,如果next==null,或者是取消状态,则循环更新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;
    }
    // 如果next节点不是空的,则唤醒,准备在当前节点执行完成后进行加锁。
    if (s != null)
        LockSupport.unpark(s.thread);
        
}

三、非公平锁

1、非公平锁的创建于入口代码
//ReentrantLock构造器默认值是true,传入false代表实现非公平锁
ReentrantLock r = new ReentrantLock(false);
r.lock();
//非公平锁就是当有线程尝试加锁,不管队列中有无节点,先尝试获取锁。
//获取到就将可以执行线程设置为自己,获取不到在进行入队操作,入队操作和公平锁相同。
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

四、下面说一下node节点的状态waitStatus

// CANCELLED:由于超时或中断,此节点被取消。节点一旦被取消了就不会再改变状态。特别是,取消节点的线程不会再阻塞。
static final int CANCELLED =  1;
// SIGNAL:此节点后面的节点已(或即将)被阻止(通过park),因此当前节点在释放或取消时必须断开后面的节点
// 为了避免竞争,acquire方法时前面的节点必须是SIGNAL状态,然后重试原子acquire,然后在失败时阻塞。
static final int SIGNAL    = -1;
// 此节点当前在条件队列中。标记为CONDITION的节点会被移动到一个特殊的条件等待队列(此时状态将设置为0),直到条件时才会被重新移动到同步等待队列 。(此处使用此值与字段的其他用途无关,但简化了机制。)
static final int CONDITION = -2;
//传播:应将releaseShared传播到其他节点。这是在doReleaseShared中设置的(仅适用于头部节点),以确保传播继续,即使此后有其他操作介入。
static final int PROPAGATE = -3;

//0:以上数值均未按数字排列以简化使用。非负值表示节点不需要发出信号。所以,大多数代码不需要检查特定的值,只需要检查符号。
//对于正常同步节点,该字段初始化为0;对于条件节点,该字段初始化为条件。它是使用CAS修改的(或者在可能的情况下,使用无条件的volatile写入)。

ps:学习是一个循序渐进的过程。不记录永远记不住。此篇文章是自己学习记录的过程,如果对你有帮助,请帮忙点赞。如果有哪里讲的不对的地方,欢迎一起讨论。

你可能感兴趣的:(AQS原理实现(一)ReentrantLock)