Java多线程(9)ReentrantLock原理

上篇:Java多线程(8)线程池

ReentrantLock原理

继承关系

在这里插入图片描述

在这里插入图片描述

  • ReentrantLock 在内部用了内部类 Sync 来管理锁,所以真正的获取锁和释放锁是由 Sync 的实现类来控制的。
  • Sync 有两个实现,分别为 NonfairSync(非公平锁)和 FairSync(公平锁)。
  • 默认是非公平锁
public ReentrantLock() {
	sync = new NonfairSync();
}

一. 加锁流程

假设有两个线程,Thread-0和Thread-1,竞争同一个对象锁

当Thread-0调用lock方法时:
在这里插入图片描述
sync.lock()的实现:
在这里插入图片描述

当Thread-0进来时(此时没有竞争):
在这里插入图片描述

加锁过程

使用cas原子操作尝试将对象锁的状态(state) 从 0 改变成 1
在这里插入图片描述

如果改变成功,则直接将对象的OwnerThread设置为Thread-0

当第Thread-1调用lock方法尝试获取锁(即出现竞争关系)
在这里插入图片描述

Thread-1尝试将对象锁的状态从0改变成1
在这里插入图片描述

但,此时锁的状态已经为1 所以这次cas操作会失败,然后他会走else下面的acquire方法

acquire方法源码:
在这里插入图片描述
这个时候Thread-1会再次去尝试获取这个锁:
在这里插入图片描述
这个时候有两种情况

  1. 尝试获取锁的期间,如果Thread-0线程放开了锁,那么他会立刻得到这把锁(原理也是将锁的状态改为1,OwnerThread设置为Thread-1)
  2. 尝试获取锁的期间,还是加锁失败了,那么他会执行addWaiter方法
    在这里插入图片描述

进入addWaiter,Thread-1会构造一个Node(结构为双向链表)队列

  • Node的创建是懒惰的
  • 第一个Node成为Dummy(哑元)或哨兵,用来占位不关联线程

在这里插入图片描述
在这里插入图片描述

接下来Thread-1会执行acquire方法

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

进入shouldParkAfterFailedAcquire,发现前驱节点head的waitStatus不是SIGNAL,compareAndSetWaitStatus将head的waitStatus设置为-1

SIGNAL定义为-1
在这里插入图片描述

在这里插入图片描述

设置完成之后,回到 acquireQueued方法,for无限循环最后Thread-1还是再次进入shouldParkAfterFailedAcquire
在这里插入图片描述
Thread-1再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true
在这里插入图片描述
返回true以后,进入 parkAndCheckInterrupt

在这里插入图片描述
在这里插入图片描述

Thread-1 park(灰色表示)

在这里插入图片描述

若是多个线程经历上述失败过程:
在这里插入图片描述

二. 释放锁 unlock

在这里插入图片描述
在这里插入图片描述
进入tryRelease,若成功:

  • 设置 exclusiveOwnerThread 为 null
  • state = 0
    在这里插入图片描述
    在这里插入图片描述

三. 释放成功竞争锁

在这里插入图片描述
进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中的最近node是Thread-1

那么 Thread-1 回到 acquireQueued方法

在这里插入图片描述
在这里插入图片描述

如果这个尝试加锁成功(没有线程竞争或者为公平锁的情况下)那么:

  • exclusiveOwnerThread 为 Thread-1,state = 1
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
  • 原本的 head 因为从链表断开,而可被垃圾回收

在这里插入图片描述

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

在这里插入图片描述

如果不巧又被 Thread-4 占了先

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

四. 锁重入

当对象有锁的的时候,他会进入这个判断,如果当前线程等于加锁的线程那么他的state 就会++
在这里插入图片描述

解锁时:
每一次释放会让state -1只有等 state==0的时候才能释放锁

在这里插入图片描述

五. 公平锁

// 与非公平锁主要区别在于 tryAcquire 方法的实现
protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		// 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
		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;
}

猜你喜欢

转载自blog.csdn.net/haiyanghan/article/details/109677808