


其实它是定义了一个静态的内部类去继承AQS这个抽象类,然后再基于AQS中的方法去定义自己的方法。
ReentrantLock是悲观锁,也是可重入的,悲观锁就是同一时刻只能有一个线程获得锁,可重入就是一把锁可以被同一个线程多次获得,有两个重要的属性,一个是AQS中的state,记录锁重入的次数,另外一个是AQS父类AbstractOwnableSynchronizer中的exclusiveOwnerThread属性,叫当前拥有锁的线程,AQS中的Q代表queue,队列,如果同一时刻有多个线程过来抢占锁,最后肯定是只有一个线程获得锁,其它线程就会到队列里等待,这个队列就是基于AQS里的Node类构建的:

AQS中使用的队列是CLH队列的一个变种,因为CLH队列里的线程不是阻塞等待而是自旋等,这个队列里的节点有几个重要的属性:
/*** 共享模式节点* */static final Node SHARED = new Node();/*** 独占模式节点*/static final Node EXCLUSIVE = null;/*** 如果在同步队列中的线程等待超时或者被中断,* 在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待* */static final int CANCELLED = 1;/*** 后继节点的线程处于等待状态,而当前的节点如果释放了同步状态或者被取消,* 将会通知后继节点,使后继节点的线程得以运行。*/static final int SIGNAL = -1;/*** 节点在等待队列中,节点的线程等待在Condition上,当其他线程对Condition调用了signal()方法后,* 该节点会从等待队列中转移到同步队列中,加入到同步状态的获取中*/static final int CONDITION = -2;/*** 表示下一次共享式同步状态获取将会被无条件地传播下去*/static final int PROPAGATE = -3;/*** 标记当前节点的信号量状态 (1,0,-1,-2,-3)5种状态* 使用CAS更改状态,volatile保证线程可见性,高并发场景下,* 即被一个线程修改后,状态会立马让其他线程可见。*/volatile int waitStatus;
构建什么样的队列,以及队列中的节点目前处于什么状态吗,都是通过修改waitStatus属性为以上五个值来实现的。AQS中的队列分为同步队列和条件队列。Node中哪些属性用于同步队列,哪些属性又用于条件队列呢?

上面红框的属性都是各自队列独有的,本篇文章只介绍同步队列,由于条件队列需要特定知识做铺垫,就放到后续章节中说。
下面通过一个小Demo分析下ReentrantLock加锁和解锁以及其中同步队列的原理:
package com.ayo.dubbo.consumer;import java.util.concurrent.locks.ReentrantLock;/*** 1、分析ReentrantLock中的可重入锁* 2、分析ReentrantLock中的同步队列** @Authror ayo* @Date 2021/1/19 14:32*/public class LockDemo {/*** 定义一把杀猪刀*/private ReentrantLock knife = new ReentrantLock(true);/*** 杀猪* @param peopleName*/public void KillThePig(String peopleName){System.out.println(peopleName + "准备杀猪");//要杀猪得先拿起刀knife.lock();System.out.println(peopleName + "杀第一头猪");//又拿起刀(很多人等着用刀),但是必须杀够两头后才会把刀给别人knife.lock();System.out.println(peopleName + "杀第二头猪");/*** 加几次锁,就得释放几次,这样才能完全释放掉*/knife.unlock();knife.unlock();System.out.println("归还杀猪刀");System.out.println("---------------------");}public static void main(String[] args) {final LockDemo lockDemo = new LockDemo();new Thread(new Runnable() {@Overridepublic void run() {String name = Thread.currentThread().getName();lockDemo.KillThePig(name);}}, "老王").start();/*new Thread(new Runnable() {@Overridepublic void run() {String name = Thread.currentThread().getName();lockDemo.KillThePig(name);}}, "老王媳妇").start();*/}}
(一)、首先看第一条,可重入锁,在killThPig方法的第一行打个断点,然后debug方式运行main方法,一步一步观察knife中的属性变化:





可以看到state的变化,加一次锁,state的值就+1,释放一次锁,state的值就-1,exclusiveOwnerThread如上所说,就是当前持有锁的线程。那么它加锁的原理是什么,这就得看看源码了,一步步点进去lock方法:



最后会发现是一个抽象方法,看下它的实现:

会发现有两个,那应该看哪一个呢?看下刚才我写的demo,类的第一行的:
/*** 定义一把杀猪刀*/private ReentrantLock knife = new ReentrantLock(true);
构造ReentrantLock的时候传入的是true,点进去看你会发现:

这里说下,其实这个参数的含义表示是否公平,true代表是一把公平锁,false代表是一把非公平的锁,举个例子:下班高峰期你去坐地铁,人超级多,这时候但凡是个有素质的人就会在那排队进站,这就是公平锁,如果遇到个没素质的人不排队直接去前面进站,这就是非公平锁。
1、先看公平锁的实现

public final void acquire(int arg) {//尝试获取锁if (!tryAcquire(arg) &&/*如果获取失败就尝试进入队列,注意进入队列的方式是独占,ReentrantLock是一个独占锁,独占的意思是同一时刻只有一个线程能持有锁,相反共享就是这把锁可以同时被多个线程持有*/acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//中断线程阻塞等待selfInterrupt();}
/*** 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();//获取state的值int c = getState();//判断如果state的值是0,标识锁没有被任何线程持有if (c == 0) {/*为毛说是公平的,就在于这里,它先去判断了队列里有没有排队的,没有的话再去修改state的值*/if (!hasQueuedPredecessors() &&//修改state的值为1,标识抢锁compareAndSetState(0, acquires)) {//将自己的线程设置为当前持有锁的线程setExclusiveOwnerThread(current);return true;}}//否则如果当前持有锁的线程是自己else if (current == getExclusiveOwnerThread()) {//那就把state的值+1int nextc = c + acquires;//如果state的值+1后小于0,就抛出异常if (nextc < 0)throw new Error("Maximum lock count exceeded");//将state的值+1setState(nextc);return true;}return false;}
2、非公平锁的实现

final void lock() {//一上来我不管3721我先修改state的值为1,也就是抢锁if (compareAndSetState(0, 1))//如果抢锁成功就把独占线程设置为自己setExclusiveOwnerThread(Thread.currentThread());else//没抢成功,就去按公平锁的方式获取acquire(1);}
可以看到,非公平锁其实就是一上来先去抢下试试,赌的就是锁整好被别的线程释放,这时候有可能抢到的,为什么说有可能?因为如果同步队列里有别的线程的话,持有锁的线程释放锁之后,会通知队列里的线程去抢锁,这就行成一定的竞争。
源码里用到了大量的CAS算法去修改值,你点进去之后会发现其实用的都是unsafe类直接操作内存实现的:

包括变量都是用volatile修饰,用来防止并发操作带来脏数据。

(二)、分析ReentrantLock中的同步队列
接下来把上面demo中的老王媳妇注释放开,再debug,这时候观察sync的属性:

这时候head和tail是不是有值了?其实本质上这个同步队列是一个由AQS中的Node节点组成的双向链表,链表中的每个节点通过waitStatus来标识当前节点所处的状态。
关于具体是怎么入队列,以及节点之间的指针是怎么连接的,主要是AQS中的这段源码:

有兴趣的童鞋可以研究下,这里不再赘述,然后看下解锁方法:


protected final boolean tryRelease(int releases) {//将state的值减1int c = getState() - releases;//如果当前线程不是持有锁的线程,抛出异常,因为你本来就没持有锁,搁这释放个毛线啊if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//判断如果state减1后的值为0if (c == 0) {//释放锁成功free = true;//将持有锁的线程置为空setExclusiveOwnerThread(null);}//修改state的值setState(c);return free;}
关于AQS和ReentrantLock就说到这,没看过源码的可能觉得有点陌生,多看看就好了,我以前也是见源码就排斥,但是看多了,每次看到不懂的都想点进去。




