前言
在我们如果项目中涉及到并发的时候那么我们第一步想到的就是使用锁如果是在分布式的环境下的话那么我们肯定是使用的就是分布式锁这个的话可以看我自己其中的一篇博客如下:Redis最终篇分布式锁以及数据一致性_redisson 哨兵模式分布式锁一致性-CSDN博客但是如果是单机的环境下的我们就是通过使用synchronized但是这个锁的话就是会说如果说线程获得不到锁的话那么就是会一直阻塞这个时候我个人的建议还是说使用ReentrantLock锁,这篇文章的话就是会来说一说这个锁的底层的实现
AQS:
1.什么是AQS
AQS全名就是说AbstractQueuedSynchronizer这个就是说是一个抽象队列同步器,那么通过这个的话首先我们就是能够知道下面这三个消息: 1.这个就是一个抽象类:那么抽象类的作用的话起始就是给我提供了一种规范就是比如我们进行建房子的时候图纸的作用,然后就是提供了一个模块方法,这个模块方法就是在于我们可以通过继承的操作然后对里面的方法进行重写然后自己进行想要实现的业务逻辑进行书写,这个就是说框架已经建好了但是使用什么材料的话就是由自己来决定 2.就是底层的实现就是通过队列这个数据结构进行实现的,那么队列这个数据结构最大的特点就是在于先进先出 3.就是实现多线程之间的同步这个肯定会涉及到JAVA中的JUC包下面的工具类了
2.AQS的具体的实现
其实AQS的主要的实现就是说就是一个acquire(获得锁的一个过程)以及一个realse(释放锁的过程就是这么简单)
前置知识:
我们都知道锁是有互斥性以及可重入性那么这个AQS是怎么进行实现的: 1.state变量:这个就是表示有没有线程获得了锁(state!=0说明有线程已经获得锁了如果state>1的话那就是说明这个线程获得了1次以上的这个锁) 2.ExclusiveOwnerThread:这个变量就是说明了获得这个锁的线程是哪一个线程这个实现了锁的互斥性
2.1 acquire加锁
其实获得锁这个方法就是涉及到两个方法一个就是tryAcquire以及acquireQueue这两个方法下面的话就是来分析这两个方法的具体是怎么实现
tryAcquire(这里的话就是以ReenteantLock为例子进行说明):
当我们要查看这个方法的话那么我们就是可以看到Sync NofairSync以及fairSync其实这个也是特别的简单那就是说Sync其实也是一个抽象类然后fairSync(这个是公平锁的实现)以及NofairSync(这个是非公平锁的实现)这个就是我刚刚在上面说到的抽象类就是提供的tryAcqurie这个模块方法然后fairSync以及NofairSync就是通过实现然后就是通过继承然后重写模块方法并且在这个方法的基础之上的话实现独属于自己的业务逻辑下面的话就是进行具体分析
Sync: 这个就是首先第一步就是判断state变量是否为0如果为0的话那么就是设置state的值表示就是已经有线程获得锁以及设置ExclusiveOwnerThread表示的就是获得锁的线程的线程ID如果不为0的话那么就是判断是否是自己获得锁如果不是的话那么就是加锁失败
protected final boolean tryAcquire(int acquires) {
//获得当前的线程
Thread current = Thread.currentThread();
//获得当前的同步器的状态
int c = getState();
//这个就是有多少线程持有锁
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//如果当前当前获得锁的线程不是线程的
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//这个就是实现锁的可重入性
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
FairSync: 这个就是进行加锁操作的时候就是要看state是否为0除此以外的还要判断队列中是否有等待中的结点(其实这个也就是线程而已)
NoFairSync: 这个相对于faireSync而言的话其实就是说不需要判断对立中是否有正在等待中的结点直接进行加锁的操作
acquireQueued:
这个方法总结来说就是自旋和阻塞入队这两大功能:首先来说的话就是说首先就是说第一次进行获得锁的话如果没有获得锁的话就是会进行判断这个线程是否需要进行阻塞入队的操作这个时候就是shouldParkAfterFailedAcquire()这个方法进行判断这个线程的waitStatus是否为-1(这个就是说线程处于唤醒状态)是的话那么就进行阻塞入队的操作如果不是的话那么就是进行waitStatus数值的调整为-1所有总的来说的话最多的话就是进行两次获得锁的操作然后就会阻塞入队
那么我们点击parkAndCheckInterrupt看看这个AQS进行阻塞的方法是不是就是我们经常使用的object.await()方法我会发现不是使用而是park方法原因: 通过不断地进行跳跃我们就是户发现这个方法使用Native关键字进行修饰那么这个就是说明这个方法是直接在操作系统层面对线程进行阻塞的但是wait方法的话只能在对象的层面所以park方法的话更加灵活但是安全性的话就比较弱
addWaiter():
这个方法的作用的话就是说将这个线程添加到队列当中,那么首先的话就是判断这个尾结点是否尾空如果为空的话那么就是直接将这个线程作为整个队列的头结点然后就是进行尝试获得锁的操作这个就是acquire操作还有另一种情况就是说尾结点不为空那么这个时候将会将这个线程封装为node结点然后就是旧的尾结点的next的话就是会指向这个结点
2.2 realse释放锁
这个里面就是包含两个重要的方法tryRealse(这个实现了释放锁的逻辑)以及unparkSuccessor(这个就是对队列中的线程进行唤醒)
tryRealse:
tryrealse方法当中: 1.就是对当前state变量进行减1的操作2.就是判断当前线程是不是就是持有这个锁的线程,因为这个就是不可抢占性(简单来说就是不能拿别人家的东西) 3.如果线程只是加了一次锁的话那么就设置ExclusiveOwnerThread为null表示没有线程持有锁然后就是设置State变量4.如果不止加一次锁的话那么就是直接设置state变量就行了
unparkSuccessor:
总结:
acquire方法:
1.首先这个线程进行加锁操作的时候这个是一个无限循环如果第一次加锁失败的话那么这个时候就是会进行waitStatus的判断不是-1也是就唤醒的状态的时候那么就是会进行一次状态的调整也就是将waitStatus变为-1然后又会进行加锁的操作的话如果还是失败的话那么这个就会通过park()将这个线程进行阻塞然后就是会调用addWaiter()将这个线程封装为Node结点中然后就是会添加到队列当中
2.加锁操作:当线程进行加锁操作的时候首先就是会判断state变量是否为0如果为0的话那么就说明没有线程获得锁,这个时候就是会通过原子性操作将state赋值为1然后将ExclusiveOwnerThread赋值为自己的线程ID如果state不为0的时候这个时候就说明已经有线程获得锁资源了,那么就是会判断ExclusiveOwnerThread的值是否当前线程的ID如果不是的话那么这个时候就会进行一次自旋这个上面的话已经进行说明如果是当前的线程的ID的话那么这个时候就是会将state进行+1的操作这个就是实现了锁的可重入性
3.addWaiter():这个就是判断头结点是否为空第一种就是不为空的话那么就是会的这个队列当中的尾结点(oldTail)然后进行原子性操作然后就是将这个线程封装为Node然后就是将oldTail.next=Node第二种就是头结点为空那么就是会将这个线程作为头结点然后进行加锁的操作
Realse()方法:
这个就是获得队列里面的尾结点然后进行反向遍历获得头节点然后通过使用unpark方法对这个线程进行唤醒的操作,将state变量进行减1的操作如果为0的话那么就是设置ExclusiveOwnerThread设置为null如果不为空的话那么只是对state变量进行减1的操作
拓展的知识:
其实在整个AQS中存在两个队列一个就是我们整篇文章都在叙述的队列我称为普通队列还有一个队列就是ConditionQueue,这个就是用于条件对象绑定的队列唤醒以及阻塞的逻辑都是一样的不一样的就是不同的Condition的话都会有一个相对应的队列与之相对应