ReentrantLock原理浅析

本文详细解析了Java中ReentrantLock的实现原理,重点介绍了AQS(AbstractQueueSynchronizer)的作用与机制,包括其核心组件如exclusiveOwnerThread、tail、head、state以及Node节点的结构和功能。同时,探讨了ReentrantLock的公平锁和非公平锁的实现差异。

一.简单概括

1.1Java.util.concurrent juc包里面的类实现的核心是AQS(AbstractQueueSynchronizer)抽象队列同步器,要分析ReentrantLock的实现的原理,离不开分析AQS和AQS与ReentrantLock之间的关系

二.分析AQS(AbstractQueueSynchronizer)

2.1:AQS(AbstractQueueSynchronizer)抽象队列同步器的组成:
2.1.1:成员变量包括:
(1)exclusiveOwnerThread:互斥模式下,表示持有锁的当前线程
(2)tail:指向末尾节点
(3)head:指向头结点,当head节点为空的时候,表示没有任何的线程在等待
(4)stat-aqs:同步器的状态
2.1.2:AQS的原理:AQS可以看做一个同步监视器的实现,并且具有排队功能,当一个线程去获取AQS锁资源的时候,如果所资源已经被其他的线程占有,则会新建一个节点,进入到排队队列,这个排队队列也是由AQS自己来维护的,当锁资源被释放的时候,会唤醒下一个节点,尝试去获取锁资源
stat表示AQS的同步状态,他在juc里面有不同的实现:
(1)ReetrantLock:stat为0的时候,表示没有线程占有这个锁,可以获取,>=1的时候,表示有线程咱占有这个锁资源,不可以获取,>1的时候表示有锁的重入
(2)CountDownLatch:(等待其他线程结束,再继续执行线程),stat表示等于等待数量,当stat为0 的时候,执行线程。
(3)Semaphore:(可以设置同时持有这个对象线程数),stat表示还可以获取这个对象的线程数量,当stat为0 的时候,线程尝试获取的时候,会进入排队状态。
2.1.3:AQS有等待队列,等待队列的数据结构如下:
Node节点:AQS的Node节点除了head节点以外,其他的一个节点代表一个等待的线程,每个Node节点的结构如下:
(1)prev:前置节点
(2)next:后置节点
(3)thread:等待的线程
(4)waitstat:等待的状态:
.1:表示中断
.-1:表示阻塞挂起
.-2:表示等待condition
在这里插入图片描述

三.ReentrantLock的具体实现原理

3.1:ReentrantLock有公平锁和非公平锁的两种的实现的方式,FairSyn和NonFairSyn的两种的实现方式,他们都是作为ReentrantLock 的内部类,继承和实现了AQS(AbstractQueueSynchronizer)的Syn
3.2
1.首先来介绍一下 ReentrantLock的NonFairSyn(非公平实现),具体的使用:

 //默认没有参数是创建非公平锁
        ReentrantLock reentrantLock = new ReentrantLock();
        //尝试获取锁
        reentrantLock.lock();
        //释放锁资源
        reentrantLock.unlock();

2.分析reentrantLock.lock()方法:点击方法跳转,找到非公平的实现,

/**
		* Sync object for non-fair locks
		*/
	 static final class NonfairSync extends Sync {
			 private static final long serialVersionUID = 7316153563782823691L;

			 /**
				* Performs lock.  Try immediate barge, backing up to normal
				* acquire on failure.
				*/
			 final void lock() {
					 if (compareAndSetState(0, 1))//第一步
							 setExclusiveOwnerThread(Thread.currentThread());//第二步
					 else
							 acquire(1);//第三步
			 }

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

分析:
(1)第一步:compareAndSetState(0, 1),实际上利用CAS(原子操作)将stat设置成1,如果成功设置成1,则线程获取当前的锁资源,如果设置失败,则说明stat>=1,有其他的线程持有资源
CAS是一个比较并交换的操作,如果比较内存值与期望值0相等,这将内存值stat设置为1,线程获取到锁资源
(2)第二步:setExclusiveOwnerThread(Thread.currentThread()),成功设置为stat为1,获取到锁资源,这需要将ExclusiveOwnerThread设置为获取到锁资源的线程
(3)第三步:acquire(1),如果是设置stat失败,则需要重新尝试获取,或者进入排队

3.分析acquire方法:
第三步调用acquire方法,将会尝试再次回去锁资源,或者会进入排队等候队列,acquire方法是在AbstractQueuedSynchronizer里面实现的

public final void acquire(int arg) {
            if (!tryAcquire(arg) &&//尝试再次的去获取锁资源
                    acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))//如果获取不到就会进入排队等候队列
                selfInterrupt();
        }

4.分析tryAcquire方法
tryAcquire是在具体的子类里面实现的,他会尝试的再次去获取锁资源,如果是可重入获取锁资源,stat则会进行+1操作,而每次unlock都会将stat进行-1操作,直到stat为0,唤醒等待队列里面的下一个线程

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

5.分析acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
首先是addWaiter(Node.EXCLUSIVE)方法,他是由AQS实现的,他会将获取不到的锁资源的线程,组装成一个node节点,然后添加到等待队列里面,这里线程并没有被挂起

private Node addWaiter(Node mode) {
			 Node node = new Node(Thread.currentThread(), mode);//创建一个新的node节点,传入当前的线程,mode则表示为互斥模式
			 // Try the fast path of enq; backup to full enq on failure
			 Node pred = tail;//前一个为之前的最后一个
			 if (pred != null) {
					 node.prev = pred;
					 if (compareAndSetTail(pred, node)) {//比较并设置为末尾
							 pred.next = node;
							 return node;
					 }
			 }
			 enq(node);
			 return node;
	 }

然后分析acquireQueued方法:
acquireQueued方法其实就是为了减少线程挂起、唤醒次数而作的优化操作。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//获取当前节点的前置节点
                if (p == head && tryAcquire(arg)) {//第一步,如果前置节点是head,该节点的线程则不会挂起,会继续尝试获取锁资源,这样就避免了线程切换的成本
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//第二部,我们首先会检查这个线程是否需要被挂起,当节点waitstat为-1的才挂起,parkAndCheckInterrupt,挂起
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Michael_Chou_Rider

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值