美文网首页
3.ReentrantLock核心原理分析

3.ReentrantLock核心原理分析

作者: 致虑 | 来源:发表于2020-09-23 16:43 被阅读0次

    ReentrantLock核心原理分析

    ReentrantLock显式锁,相对于synchronized隐式锁而言,要想彻底了解ReentrantLock的实现原理,那么就必须理解这三个概念:可重入、公平、非公平

    其实前面通过Condition的详细介绍,应该已经基本了解ReentrantLock内部的一个具体结构(AQS+Condition),通过同步队列+等待队列,实现了线程间资源的竞争、等待与唤醒。下面在把这张图贴一下:

    condition_1

    相信通过这张图,对ReentrantLock的结构就有了一个基本的认知,无非就是通过Lock引用了一个AQS(中的state状态),通过改变竞争这个state达到锁资源的获取。

    简单看下核心代码结构

    public class ReentrantLock implements Lock {
        private final Sync sync;    //AQS 
        
        abstract static class Sync extends AbstractQueuedSynchronizer { 
            // ...
        }
      
        // 非公平
        static final class NonfairSync extends Sync {
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
    
        // 公平
        static final class FairSync extends Sync {
            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;
            }
        }
      
        // 获取锁
        void lock();
        // 获取锁,可中断
        void lockInterruptibly() throws InterruptedException;
        // 获取锁,非公平
        boolean tryLock();
        // 获取锁,设置超时
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        // 释放锁
        void unlock();
        // 锁是否被当前线程占有
        boolean isHeldByCurrentThread() 
        Condition newCondition();
    }
    

    以上就是ReentrantLock的核心代码结构了,其中Sync、NonfairSync、FairSync如何使用后面详细分析。

    那么接下来详细分析一下上面涉及到的三个概念:可重入、公平锁、非公平锁

    1. 锁重入

    其实可重入特性对于显示的ReentrantLock来说特别容易理解,那就是如果某个线程获取到锁(持有AQS的state)时,再次获取锁不会阻塞。那就是当state状态不为0时,如果当前持有锁的线程是自身(通过isHeldByCurrentThread() 判断),那就继续操作state(state++)。

    同理,重入之后,需要对等释放state(state--)次数,直到state==0才能释放锁

    看下代码

    // 尝试获取锁
    public boolean tryLock() {
        // 非公平获取
        return sync.nonfairTryAcquire(1);
    }
    
    // 锁获取逻辑
    final boolean nonfairTryAcquire(int acquires) {
      final Thread current = Thread.currentThread();
      int c = getState();
      
      // 如果等于0,说明还没有其他线程获取锁资源,直接CAS设置获取
      if (c == 0) {
          if (compareAndSetState(0, acquires)) {
                        // 将当前锁设置为自己
              setExclusiveOwnerThread(current);
              return true;
          }
      }
      
      // 否则判断获取当前持有锁的线程是否是自己,如果是,则进行重入,state+=acquires
      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;
    }
    

    过程非常简单,无非就是将state进行加法设置。

    接下来继续看看释放锁过程

    // 触发释放逻辑
    public void unlock() {
        sync.release(1);
    }
    
    // 操作释放
    protected final boolean tryRelease(int releases) {
      // 先执行state的减法操作(其实可以下面的判断是否是当前线程放在前面)
      int c = getState() - releases;
      
      // 如果持有锁的线程不是当前线程,抛异常
      if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
      
      // 因为涉及重入特性,也就是只有state一直减到0才能算完全释放
      boolean free = false;
      
      // 所以如果c==0,那就释放占有标识,同时设置state=0,同时返回true。
      if (c == 0) {
          free = true;
          setExclusiveOwnerThread(null);
      }
      setState(c);
      return free;
    }
    

    释放逻辑也一目了然,无非就是:如果当前线程是持有锁线程,就尝试state减法操作,如果减到0了就认为彻底释放了锁,否则依然持有。

    到这里,获取锁、释放锁、锁重入的逻辑就讲完了。

    2. 非公平锁

    // 默认创造的是非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
    // 当fair==true时,得到的是公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    static final class NonfairSync extends Sync {
      final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
      }
      protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
      }
    }
    

    非公平锁的获取逻辑在介绍锁重入的时候介绍过了,无非就是直接判断state状态是否可被获取,可以的话就尝试获取,没有其他逻辑。这里不再啰嗦了,重点放在公平锁的实现逻辑上。

    3. 公平锁

    直接看下代码逻辑

    // 公平锁
    static final class FairSync extends Sync {
      
      // 获取锁资源
      final void lock() {
        acquire(1);
      }
      
      // 通过AQS绕一圈回到这里,获取所资源的真正逻辑
      protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        
        // 如果state==0,还需要对同步队列进行进一步判断(这里不是等待队列哦,因为等待队列中的线程需要等待唤醒的,只有进入同步队列的线程才有资格获取锁)
        if (c == 0) {
          // 判断当前节点前面是否还有节点,如果没有,就尝试CAS设置state=0,如果成功则直接设置锁占有标识,代表获取锁成功,返回true
          if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
          }
          
          // 否则不做任何操作,else都没有
        }
        
        // 如果state != 0, 则判断当前线程是否是自己,如果不是的话就代表当前锁被其他线程占有,那就算了,否则直接进行锁重入操作
        else if (current == getExclusiveOwnerThread()) {
          int nextc = c + acquires;
          if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
          setState(nextc);
          return true;
        }
        return false;
      }
    }
    

    这里相对于非公平锁主要的差异就在于多了一个同步队列的校验
    如果state==0,还需要对同步队列进行进一步判断(这里不是等待队列哦,因为等待队列中的线程需要等待唤醒的,只有进入同步队列的线程才有资格获取锁),判断当前节点前面是否还有节点,如果没有,就尝试CAS设置state=0,如果成功则直接设置锁占有标识,代表获取锁成功,返回true

    只有在同步队列的第一个节点,才能是下一个可获取锁资格的线程节点,因此保证了所获取的公平性,其实也很简单哈。

    4. 引申

    非公平锁只需要校验state,也代表着释放锁的线程也可以立刻获取锁,这有可能导致其他线程很难获取到锁的现象,这就是"锁饥饿"

    公平锁在非公平锁的基础上增加了同步队列的校验,保证只有队列的首节点才能获取锁,从而会对性能具有一定的影响,因为线程肯定会发生切换。

    相关文章

      网友评论

          本文标题:3.ReentrantLock核心原理分析

          本文链接:https://www.haomeiwen.com/subject/aueoyktx.html