ReentrantLock笔记(二) -- 公平/非公平锁源码分析

ReentrantLock的公平/非公平锁实现

最好事先了解下CLH队列锁,也要知道CAS,再了解下LockSupport(比wait,notify好用)
参考: JAVA并发编程学习笔记之CLH队列锁

相关文章:ReentranLock笔记(一) – 基本使用

一、先分析 非公平锁(NonfairSync)

非公平锁继承关系

看一下AbstractQueuedSynchronizer类中比较重要的一些成员变量、子类

 private transient volatile Node head;
  //sync队列的尾节点
  private transient volatile Node tail;
  //个人理解成当前的占有资源计数,为0就是没有线程获得锁
  private volatile int state;

    //实现队列的一个内部类
  static final class Node {
     /** 共享等待标志 */
       static final Node SHARED = new Node();
       /** 排它等待标志 */
       static final Node EXCLUSIVE = null;
       /** 取消标志,源码中会以>0来判断该状态 */
       static final int CANCELLED =  1;
       /** 当前节点的后置节点需要唤醒标志 */
       static final int SIGNAL    = -1;
       /** 当前节点在等待队列标志 */
       static final int CONDITION = -2;
       /**表示当前场景下后续的acquireShared能够得以执行 */
       static final int PROPAGATE = -3;
        //节点状态,取值是上面那几种,值为0时表示当前节点在sync队列中,等待着获取锁。
       volatile int waitStatus;
       volatile Node prev;
       volatile Node next;
        //这个节点所属线程
       volatile Thread thread;
        //节点在condition队列中的后置节点
       Node nextWaiter;
}

还有一个比较重要的变量,是AbstractQueuedSynchronizer的父类AbstractOwnableSynchronizer中的

//保存成功获得锁的线程的引用,保证只有获得锁的线程能操作资源
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
       exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
       return exclusiveOwnerThread;
}

开始源码分析

从lock()方法入手

    //交给了sync代理该方法
    public void lock() {
        sync.lock();
    }

查看NonfairSync内部类中的lock方法

    final void lock() {
        if (compareAndSetState(0, 1))//尝试CAS设置资源数
            //设置资源数成功,那就获得锁,保存当前线程的引用
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);//没成功那就请求获取资源
    }

若失败,则看AbstractOwnableSynchronizer类的acquire

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //这里调用了tryAcquire方法,这个方法需要继承AbstractOwnableSynchronizer的子类去具体实现,
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

去看NonfairSync怎么实现

    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) {//说明还没线程获得资源(锁)
            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);//设置资源数,不用CAS
            return true;//成功获得锁
        }
        return false;//没有获得锁
    }

上面nonfairTryAcquire如果返回了true,那就没什么事了,线程得以继续运行,如果false,那就要看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)了,首先看addWaiter方法

    private Node addWaiter(Node mode) {
    //Node.EXCLUSIVE,其实是空值null,文章开头有说
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;//拿到尾节点
        if (pred != null) {//不为空可以直接插入队列
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                //CAS设置尾节点,成功则返回新的尾节点
                pred.next = node;
                return node;
            }
        }
        //当前尾节点为空,或因为多线程的操作,没有CAS操作成功
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
        for (;;) {//死循环
            Node t = tail;//拿到尾节点
            if (t == null) {//为空则先初始化一个头节点
                if (compareAndSetHead(new Node()))//CAS操作
                    tail = head;//成功则把尾节点也指向头结点
            } else {
                node.prev = t;//node的前驱节点设为尾节点
                if (compareAndSetTail(t, node)) {//CAS设置尾节点为node
                    t.next = node;//前任尾节点的后置节点设为node
                    return t;//返回前任尾节点
                }
            }
        }
    }
    /**
    说明一下,看这个代码的时候有个疑问。
    【问题1】如果队列为空,那会设置一个空节点为头结点,然后再把要插入的节点作为其后置节点,而不是直接把要插入的节点作为头结点?
    */

回顾下,上面要看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,addWaiter看完了,它返回的是新插入节点,就看acquireQueued方法,arg是请求的资源数,前面传的是1

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;//先立一个flag,一开始是true,表示失败
        try {
            boolean interrupted = false;//这个是中断标志,标志线程要获锁的过程中有没有被触发中断
            for (;;) {
                final Node p = node.predecessor();//获得前驱节点
                if (p == head && tryAcquire(arg)) {
        //如果前驱节点是头结点,则尝试获得锁(前面说了该方法)
        //这就回答了前面的【问题1】,
                    setHead(node);//成功后设置头结点
                    p.next = null; // help GC
                    failed = false;//没有失败,设置为false
                    return interrupted;//中断标记
                }
                //看下面说明
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;//如果上面都为true
            }
        } finally {
            if (failed)//上面是死循环,没看到有break之类的,这里就是抛异常,failed没被改变的情况了
                cancelAcquire(node);
        }
    }
     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//前驱节点的状态
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {//大于0是取消状态,文章开头讲了
            /*前驱节点被取消,需要重新找前驱节点*/
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);//一直循环到前驱节点的状态小于等于0
            pred.next = node;
        } else {
            /*设置前驱节点为SIGNAL状态 */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    //这段代码就是保证当前节点的前驱节点(不能是取消的节点),其状态为SIGNAL

回顾一下上面if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()),shouldParkAfterFailedAcquire返回true了,就看parkAndCheckInterrupt方法

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//个人理解是休眠线程
        return Thread.interrupted();//返回线程的中断标志,同时清除中断标志
    }

这里如果返回结果决定上面acquireQueued方法的返回结果,在回顾下

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

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

所以如果parkAndCheckInterrupt返回了true,那最后selfInterrupt会重新给线程设置中断标志位true

再来看看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;
        //这个用于待会CAS操作
        Node predNext = pred.next;
        // 设置取消状态,其他线程会跳过
        node.waitStatus = Node.CANCELLED;
        // 如果是尾节点,直接设置pred为尾节点就行
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) {
            //条件:不是头节点,且(signal状态或非取消状态并成功设置成signal状态),且线程引用不为空
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);//设置成pre的后置节点
            } else {
                //说明pred是头结点,或状态不是
                unparkSuccessor(node);
            }

            node.next = node; // help GC这里把next置空,下次队列调整,该节点就能达到回收的条件了
        }
    }

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)//还没取消,设置为等待状态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; //从尾部开始找到未取消的节点(不等于node)
        }
        if (s != null)
            LockSupport.unpark(s.thread);//唤醒该节点的线程
    }

说明: cancelAcquire就是为了把节点node从队列中删除,首先把node的waiteStatus设为取消、thread设置为空,然后一直找到该节点的未取消的前驱节点pre,如果pre不是头结点、状态还是singal,那就可以断开node的next节点,拼接到pre后面;否则就要唤醒node的后置节点,后置节点会进行队列的调整(shouldParkAfterFailedAcquire方法)

好了,lock方法分析完了,现在看unlock方法

    public void unlock() {
        sync.release(1);
    }

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//唤醒后置节点,看上面
            return true;
        }
        return false;
    }

    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;//单前资源数减去释放的资源数
        //这里保证是获得锁的线程才能进行释放操作
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {//为0说明当前线程释放全部资源了(等于释放锁)
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

释放锁比较简单,当获得锁的线程所有资源时,唤醒后置线程,进行锁争夺,所以,调用unlock方法的话,其实是只有两个线程在争夺锁(前任获得锁的线程和其后置线程,所以不公平!!)

二、分析 公平锁(FairSync)

与非公平锁差别在于两个方法

    final void lock() {
        acquire(1);//直接请求资源
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

来看看hasQueuedPredecessors()方法

    public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        //h!=t说明队列不止一个节点
        //(s = h.next) == null 说明就一个头结点
        //s.thread != Thread.currentThread()说明头结点的下一个节点不是单前线程的节点
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

说明:上面的 “! hasQueuedPredecessors()” 就是想保证当前sync队列不止一个节点且当前线程是头结点的下一个节点,这样短路的写法,就保证只能是队列头结点的下一个节点的线程能获得锁。

参考:

  1. JDk1.8源码
  2. AbstractQueuedSynchronizer的介绍和原理分析

猜你喜欢

转载自blog.csdn.net/seasonLai/article/details/82501906