图解AQS原理之ReentrantLock详解-公平锁

概述

前面已经讲解了关于AQS的非公平锁模式,关于NonfairSync非公平锁,内部其实告诉咱们谁先争抢到锁谁就先得到资源,下面就来分析一下公平锁FairSync内部是如何实现公平的?若是没有看过非公平锁的先去了解下非公平锁,由于这篇文章前面不会讲太多内部结构,直接会对源码进行分析java

前文链接地址:图解AQS原理之ReentrantLock详解-非公平锁segmentfault

本文分析的JDK版本是1.8

舒适提示:读本文内容建议结合以前写的非公平,前篇设计了不少基础性内容less

源码分析

在源码分析以前,咱们先来看一下ReentrantLock如何切换获取锁的模式呢?实际上是在构造器中传递指定的类型变量来控制使用锁的方式,以下所示:函数

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

fair参数指定为true时,表明的是公平锁,若是指定为false则使用的非公平,无参的构造函数默认使用的是非公平模式,以下所示:源码分析

public ReentrantLock() {
    sync = new NonfairSync();
}

接下来咱们以一个例子来进行后面的说明:ui

public class ReentrantLockDemo {

    public static void main(String[] args) throws Exception {
        AddDemo runnalbeDemo = new AddDemo();
        Thread thread = new Thread(runnalbeDemo::add);
        thread.start();
        Thread.sleep(500);
        Thread thread1 = new Thread(runnalbeDemo::add);
        thread1.start();
        System.out.println(runnalbeDemo.getCount());
    }

    private static class AddDemo {
        private final AtomicInteger count = new AtomicInteger();
        private final ReentrantLock reentrantLock = new ReentrantLock(true);
        private final Condition condition = reentrantLock.newCondition();

        private void add() {
            try {
                reentrantLock.lockInterruptibly();
                count.getAndIncrement();
            } catch (Exception ex) {
                System.out.println("线程被中断了");
            } finally {
//                reentrantLock.unlock();
            }
        }

        int getCount() {
            return count.get();
        }
    }
}

咱们经过源码能够看到这里咱们启动了两个线程,两个线程分别进行同步锁操做,这里我并无释放掉锁,由于方便分析队列的状况,固然你也能够在内部写一个死循环,不释放锁就能够了,我这里简单的不释放锁,使用的是可中断的获取锁操做方法lockInterruptibly,这里内部的原理咱们上一篇文章中已经讲解过了,这里并不过多的去分析内部原理,这个ReentrantLocklockInterruptibly调用内部类AQSacquireInterruptibly,可是实际上是FairSync内部类继承了内部类Sync,而内部类Sync有继承了AbstractQueuedSynchronizer简称AQS,acquireInterruptibly源码信息以下所示:this

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

这里咱们经过上一篇文章得知tryAcquire是须要子类去实现的方法,咱们在例子中指定了使用的是公平锁,因此tryAcquire方法的实现是在ReentrentLockFairSync类中,咱们来具体看一下这个方法,重点也在这个方法中其余的其实都是同样的,由于用的方法都会同样的非公平和公平锁的调用,惟独不同的就是子类实现的方法是不相同的,接下来咱们就来看一下公平锁的tryAcquire是如何实现的?线程

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;                                            //state递增
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);                                                            //设置state状态
        return true;                                                                    //得到锁成功
    }
    return false;                                                                            //得到锁失败
}

对比非公平锁的NonfairSync类的tryAcquire方法,其实就是在锁可用的状况下增长了一个判断条件,这个判断方法就是hasQueuedPredecessors,从方法的名称来看说的是有等待的线程队列,换句话说已经有人在排队了,新来的线程你就不能加塞,而非公平模式的谁先争抢到锁就是谁的,管你先来不先来,接下来咱们具体看一下这个设计

hasQueuedPredecessors方法源码:code

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // 得到尾节点
    Node h = head; // 得到头节点
    Node s;
    return h != t &&    //头节点和尾节点相同表明队列为空        
        ((s = h.next) == null || s.thread != Thread.currentThread());    //头节点的next节点为空表明头节点,以及s.thread不是当前线程不是本身的话表明队列中存在元素
}

经过上面的源码信息,能够得出其实内部主要就是判断有没有排队等待的节点,队列是否为空,若是为空的话则能够争抢锁,若是队列不为空,伙计你必须老老实实给我排队去,除非占有锁的线程和请求锁的线程是同样的,不然仍是老老实实排队去,这就是公平模式的锁操做,还有一个lock方法,公平模式的lock方法,没有直接上来先获取锁,而是先尝试得到锁直接调用AQSaquire方法进行尝试获取锁,下面是FairSync源码:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);                                    //这里直接调用了aquire并无尝试修改state状态
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    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;
    }
}

结束语

本内容主要是结合上篇内容的一个续篇,能够结合上篇而后再看下篇会比较清晰些。

相关文章
相关标签/搜索