Renntrantlock原理(源码解析)

Renntrantlock原理(源码)


前言

记录第一次写博客,在面试中,经常发现会问Synchronized与ReentrantLock的区别,所以深入理解下ReentrantLock实现的原理。我们知道Synchronized底层是JVM实现的,在字节码中修饰在方法层面的同步关键字,会多一个 ACC_SYNCHRONIZED(方法)的判别位,修饰在代码块层面的同步块会多一个 monitorenter和 monitorexit关键字,而Monitorenter和Monitorexit是监视器独有的,监视器包括3部分,owner(进入锁的占有线程)、Waitset(被wait()命令沉睡等待唤醒的线程)以及EntryList(阻塞队列)。底层Hostpot源码还没看,忙完这一阵子,抽时间研读一下。本文主要记录下自己认识的ReentrantLock的原理,便于日后学习。而他们两者的区别不多说了。(可中断,用拥有条件变量、可设置公平锁、可设置超时时间避免死锁)

一、ReentrankLock的AQS部分源码

首先我们知道,ReentrantLock是由AQS同步器框架而来的。首先看下AQS的部分源码。
下面展示一些 AQS获取锁的源码。这种设计风格方式是模板设计模式。tryAcquire、tryRelease由AQS暴露给外部也可以理解为给ReentrantLock的AQS具体实现用的。
下面是Acquire方法,判别的是是否获取到锁,如果没有获取到锁,那么就在原先位置上加一个备份(包括该线程的状态),然后把本尊(线程本尊)放到阻塞队列(acquireQueued)。Release也类似。
子类只需要实现自己获取锁的逻辑和释放逻辑即可,至于排队阻塞等待、唤醒机制由AQS完成。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&  //没有获取到锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //分身,并加入到阻塞队列中
            selfInterrupt();
    }
	//子类实现获取锁的逻辑,AQS并不知道怎么用state上锁
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {     //如果释放锁成功
            Node h = head;         //检查阻塞队列唤醒
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
	//子类获取锁的逻辑,AQS也不知道怎么释放锁(这些都给ReentranLock做)
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

二、Reentrantlock原理

在ReentrantLock使用中,通常用lock方法。以下是Lock方法的源码实现。

    public void lock() {
        sync.lock();
    }

不难看出,lock方法调用了Sync的Lock方法,点进去看下。
在这里插入图片描述
这里可以清晰的表示Lock是抽象的方法,往下看实现的两个方法,分别为FairSync与NofairSync。说明lock方法是根据公平与非公平进行的。以下是公平锁与非公平锁的原理。
[外链图片转存失败,源站可能有防盗在这里插入!链机制,建描述]议将图片上https://传(imblogsdnimg.cn/98UEC637930b00fa499cded180f6a0bf.png72394)(.png)]
在这里插入图片描述

非公平锁比公平锁效率高(公平锁需要排队与唤醒时间也就是上下文切换 + 调度时间)

三.ReentrantLock源码实现

3.1核心变量和构造器

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final Sync sync;
    
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

上图可以看出,默认创建的非公平锁,在构造器中可以控制使用公平锁或者是非公平锁。

3.2.公平与非公平锁(获取锁)实现原理

非公平代码流程:先在reentrantlock类中继承了抽象内部类,再调用Sync静态内部类中的 nonfairTryAcquire。

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

        final void lock() {
        //非公平锁直接抢锁,不管有没有线程排队(用CAS)尝试将state置为1
        //如果置为1了,在OwnerThread中把当前线程加入在里面。(这种方案和monitor的owner类似)
            if (compareAndSetState(0, 1))
            //上锁成功,标识当前线程为获取锁的线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
               //抢锁失败,进入AQS的标准获取锁流程
               //这里是AQS,如果下面tryrequire返回true,则又获取到锁了,否则进入阻塞队列。详细请看AQS源码解析。
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
        	//使用AQS提供的获取非公平锁方法获取锁
            return nonfairTryAcquire(acquires);
        }
    }

    
abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        //调用非公平锁标准获取方法
        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()) {
            	//利用state政协变量记录次数
                int nextc = c + acquires;
                //如果超过int表示范围,表名符号溢出,所以抛出异常
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //表名需要AQS来将当前线程放入阻塞队列,然后进行阻塞操作,等待唤醒
            return false;
        }

下面是公平锁的实现源码。
公平代码流程:先在Reentrantlock类中继承了抽象内部类,但是这里公平锁的tryacquire在本方法中实现,没有在Sync中。为什么呢?李大爷喜欢。

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
		 //有reentrantlock调用
        final void lock() {
           //没有尝试抢锁,直接进入AQS的标准流程。
            acquire(1);
        }
		//AQS调用,子类自己实现获取锁的流程
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //此时正好获取锁的线程释放了锁,也有可能本身没有线程获取锁
            if (c == 0) {
            	//这里和非公平锁区别在于:hasQueuedPredecessors看看队列中是否有线程正在排队,没有在通过CAS抢锁。
                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;
            }
            //AQS来将当前线程放入阻塞队列
            return false;
        }
    }
    

3.2.释放锁操作

公平锁与非公平锁公用方法,因为在释放锁的时候,并不区分是否公平。

    public void unlock() {
        sync.release(1);
    }
		
	   protected final boolean tryRelease(int releases) {
	       //拿到锁的重入次数
	       int c = getState() - releases;
	       //如果当前线程不是上锁的线程,是无效的。
	       if (Thread.currentThread() != getExclusiveOwnerThread())
	           throw new IllegalMonitorStateException();
	       boolean free = false;
	       //不是重入锁,那么当前线程一定是释放锁了,把当前AQS用于保存当前锁对象的变量ExclusiveOwnerThread设置为null,表明释放锁成功。
	       if (c == 0) {
	           free = true;
	           setExclusiveOwnerThread(null);
	       }
	       //此时的state全局变量没有改变,也就是在setState之前,没有别的线程获取到锁,这保证了以上操作的原子性。
	       setState(c);
	       //告诉AQS,当前释放锁成功,可以唤醒正在等待锁的线程了。
	       return free;
   }

总结

在上述,还有一些源码没涉及,但是如果深入研究,比如条件变量,这里是直接引用了AQS的条件变量,所以先研究AQS,也就水到渠成了。

public class test82 {
    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        try {
            //互斥区
        }finally {
            reentrantLock.unlock();
        }
    }
}

在上述代码中,new的是非公平锁,调用lock对象时,由于此时并没有锁锁住它,调lock的时候回调Sync的lock方法,而lock方法里面对于非公平锁用的是CAS成功直接返回。而调用unlock时,获取当前的state-1,看是否为0,如果为0,则成功。

RenntrantReadwritelock这个同步器和reentrantlock类似,区分为锁分为读锁与写锁。写写互斥,写读互斥,读读不互斥。
在这里插入图片描述
RenntrantReadwritelock源码暂时看了些,主要看AQS源码,过几天总结下AQS源码实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值