JUC锁框架源码阅读-ReentrantLock

非公平锁

并不会严格按照队列先后顺序获取锁,可能会出现插队

阅读之前先要阅读《JUC锁框架源码阅读-AbstractQueuedSynchronizer》

类图

 

获取锁

 

main

 public static void main(String[] args)  {
        //<1>创建锁 无参构造函数默认是非公平锁
        ReentrantLock reentrantLock=new ReentrantLock();
        //<2>加锁
        reentrantLock.lock();
    }

1.非公平锁使用的是内部类ReentrantLock.NonfairSync的对象

2.获取锁实现,直接CAS修改state状态为1(直接参与竞争锁 非公平体现)如果获取失败返回获取失败,AQS后续会加入CLH队列并阻塞等待唤醒

<1>ReentrantLock构造函数

 public ReentrantLock() {
        //可以看到创建的是内部静态类NonfairSync
        sync = new ReentrantLock.NonfairSync();
    }

<2>lock

java.util.concurrent.locks.ReentrantLock.NonfairSync#lock

 final void lock() {
        /**
         * 调用尝试父类的CAS方法将 state从0设置为1 获取锁成功调用  这里就是非公平 可能出现插队的实现,不是放入对队列 而是直接参与获取
         *java.util.concurrent.locks.AbstractQueuedSynchronizer#compareAndSetState
         */
        if (compareAndSetState(0, 1))
            //<3>获取锁成功 调用父类方法设置当前持有锁线程 java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //<4>调用父类的加锁方法acquire
            acquire(1);
    }

<3>setExclusiveOwnerThread

java.util.concurrent.locks.AbstractOwnableSynchronizer#setExclusiveOwnerThread

protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

<4>acquire

我们通过《AQS源码阅读》 跟代码可以看到 tryAcquire是模板模式 最终还是子类自己实现的加锁逻辑

public final void acquire(int arg) {
        /**
         *<5>tryAcquire  模板模式 是抽象方法由子类实现获取锁步骤
         *addWaiter   如果加锁失败 创建节点并放入队列尾
         *acquireQueued 通过新创建的节点 判断是重试获取锁。还是阻塞线程
         */
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(node.EXCLUSIVE), arg))
            //发出线程中断通知
            selfInterrupt();
    }

<5>tryAcquire

java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire

  protected final boolean tryAcquire(int acquires) {
        //<6>具体的加锁逻辑
        return nonfairTryAcquire(acquires);
    }

<6>nonfairTryAcquire

java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire

 final boolean nonfairTryAcquire(int acquires) {
        //获取当前线程
        final Thread current = Thread.currentThread();
        //获取锁状态 父类方法 0表示还未被占用 大于0表示被占用
        int c = getState();
        if (c == 0) {
            //CAS尝试获取一次锁
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //如果大于0判断持有所得线程 是不是当前线程,因为锁是可重入的 可以重复获取
        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;
    }

指定时间的获取锁

main

  //<>创建锁 无参构造函数默认是非公平锁
        ReentrantLock reentrantLock=new ReentrantLock();
        try {
//<1>获取锁 reentrantLock.tryLock(
1,TimeUnit.SECONDS); }finally { //<>释放锁 reentrantLock.unlock(); }

1.如果获取失败返回获取失败

2.AQS后续操作自旋并阻塞指定时间

3.后续到了阻塞时间会再次自旋。判断到超时了则会直接返回false 终结自旋

4.或者别的线程释放了锁。AQS唤醒了这个线程,自旋判断没有超时。则会继续超时获取锁如果获取失败继续阻塞指定时间

<1>tryLock

java.util.concurrent.locks.ReentrantLock#tryLock(long, java.util.concurrent.TimeUnit)

  public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        //<2>调用的sync的 sync继承自父类的
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

<2>tryAcquireNanos

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquireNanos

可以阅读《AQS源码阅读》看后续流程

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        //如果线程已经中断 直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        //<5>or<4>调用子类实现尝试获取锁,如果没有获取到 则调用doAcquireNanos 阻塞指定时间
        return tryAcquire(arg) ||
                doAcquireNanos(arg, nanosTimeout);
    }

 

释放锁

main

 public static void main(String[] args)  {
        //创建锁 无参构造函数默认是非公平锁
        ReentrantLock reentrantLock=new ReentrantLock();
        try {
            reentrantLock.lock();
        }finally {
            //<1>释放锁
            reentrantLock.unlock();
        }
    }

1.首先根据父类的getExclusiveOwnerThread()当前持有锁线程 判断是不是当前线程获取 如果不是则报错

4.释放前通过state-1 如果等于0才做具体释放逻辑(可重入逻辑)

3.判断state是不是等于0如果等于0表示锁已经被释放 重复调用2次unlock。则清空持有锁线程为null setExclusiveOwnerThread(null); 返回true释放成功 后续交给AQS处理

<1>unlock

java.util.concurrent.locks.ReentrantLock#unlock

 public void unlock() {
        //<2>调用父类的release方法
        sync.release(1);
    }

<2>release

后续流程可以参考《AQS源码阅读》

// 在独占锁模式下,释放锁的操作
    public final boolean release(int arg) {
        // <3>调用tryRelease子类方法,尝试去释放锁,由子类具体实现
        if (tryRelease(arg)) {
            Node h = head;
            // 如果队列头节点的状态不是0,那么队列中就可能存在需要唤醒的等待节点。
            // 还记得我们在acquireQueued(final Node node, int arg)获取锁的方法中,如果节点node没有获取到锁,
            // 那么我们会将节点node的前一个节点状态设置为Node.SIGNAL,然后调用parkAndCheckInterrupt方法
            // 将节点node所在线程阻塞。
            // 在这里就是通过unparkSuccessor方法,进而调用LockSupport.unpark(s.thread)方法,唤醒被阻塞的线程
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

<3>tryRelease

java.util.concurrent.locks.ReentrantLock.Sync#tryRelease

  protected final boolean tryRelease(int releases) {
        //状态-1 大于0的数字表示可重入加了多少次锁
        int c = getState() - releases;
        //如果加锁线程非当前线程抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //当c等于0表示最后一次调用unlock 则进行锁的释放
        if (c == 0) {
            free = true;
            //获得锁的线程设置为null
            setExclusiveOwnerThread(null);
        }
        //设置state
        setState(c);
        return free;
    }

公平锁

加锁

严格按照阻塞队列获取,先等待先获取

main

    public static void main(String[] args)  {
        //<1>创建锁 无参构造函数默认是非公平锁
        ReentrantLock reentrantLock=new ReentrantLock(true);
        //<2>加锁
        reentrantLock.lock();
    }

1.公平锁 获取锁,会先判断CLH队列里面有没有数据,如果没有则表示没有等待线程 则直接尝试获取锁

2.如果CLH有等待线程则直接加入CLH队列(公平锁的体现)

<1>ReentrantLock

 public ReentrantLock(boolean fair) {
        //true的时候初始化的是FairSync 
        sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
    }

<2>lock

java.util.concurrent.locks.ReentrantLock.FairSync#lock

   final void lock() {
        //<3>调用AQS的 acquire 
        acquire(1);
    }

<3>acquire

具体可以参考《AQS源码阅读》

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire

public final void acquire(int arg) {
        /**
         *<4>tryAcquire  模板模式 是抽象方法由子类实现获取锁步骤
         *addWaiter   如果加锁失败 创建节点并放入队列尾
         *acquireQueued 通过新创建的节点 判断是重试获取锁。还是阻塞线程
         */
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(node.EXCLUSIVE), arg))
            
            selfInterrupt();
    }

<4>tryAcquire

java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire

 protected final boolean tryAcquire(int acquires) {
        //获取当前线程
        final Thread current = Thread.currentThread();
        //获取锁状态
        int c = getState();
        //等于0表示 当前空闲状态可以尝试获取
        if (c == 0) {
            //<5>如果在CLH队列才尝试获取 否则返回false AQS放入CLH队列阻塞 (公平锁的实现)
            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;
    }

<5>hasQueuedPredecessors

AQS方法 遍历队列 判断当前线程是否在AQS队列中

java.util.concurrent.locks.AbstractQueuedSynchronizer#hasQueuedPredecessors

  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; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

公平锁和非公平锁的区别

公平锁,如果获取锁失败,CLH队列里面没有元素才会尝试竞争,否则直接放入CLH队尾

非公平锁,如果锁获取失败,会直接参与一次竞争,这个时候如果释放锁是有可能获取成功的 就插队了

posted @ 2021-08-30 17:07  意犹未尽  阅读(41)  评论(0编辑  收藏  举报