ReentrantLock基础讲解

思考题

  • 什么是可重入,什么是可重入锁? 它用来解决什么问题?
  • ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。
  • ReentrantLock是如何实现公平锁的?
  • ReentrantLock是如何实现非公平锁的?
  • ReentrantLock默认实现的是公平还是非公平锁?
  • 使用ReentrantLock实现公平和非公平锁的示例?
  • ReentrantLock和Synchronized的对比?
  • 为啥ReentrantLock默认要是非公平的锁呢?
  • 什么时候用ReentrantLock,什么时候用Synchronized呢?

一、简介

1、ReentrantLock类是可重入锁,实现了Lock接口,所以,有lock和unlock方法可以使用。

2、ReentrantLock里边有三个内部类:Sync、NonfairSync、FairSync

Sync继承了AQS类,NonfairSync和FairSync都是Sync的子类。ReentrantLock根据这两个可以分别实现公平锁和非公平锁

3、对于ReentrantLock的操作,主要是涉及到AQS类的方法。AQS类中的state字段,是ReentrantLock锁可重入的核心。state字段默认是0,表示对象未被线程获取,大于0的次数表示被一个线程多次获取的次数。可重入的特性是state字段不仅仅是0和1,还可以是大于1的数值!

4、ReentrantLock类通过重写AQS同步器的:getState、setState和compareAndSetState方法来原子的操作state字段。

二、三个内部类的源码

Sync类

abstract static class Sync extends AbstractQueuedSynchronizer { //继承了AQS
    // 序列号
    private static final long serialVersionUID = -5179523762034025860L;
    
    // 获取锁
    abstract void lock();
    
    // 非公平方式获取锁
    final boolean nonfairTryAcquire(int acquires) {
        // 当前线程
        final Thread current = Thread.currentThread();
        // 获取状态,记录的是锁重入的次数
        int c = getState();
        if (c == 0) { // 表示没有线程正在竞争该锁
            if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用
                // 设置当前线程独占
                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); 
            // 成功
            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) {
            free = true;
            // 已经释放,清空独占
            setExclusiveOwnerThread(null); 
        }
        // 设置标识
        setState(c); 
        return free; 
    }
    
    // 判断资源是否被当前线程占有
    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    // 新生一个条件
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class
    // 返回资源的占用线程
    final Thread getOwner() {        
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    // 返回状态
    final int getHoldCount() {            
        return isHeldExclusively() ? getState() : 0;
    }

    // 资源是否被占用
    final boolean isLocked() {        
        return getState() != 0;
    }

    /**
        * Reconstitutes the instance from a stream (that is, deserializes it).
        */
    // 自定义反序列化逻辑
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
} 

NonfairSync类

// 非公平锁
static final class NonfairSync extends Sync {
    // 版本号
    private static final long serialVersionUID = 7316153563782823691L;

    // 获得锁
    final void lock() {
        if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
            // 把当前线程设置独占了锁
            setExclusiveOwnerThread(Thread.currentThread());
        else // 锁已经被占用,或者set失败
            // 以独占模式获取对象,忽略中断
            acquire(1); 
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

FairSyn类

// 公平锁
static final class FairSync extends Sync {
    // 版本序列化
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        // 以独占模式获取对象,忽略中断
        acquire(1);
    }

    /**
        * 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) { // 状态为0
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
                // 设置当前线程独占
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
            // 下一个状态
            int nextc = c + acquires;
            if (nextc < 0) // 超过了int的表示范围
                throw new Error("Maximum lock count exceeded");
            // 设置状态
            setState(nextc);
            return true;
        }
        return false;
    }
}

三、ReentrantLock源码

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final Sync sync;  //AQS同步器子类实例
    public ReentrantLock() {  //无参构造,默认是非公平锁
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) { //带参构造,根据Boolean值确定公平与否
        sync = fair ? new FairSync() : new NonfairSync();
    }
     public void lock() {  //lock--》AQS的lock方法---》unsafe类的底层lock
        sync.lock();
    }

核心源码

//上锁时如果线程被打断,则中断当前方法
public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
//尝试获取锁,且是非公平的
public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
//尝试获取锁,当过程中遇到超时或被打断,则中断当前方法
public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
//释放锁,调用的是ReentrantLock的内部类的Sync类的重写AQS类的tryRelease方法,涉及到state的值减一  
public void unlock() {
        sync.release(1);
    }
//创建一个条件对象    
public Condition newCondition() {
        return sync.newCondition();
    }
//得到 state的值,state的值默认是0,随着线程的重入加一   
public int getHoldCount() {
        return sync.getHoldCount();
    }
//判断是否被当前线程独占    
public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
//就是检查state的值,如果为0,表示未被线程占用,否则,被占用,也就是上锁了    
public boolean isLocked() {
        return sync.isLocked();
    }
//判断是否为公平锁    
public final boolean isFair() {
        return sync instanceof FairSync;
    }
//得到当前占有的线程    
protected Thread getOwner() {
        return sync.getOwner();
    }
//查询是否有线程正在等待获取此锁    
public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }
//查询给定线程是否正在等待获取此锁
public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }
//返回 线程等待去获取这个锁的大概数量    
public final int getQueueLength() {
        return sync.getQueueLength();
    }
//返回一个可能去等待尝试获取这个锁的线程集合    
protected Collection<Thread> getQueuedThreads() {
        return sync.getQueuedThreads();
    }
//查询是否有任何线程正在等待与此锁关联的给定条件    
public boolean hasWaiters(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
    }
//返回在条件队列中与此锁关联的大概的线程数    
public int getWaitQueueLength(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
    }
//返回一个集合,该集合包含可能存在的线程正在等待与此锁关联的给定条件    
protected Collection<Thread> getWaitingThreads(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

总结:ReentrantLock类全部是通过AQS子类实例来操作。其中AQS子类Sync继承AQS,重写AQS的一些方法和新增自己的一些方法,使之满足ReentrantLock类的操作!所以,本质上,任然是AQS类在操作锁

四、思考题解答

1、什么是可重入,什么是可重入锁? 它用来解决什么问题?

可重入是指一个对象的锁,这个锁对应有个锁计数器,加锁和释放锁对应的指令是 monitorenter和monitorexit

执行monitorenter使锁计数器加一,monitorexit使锁计数器减一。

锁计数器默认为0,表示现在没有被任何线程使用,当线程A进来获取锁,那么,执行monitorenter指令,同时,锁计数器加一,当前值为1。在线程A的管程中,有需要用到该对象,此时,不需要再重新获取对象的锁,而是锁计数器直接加一操作即可,这就是可重入性。

可重入锁是一个线程在第一次获取资源时,才需要获取锁,后续操作中,再次用到锁的时候,不需要重新获取锁(acquire),而是直接state变量加一操作即可。

它可以用来解决同一个线程不断的获取锁和释放锁的问题,我们知道,频繁的获取锁和释放锁是很消耗性能的

2、ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。

ReentrantLock有个内部类Sync,它继承了AQS,同时,ReentrantLock还有另外两个内部类,一个NonfairSync,另一个FairSyn,他们都是继承Sync,分别表示非公平锁和公平锁

3、ReentrantLock是如何实现公平锁和非公平锁的?

首先,公平与否是在获取锁上进行的体现。

公平锁会获取锁时会判断同步队列里是否有线程再等待,若有获取锁就会失败,并且会加入同步队列。

非公平锁获取锁时不会判断阻塞队列是否有线程再等待,所以对于已经在等待的线程来说是不公平的,但如果是因为其它原因没有竞争到锁,它也会加入阻塞队列。

进入同步队列的线程,竞争锁时都是公平的,应为队列为先进先出(FIFO)

公平锁的实现原理在FairSyn内部类中

 protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取状态
        int c = getState();
        if (c == 0) { // 状态为0
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
                // 设置当前线程独占
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
            // 下一个状态
            int nextc = c + acquires;
            if (nextc < 0) // 超过了int的表示范围
                throw new Error("Maximum lock count exceeded");
            // 设置状态
            setState(nextc);
            return true;
        }
        return false;
    }

比较NonfairSync类中的非公平锁,源码在Sync类中,原因是ReentrantLock默认是非公平锁

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);
                return true;
            }
            return false;
        }

小结:两个方法的不同点在于公平锁实现的同步器,线程在进来获取锁时,会查一下同步队列中是否有正在等待的线程,有,则自己弄到同步队列的尾部,让等待在同步队列中的线程获取锁。

非公平锁实现的同步器,线程进来后获取锁,它不会判断同步队列中是否有正在等待锁的线程,而是直接自己尝试获取锁,如果成功,则执行代码,不成功,则放入到同步队列的尾部,这样,不符合同步队列的FIFO操作,也对正在等待的线程不公平。

4、ReentrantLock和Synchronized的对比?

① 底层实现上来说,synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),对象只有在同步块或同步方法中才能调用wait/notify方法,ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁

synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向OS申请重量级锁,ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能。

② 是否可手动释放:

synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用; ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()和unlock()方法配合try/finally语句块来完成,使用释放更加灵活。

③ 是否可中断

synchronized是不可中断类型的锁,除非加锁的代码中出现异常或正常执行完成; ReentrantLock则可以中断,可通过trylock(long timeout,TimeUnit unit)设置超时方法或者将lockInterruptibly()放到代码块中,调用interrupt方法进行中断。

④ 是否公平锁

synchronized为非公平锁 ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁。

⑤ 锁是否可绑定条件Condition

synchronized不能绑定; ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒,而不是像synchronized通过Object类的wait()/notify()/notifyAll()方法要么随机唤醒一个线程要么唤醒全部线程。

⑥ 锁的对象

synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。

5、为啥ReentrantLock默认要是非公平的锁呢?

公平是好事,不公平是不好的,不是吗?(当孩子们想要一个决定时,总会叫嚷“这不公平”。我们认为公平非常重要,孩子们也知道。)在现实中,公平保证了锁是非常健壮的锁,有很大的性能成本。要确保公平所需要的记帐(bookkeeping)和同步,就意味着被争夺的公平锁要比不公平锁的吞吐率更低。作为默认设置,应当把公平设置为 false ,除非公平对您的算法至关重要,需要严格按照线程排队的顺序对其进行服务。

6、什么时候用ReentrantLock,什么时候用Synchronized呢?

使用Synchronized:java.util.concurrent.lock 中的锁定类是用于高级用户和高级情况的工具 。一般来说,除非您对 Lock 的某个高级特性有明确的需要,或者有明确的证据(而不是仅仅是怀疑)表明在特定情况下,同步已经成为可伸缩性的瓶颈,否则还是应当继续使用 synchronized。

使用ReentrantLock:在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。

五、总结

1、ReentrantLock实现了Lock接口,意味着有Lock的特性,比如:可中断操作,必须手动关闭锁,获取锁有返回值等。
2、ReentrantLock有三个内部类,Sync、FairSync和NonFairSync三个,Sync继承了AQS,FairSync和NonFairSync继承了Sync,分别实现了公平锁和非公平锁,类似于semaphore信号量类
3、公平锁和非公平锁体现在获取锁时,和semaphore类也是一样的原理:如果是公平锁,一个线程进来后,判断这个线程是否为同步队列中的第一个节点,否则,将当前线程作为最后一个节点放入同步队列尾部。而非公平锁相反,一个线程进来后,首先去争取锁对象,而不会判断这个线程是否为同步队列的第一个元素。
4、ReentrantLock的核心其实就是对继承的AQS类的state属性的操作,这个state属性不仅仅是0和1了,由于可以重入,导致state的值可以大于等于0,每次线程重入则加一。且对这个state属性的值的操作也是通过重写了AQS类的三个方法:getState、setState和compareAndSetState。延展一下:对于一般的锁,都会实现AQS类,对state属性进行操作,也相应的需要重写getState、setState和compareAndSetState这三个方法!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值