JUC专题 - 一篇文章弄懂Lock锁

38 阅读5分钟

1 ReentrantLock

1.1 执行流程概览

对弈一个锁,如果让我们从0设计,我们应该考虑以下几点:

  • 一定会设计到锁的抢占 , 需要有一个标记来实现互斥。 全局变量(0,1)
  • 抢占到了锁,怎么处理
  • 没抢占到锁,怎么处理
    • 需要等待(让处于排队中的线程,如果没有抢占到锁,则直接先阻塞->释放CPU资源)
      • 如何让线程等待?
        • wait/notify(线程通信的机制,无法指定唤醒某个线程)
        • LockSupport.park/unpark(阻塞一个指定的线程,唤醒一个指定的线程)
        • Condition
    • 需要排队(允许有N个线程被阻塞,此时线程处于活跃状态)
      • 通过一个数据结构,把这N个排队的线程存储起来
  • 抢占到锁的释放过程,如何处理
    • LockSupport.unpark() -> 唤醒处于队列中的指定线程
  • 锁抢占的公平性(是否允许插队)

那么让我们从流程简单分析下ReentrantLock: image.png 上图展示了ReentrantLock的执行流程:

  • 首先,记录是否有锁的变量、阻塞队列、标记哪个线程吃有锁,这三个部件都是由一个抽象类AQS锁实现的,其中voliate修饰的state标识是否有锁,exclusiveOwnerThread指向当前获取锁的线程,并且有一个AQS阻塞队列(双向链表结构)存储阻塞的线程
  • ThreadA尝试获取锁,首先会去看state是否为0
    • 为0 则代表没有锁,则通过CAS操作将state由0变1,并且exclusiveOwnerThread存储ThreadA,标识ThreadA获取锁成功
    • 不为0 则代表有线程获取了锁,则进入AQS阻塞队列,进入后首先会自旋一次尝试能不能获取锁,假如没有成功,则调用LockSupport.park去让线程进入阻塞状态(例如图中的ThreadB和ThreadC)
  • 假如ThreadA调用unlock释放锁,则会调用LockSupport.unpark唤醒AQS阻塞队列head节点的下一个节点的线程ThreadB,此时ThreadB再进行自旋一次尝试获取锁
  • 公平锁和非公平锁体现:假如ThreadA调用unlock之后,在LockSupport.unpark(ThreadB)之前恰好有一个ThreadD尝试获取锁,则会抢占锁成功,这样就是一个非公平锁,所以公平锁和非公平锁的体现就是新来的线程要不要去阻塞队列排队

1.2 源码分析

在进入源码之前,先来看下类的关系图:

image.png 我们可以看到,reentrantlock实现lock接口,同时依赖于Sync同步器,而Sync同步器继承了AQS抽象类,对于公平锁和非公平锁有相应的实现

1.2.1 公平锁

final void lock() {
    acquire(1); //抢占1把锁.
}
public final void acquire(int arg) { -> AQS里面的方法
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) { //表示无锁状态
        if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) { //CAS(#Lock) -> 原子操作| 实现互斥
            的判断
            setExclusiveOwnerThread(current); //把获得锁的线程保存到
            exclusiveOwnerThread中
            return true;
        }
    }
//如果当前获得锁的线程和当前抢占锁的线程是同一个,表示重入
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires; //增加重入次数.
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc); //保存state
        return true;
    }
    return false;
}

1.2.2 非公平锁

final void lock() {
//不管当前AQS队列中是否有排队的情况,先去插队
    if (compareAndSetState(0, 1)) //返回false表示抢占锁失败
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
public final void acquire(int arg) { --AQS
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
//hasQueuedPredecessors
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    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;
}

1.2.3 加入队列并进行自旋等待

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

其中acquireQueued(addWaiter(Node.EXCLUSIVE), arg) :

  • addWaiter(Node.EXCLUSIVE) -> 添加一个互斥锁的节点
  • acquireQueued() -> 自旋锁和阻塞的操作
private Node addWaiter(Node mode) {
//把当前线程封装成一个Node节点。
    Node node = new Node(Thread.currentThread(), mode); //后续唤醒线程的时候,需要
    得到被唤醒的线程.
// Try the fast path of enq; backup to full enq on failure
            Node pred = tail;
//假设不存在竞争的情况
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
private Node enq(final Node node) {
    for (;;) {//自旋
        Node t = tail;
        if (t == null) { // Must initialize
//初始化一个head节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
//node表示当前来抢占锁的线程,有可能是ThreadB、 ThreadC。。
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) { //自旋
//begin ->尝试去获得锁(如果是非公平锁)
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { //如果返回true,则不需要等待,直接返
                回。
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
//end
//否则,让线程去阻塞(park)
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) //LockSupport.park
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
//ThreadB、 ThreadC、ThreadD、ThreadE -> 都会阻塞在下面这个代码的位置.
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); //被唤醒. (interrupt()->)
    return Thread.interrupted(); //中断状态(是否因为中断被唤醒的.)
}

1.2.5 unlock

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head; //得到当前AQS队列中的head节点。
        if (h != null && h.waitStatus != 0) //head节点不为空
            unparkSuccessor(h); //
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0) //表示可以唤醒状态
        compareAndSetWaitStatus(node, ws, 0); //恢复成0
    Node s = node.next;
    if (s == null || s.waitStatus > 0) { //说明ThreadB这个线程可能已经被销毁,或
        者出现异常...
        s = null;
//从tail -> head进行遍历.
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0) //查找到小于等于0的节点
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread); //封装在Node中的被阻塞的线程。ThreadB、ThreadC
}