ReentrantLock源码解析

前言:
这是我参加工作之后,第一次开始写博客,我也不知道自己是怎么了,就突然想在网上沉淀点自己的东西,可能是平时在网上拿来主义太多,让我有点不太好意思吧。也可能是最近出去面试了几家公司,发现自己这两年都是在业务代码,对于底层的沉淀太少,虽然业务代码对我来说,没什么问题,但是只要涉及到底层的东西,我就会哑口无言,才让我痛下决心,一定要沉淀点属于自己的东西。不管怎么样,今天开始了这个博客,希望自己能够坚持住,保持自己的初心,持续更新技术和自己生活中的乐趣。共勉!!!

好了废话就不多说,开始下面的整体:

一. ReentrantLock介绍:
ReentrantLock是可重入锁,那么顾名思义就是支持重复进入的锁,该锁表示支持同一线程对资源的重复加锁,即当前线程获取该锁不会被阻塞。该锁实现了Lock接口的一个类,也是在实际工作中经常使用到的一个锁。
在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:

  1. 重入性的实现原理;
  2. 公平锁和非公平锁。

二. ReentrantLock实现原理:
ReentrantLock两种锁实现的底层都是继承了AbstractQueuedSynchronizer(AQS同步机制框架)和 final volatile status的同步状态机制。

//status的状态值:
SIGNAL: 当前节点的后继处于阻塞状态(or即将处于阻塞状态),因此当当前节点的线程发生了 release或者cancel后,应该使得其后继节点发生移动.为了避免竞争,acquire方法 必须先暗示他们需要一个signal,然后重复尝试原子的acquire操作,如果失败则进入阻塞状态。
CANCELLED:节点被撤销通常都是因为超时或者被中断.节点从来不会保存这个状态.特殊的是被撤销节点的线程不会再出现阻塞。
CONDITION:当前节点处于条件队列.它不会用作同步队列节点直到从条件队列被转移走,一旦被转移走,它的status值将会被设置为0(此值和status的其它值之间没有什么关系,但简化了同步机制)。
PROPAGATE:方法releaseShared的调用应该向其它节点传播这一消息.为了保证持续传播,这一状态值在doReleaseShared方法中被设置(设定操作只能由头节点完成),即使有其它操作穿插其中。
0: 非上述情况的其它情况。(锁最初的状态)

重进入是任意线程在获取到锁之后,能够再次获取锁而不被阻塞的现象,满足这个条件,那么程序必须实现两个条件:

1. 锁需要去识别获取锁的线程是否为当前占据的线程,如果是则再次获取成功。
2. 锁的最终释放,由于锁会被获取N次,只有锁被释放N次之后,该锁才会被释放。

ReentrantLock是通过组合自定义同步器实现锁的获取与释放,重入锁的默认构造方法是非公平锁机制,下面以非公平锁为例:

		//默认构造方法里面实现的是:非公平锁机制
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //当C==0表示:当前锁出于默认状态,未被任何线程占用,当前线程可以获取该锁
            if (c == 0) {
            	//status== 0,通过CAS竞争成功,将state的状态值设置为acquires。
                if (compareAndSetState(0, acquires)) {
                	//设置锁的拥护者为当前线程,并返回true
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //若被占用,检查当前线程是否为占用线程
            else if (current == getExclusiveOwnerThread()) {
            	//如果是占用线程是当前线程,则不参与锁的竞争,并将当前的状态state设置为原状态加上acquires。
                int nextc = c + acquires;
                if (nextc < 0) // overflow    防溢出
                    throw new Error("Maximum lock count exceeded");
                //再次占用锁,在原来的基础上加1,与前面说到的第二个条件吻合,释放的时候也需要释放相应的次数
                setState(nextc);
                return true;
            }
            return false;
        }

该方法的逻辑首先判断该锁是否被获取过,没有获取就直接获取,并将锁的调用状态值设置为acquires(其实就是1),也就是记录需要释放锁一次;第二个if判断就充分表达出这个锁为什么是重入锁的原因请看,它首先判断当前获取锁的线程是否是当前的线程,如果是当前的线程,则获取次数在原来的基础上加上acquires,同理释放的时候的需要释放相应的次数。

下面再来看下非公平锁的释放方法

//非同步锁的释放机制
protected final boolean tryRelease(int releases) {
			//当前获取锁的线程获取数量减一
            int c = getState() - releases;
            //判断当前获取锁的线程是否是当前线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //当我们的获取数量和释放数量相同的时候,也就是C等于零的时候,设置当前获取锁的线程为空,也就是最初期状态
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //否则设置当前获取锁的数量为减一,并返回false。
            setState(c);
            return free;
        }

该方法的释放逻辑为:首先在原来的获取次数上减一,并判断当前获取锁的线程是否是当前线程,如果不是就直接抛出异常,如果是当前线程,判断当前获取锁的数量是否减到了0,如果是方法返回true,并将当前获取锁的线程设置为空,如果不是0,就在原来获取次数上减一并返回false。
从这里我们可以看出,非同步线程的释放锁机制,一定要是当前线程获取锁的数量为零,才能释放锁,这点非常重要。

我们再来看下非公平锁的类:

	/**
     * 非公平锁继承Sync
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * 获取锁的资源
         */
        final void lock() {
        	//直接参与锁的竞争,假设当前线程获取锁的数量为0,获取锁之后,在原来的基础上加一
            if (compareAndSetState(0, 1))
            	//并设置设置获取锁的线程为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	//失败则调用 AbstractQueuedSynchronizer.acquire()获取锁
                acquire(1);
        }

		//实质上方法tryAcquire底层还是调用前面的nonfairTryAcquire()方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

关于方法acquire()方法源码分析,详见我的下篇博客(AQS源码分析)。

三. 公平锁和非公平锁
ReentrantLock重入锁支持两种方式,一个是公平锁和另外一种非公平锁;如果一个锁是公平锁,那么在锁的机制上则完全符合时间上绝对顺序,也就是FIFO(先进先出队列)。

	//默认构造器方法是实现非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

当前ReentrantLock也提供了另外一个构造方法,也就是:

	//传入一个boolean值,来判断构造方法实现锁的方式
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

下面我们来分析下公平锁的类源码:

	/**
     * 公平锁的实现机制
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

		//这里的获取锁资源方法,与非公平锁不一样,直接调用了acquire(1)这个方法,而没有去立刻获取锁资源。
        final void lock() {
            acquire(1);
        }

        /**
         * 
         * 尝试获取锁
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            	//与非公平锁不同的一点是新增了一个方法hasQueuedPredecessors()
                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;
        }
    }

从源码中观察可以发现,其实公平锁和非公平锁的实现机制是差不多的,只不过在获取锁资源上面多了一个方法,就是hasQueuedPredecessors()方法。
该方法的作用是:加入同步队列的当前节点是否有前驱节点的判断,如果有前驱节点,表明在队列中有线程比当前线程更早的请求获取锁资源,所以这里方法必然会返回false,需要等待前线程获取锁资源并释放锁资源之后,重复上面的步骤直到hasQueuedPredecessors()方法返回false之后,当前线程才有机会去获取锁资源。
由此我们可以得出:公平锁每次都是从同步队列线程的第一个子节点获取锁资源,而非公平锁则不一定是从同步队列线程的第一个子节点获取锁资源,有可能是刚刚释放锁资源的线程。

总结:

  1. 公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。

  2. 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

方法用例:

class ReentrantLockDemo {

    private final static Logger logger = LoggerFactory.getLogger(ReentrantLock.class);

    //定义一个公平锁
    private final static ReentrantLock lock1 = new ReentrantLock(true);

    //定义一个非公平锁
    private final static ReentrantLock lock2 = new ReentrantLock(false);

    /**
     * 非公平锁使用方法
     */
    public void reentrantLockFalse(){

        try{

            //获取锁资源
            lock2.lock();
            for(int i =0 ; i< 3; i++){
                System.out.println("非公平锁获取线程的名称为:" + Thread.currentThread().getName() + "非公平锁遍历次数为:" + i);
            }
        }catch (Exception e){
            logger.error("非公平锁使用抛出异常 ReetrantLockFalse >> e = {}", ExceptionUtils.getMessage(e));
            throw e;
        } finally {
            //释放锁资源
            lock2.unlock();
        }
    }

    /**
     * 公平锁的使用方法
     */
    public void reentrantLockTrue(){
        try{
            //获取锁资源
            lock1.lock();
            for(int i =0 ; i< 3; i++){
                System.out.println("公平锁获取线程的名称为:" + Thread.currentThread().getName() + "非公平锁遍历次数为:" + i);
            }
        }catch (Exception e){
            logger.error("公平锁使用抛出异常 ReetrantLockFalse >> e = {}", ExceptionUtils.getMessage(e));
            throw e;
        }finally {

            //释放锁资源
            lock1.unlock();
        }

	public static void main(String[] args) {
	        ReentrantLockDemo lockDemo = new ReentrantLockDemo();
	
	        //声明一个非公平线程锁
	        MethodA methodA1 = new MethodA(lockDemo);
	        MethodA methodA2 = new MethodA(lockDemo);
	        MethodA methodA3 = new MethodA(lockDemo);
	
	        //声明一个公平线程锁
	        MethodB methodB1 = new MethodB(lockDemo);
	        MethodB methodB2 = new MethodB(lockDemo);
	        MethodB methodB3 = new MethodB(lockDemo);
	        methodA1.start();
	        methodA2.start();
	        methodA3.start();
	
	
	        methodB1.start();
	        methodB2.start();
	        methodB3.start();
	
	    }
    }

class MethodA extends Thread {

    private ReentrantLockDemo lock;

    public MethodA(ReentrantLockDemo lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        lock.reentrantLockFalse();
    }
}

class MethodB extends Thread {
    private ReentrantLockDemo lock;

    public MethodB(ReentrantLockDemo lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        lock.reentrantLockTrue();
    }
}

运行结果:

##非公平锁运行结果
非公平锁获取线程的名称为:Thread-0非公平锁遍历次数为:0
非公平锁获取线程的名称为:Thread-0非公平锁遍历次数为:1
非公平锁获取线程的名称为:Thread-0非公平锁遍历次数为:2
非公平锁获取线程的名称为:Thread-1非公平锁遍历次数为:0
非公平锁获取线程的名称为:Thread-1非公平锁遍历次数为:1
非公平锁获取线程的名称为:Thread-1非公平锁遍历次数为:2
非公平锁获取线程的名称为:Thread-2非公平锁遍历次数为:0
非公平锁获取线程的名称为:Thread-2非公平锁遍历次数为:1
非公平锁获取线程的名称为:Thread-2非公平锁遍历次数为:2

##公平锁运行结果
公平锁获取线程的名称为:Thread-3非公平锁遍历次数为:0
公平锁获取线程的名称为:Thread-3非公平锁遍历次数为:1
公平锁获取线程的名称为:Thread-3非公平锁遍历次数为:2
公平锁获取线程的名称为:Thread-4非公平锁遍历次数为:0
公平锁获取线程的名称为:Thread-4非公平锁遍历次数为:1
公平锁获取线程的名称为:Thread-4非公平锁遍历次数为:2
公平锁获取线程的名称为:Thread-5非公平锁遍历次数为:0
公平锁获取线程的名称为:Thread-5非公平锁遍历次数为:1
公平锁获取线程的名称为:Thread-5非公平锁遍历次数为:2

从上面的运行结果可以看出公平锁和非公平锁的区别,这里我就不做过多的描述,大家请看运行后的结果。

博客中某些语句借鉴于:
https://blog.csdn.net/u011521203/article/details/80186741
https://blog.csdn.net/blogs_broadcast/article/details/80723021
https://blog.csdn.net/caoxiaohong1005/article/details/80173470
以及书籍:java并发编程艺术

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值