ReentrantLock

在分析了AQS之后,再来看ReentrantLock就非常简单了。ReentrantLock是一个可重入的独占锁。我们来看下具体的实现。

先看构造方法

    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;
    public ReentrantLock() {
        sync = new NonfairSync();
    }

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

ReentrantLock有两个构造方法,无参构造方法调用的是NonFairSync这个AQS子孙类来赋值给sync属性。调用无参构造方法的ReentrantLock是非公平锁,也是我们推荐使用的。我们可以通过调用ReentrantLock的有参构造方法来决定使用公平锁还是非公平锁。来看下NonfairSyncFairSync的区别。

NonfairSync

  static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
      //重写了Sync的lock方法
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
  //重写了AQS的tryAcquire方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

FairSync

 static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
      //重写了Sync的lock方法

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
       //重写了AQS的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;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

两个类都是ReentrantLock#Sync的子类,都重写了lock()方法,而且都重写了祖先类AQS的tryAcquire方法(该方法Sync没有重写,意味着子类必须实现)。所以我们知道两个AQS的子孙类肯定在获取锁的具体实现是不一样的,但是在释放锁的实现上都是一样的,具体实现由其父类ReentrantLock#Sync提供。

获取锁

NonfairSync#lock()

  

    final void lock() {
        //比较是否当前AQS的state字段为0,并且尝试设置为1,CAS
            if (compareAndSetState(0, 1))
                //如果成功,设置当前AQS的持有者为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //如果失败,调用acquire方法
                acquire(1);
        }

1.先直接使用CAS去设置当前AQS的state字段的值,如果设置成功,则将当前AQS的持有者设置为当前线程。

2.如果失败了就需要调用AQS中的final方法acquire去尝试获取锁

我们来看NonFairSync中是如何实现具体的获取锁的逻辑的。

  //NonFairSync
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
//Sync
           final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
               //查看AQS中stats的值
            int c = getState();
               //如果为0,表示目前没有线程拥有AQS
            if (c == 0) {
                //CAS设置stats的值
                if (compareAndSetState(0, acquires)) {
                    //设置当前线程拥有Thread
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
               //如果目前有线程拥有AQS,比较是否当前线程拥有
            else if (current == getExclusiveOwnerThread()) {
                //如果是,这里执行重入的操作,这里可以看出ReentrantLock是可重入锁
                //这里为什么可以不用CAS呢,因为一个线程在一个时刻只能做一件事。因为执行这部分的前提是当前线程拥有AQS
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
               
            return false;
        }

有了AQS这个实现的非常好的抽象类,我们实现一个获取锁的逻辑,只需要重写tryAcquire这个方法就好了,确实是方便,不需要程序员去考虑如何在自旋线程的时候避免避免线程进入内核的阻塞状态,这些AQS都帮我们做好了,我们只需要是定义好什么情况下当前线程可以获取锁就可以了。分析下这个方法的实现。

1.查看AQS中state属性的值,如果state的值为0,这个时候就代表没有任何线程占有AQS了,因为如果有线程占有AQS的时候,state是不可能为0的。这个时候就可以直接将占有AQS的线程设置为当前线程,返回true,即宣告当前线程获取到锁了。

//AQS中
private transient Thread exclusiveOwnerThread;
   
protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

AQS中有一个属性exclusiveOwnerThread,这个属性是提供给独占类型的锁使用的,用来设置占有当前独占锁的线程。

2.如果state不为0,代表有线程占有独占锁,那么就判断占有独占锁的线程是不是当前线程,如果不是,那么直接返回false,获取独占锁失败了,当前线程进入自旋。如果是的话,那么就是线程重入了,这块逻辑就是ReentrantLock是可重入锁的实现,那么就需要将重入次数加一,然后返回true,宣告当前线程已经成功获取到锁了。

FairSync#lock()

  final void lock() {
            acquire(1);
        }

与非公平锁不同,当调用公平锁方法的时候,是不会先去尝试当前锁是否能够抢占独占锁的,而是直接调用AQS的final方法acquire。同样我们直接分析定义当前线程可以获取锁的逻辑就好。

   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;
        }
    }

1.查看AQS中state属性的值,如果state的值为0,这个时候就代表没有任何线程占有AQS了。那么先调用hasQueuedPredecessors()(AQS实现,也是final方法)方法来查看是否当前线程Node节点前面还有被阻塞的Node节点,如果没有的话,那么才使用CAS设置state的值,然后将当前线程设置为占有独占锁的线程。

2.如果state不为0,代表有线程占有独占锁,那么就判断占有独占锁的线程是不是当前线程,如果不是,那么直接返回false,获取独占锁失败了。如果是的话,那么就是线程重入了,这块逻辑就是ReentrantLock是可重入锁的实现,那么就需要将重入次数加一,然后返回true,宣告当前线程已经成功获取到锁了。这块的逻辑和非公平锁的逻辑一样。

我们看到非公平锁与公平锁的差别就在于公平锁对于在公平锁获取锁的过程是FIFO,先进入到CLH队列中的阻塞线程先获取到锁,否则就算轮到你这个线程占有CPU运行了,但是你不是CLH队列中第一个带有线程的Node节点,你也只能往后稍稍,公平锁可以解决线程饥饿问题,可以避免线程长时间的处于阻塞状态。但是公平锁会导致线程的频繁切换,线程的频繁切换也是非常耗时的,这会降低多线程的效率,而对于非公平锁,只要你被唤醒了,拿到CPU运行了,那么我就把独占锁给你,不需要去进行多余的线程切换,非公平锁的吞吐量是大于公平锁的,Doug Lea大师在实现ReentrantLock的时候,默认也是使用了非公平锁,在一般的情况下,我们推荐使用非公平锁,防止线程频繁切换。

释放锁

接下来,我们看释放锁的方法

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

ReentrantLock释放锁的方法直接调用AQS中的final方法release。我们直接来分析ReentrantLock中定义何时释放锁的具体逻辑,tryRelease方法的实现。

 protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
          //可重入会导致state叠加,只有为0的时候会返回true
            if (c == 0) {
                free = true;
                //设置当前拥有AQS的线程为null
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

1.判断当前线程是否是占有独占锁的线程,如果不是,那么就抛出异常,释放锁方法调用的时候,一定是在当前线程是占有独占锁线程的情况下的。

2.如果state减一的值是0,也就是说,我们这时候可以去释放独占锁了,将AQS中的exclusiveOwnerThread属性设置为null,返回true,宣告独占锁已释放,release方法可以进行唤醒阻塞线程的操作。如果减一的值不为0,那么证明重入次数依然大于尝试释放独占锁的次数,返回false,宣告独占锁还未被释放。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值