我们从ReentrantLock 来分析,ReentrantLock 叫互斥锁也叫可重入锁,可重入的特性可以避免死锁,它是基于AQS来实现的,ReentrantLock采用内部自己定义的一个抽象静态类Sync 来管理锁 那么Sync则继承了AQS ,ReentrantLock 内部通过继承Sync抽象静态类,实现了两种锁一种是公平锁,一种是非公平锁,ReentrantLock实现了AQS的两种模型,以下就是ReentrantLock调用链路图。
以下是ReentrantLock 的lock 方法调用时序图
AQS到底是个啥,AQS全称 AbstractQueuedSynchronizer 它是一个同步工具类,AQS 包含两种模式一种是互斥模型一种是共享模型两种不同的模型
互斥——>互斥锁 多个线程同时访问只有一个线程能够获得锁 例如ReentrantLock
共享——>读写锁 多个线程同时访问都能获得锁,不会有阻塞 例如ReadWriteLock
AQS是一个抽象类,其他工具类继承这个抽象类,类似模版模式,并且重写里面的方法,并对内存中的锁标记state,进行逻辑封装
AQS 基于变量state 锁标记 和双向链表实现的队列 从而实现的线程同步机制
从上面这幅图可以看出来,头节点指向一个节点,尾节点指向一个节点,这样就形成了一个双向链表的形式,在AQS中,只有头节点才能获得到锁,其他的节点,都处于在阻塞阶段,
reentrantLock源码
public void lock() {
sync.lock(); //调用内部抽象静态同步类的锁方法,这个lock()方法是一个抽象的方法,主要实现包括非公平锁和公平锁的方法
}
//非公平锁实现类
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)) //采用cas 根据期望的值和内存中的值进行比较,如果相同,则进行更新成当前线程传入的值,虽然是原子性的操作,但是会产生ABA的问题。
setExclusiveOwnerThread(Thread.currentThread());//设置为获得锁的线程
else
acquire(1); //尝试获得锁 调用AQS类中的方法acquire
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
aqs源码
//对没有获得锁的线程进行尝试获得锁
public final void acquire(int arg) {
//尝试获得锁失败然后初始化节点并且对获得锁失败线程封装成Node节点数据并放入同步队列中
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//AQS定义了一个尝试加锁的方法,方便其他继承这个抽象类,并重写这个方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
reentrantLock源码
//非公平锁实现类
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)) //采用cas 根据期望的值和内存中的值进行比较,如果相同,则进行更新成当前线程传入的值,虽然是原子性的操作,但是会产生ABA的问题。
setExclusiveOwnerThread(Thread.currentThread());//设置为获得锁的线程
else
acquire(1); //尝试获得锁 调用AQS类中的方法acquire
}
//重写了AQS的tryAcquire 方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
//非公平锁进行尝试加锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //获取当前线程
int c = getState(); //调用aqs获取加锁标记state,这个state在aqs里面是volatile 修饰的
if (c == 0) { //如果当前内存中的锁标记为0,那么证明没有线程获得锁,所以下面又尝试获得锁,这个就是非公平锁,插队的性质
if (compareAndSetState(0, acquires)) { //进行cas 比较,进行加锁
setExclusiveOwnerThread(current); //设置独占线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //内存中的state标记不为0,但是当前线程等于内存中独占的线程,证明,该线程已经获得锁了,获得锁的线程再次尝试获得锁,就是重入
int nextc = c + acquires; //那么重入,则就会进行+1操作,内存中记录重入的次数
if (nextc < 0) // overflow //对c进行校验,防止c+1之后小于0
throw new Error("Maximum lock count exceeded");
setState(nextc);//重新设置锁标state的数值
return true;
}
return false;
}
如果这块进行尝试加锁,如果加锁没有成功,则就会返回到上面aqs 中的 acquire 方法里面的addWaiter 方法
//添加到等待队列中,这里面Node 可以有两种模式
//一种是SHARED 共享模式,一种是 EXCLUSIVE 独占模式 ReentrantLock 非公平锁是一种独占模式
private Node addWaiter(Node mode) {
//对当前线程封装成node节点
Node node = new Node(Thread.currentThread(), mode);
//这个tail 就是Node节点中的属性值,尾节点,形成双向链表的属性其中之一
//这个tail 是volatile修饰的
Node pred = tail;
if (pred != null) {
node.prev = pred; //把刚刚封装好的node的上一个节点指向pred节点
if (compareAndSetTail(pred, node)) { //这面是cas原子性进行比较,把当前的tail节点和内存中tail节点进行比较,如果相等,则就把刚刚封装好的node节点赋值给内存中的tail节点
pred.next = node; //把node加入到了链表中
return node;//返回尾节点
}
}
enq(node); //如果尾节点等于null则进行节点的初始化操作
return node;
}
//这个方法实现是一种自旋的形式
//第一遍for循环 进行节点的头节点和尾节点进行初始化操作
//第二遍for循环 把未获得锁的线程封装成 node加入到链表,也是说形成一个双向链表
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t; //把node节点上一个节点指向尾节点
if (compareAndSetTail(t, node)) { //根据尾部节点和传进来的节点进行cas比较,如果和内存中节点相同则就会把内存中的尾节点更新成node节点
t.next = node; //原来的尾节点下一个指向node节点,形成一个双向链表
return t;//返回原来的尾部节点
}
}
}
}
//把没有获得锁的节点加入到队列中
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)) {//判断p是不是一定等于head 会不会有数据不一致问题,并且再一次尝试加锁
setHead(node); //设置头节点为node节点
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//获取节点的上一个节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
//争强锁失败之后,把线程挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //表示节点的状态
if (ws == Node.SIGNAL) //如果该节点==-1则证明需要线程挂起操作
return true;
if (ws > 0) { //大于0表示取消节点状态,
//什么时候节点会出现 CANCELLED 状态,当同步队列里面的线程出现等待超时出现异常的情况或者代码里面主动中断则会赋予CANCELLED 状态
do { //这块等于CANCELLED 状态的pred节点过滤掉
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;//pred节点下一个节点指向node节点
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //把前一个节点状态置为-1
}
return false;
}
//挂起线程并返回中断状态
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//该线程挂起
return Thread.interrupted(); //返回该线程的中断状态并且该线程的中断状态在内存中进行复位操作。
}
//挂起当前线程
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
以上的代码,就是多个线程同时去竞争锁,当只有一个线程获得锁之后,其他线程则封装成Node节点形成双向链表,对节点里面的wait状态进行更新操作,然后线程节点进行挂起操作,等待获得锁那个线程,释放锁操作,然后这个线程的下一个线程则被唤醒,然后竞争锁操作。
以下代码就是如果进行释放锁,并且被挂起的线程如何被唤醒,然后重新竞争锁
//ReentrantLock 释放锁操作
public void unlock() {
sync.release(1);
}
//释放锁
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的tryRelease这个方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}