// 非公平锁尝试获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取已经加锁的次数
int c = getState();
// 没有线程持有锁
if (c == 0) {
// 直接抢锁。没有判断队列中是否有线程排队,插队,不公平
if (compareAndSetState(0, acquires)) {
// 抢锁成功
setExclusiveOwnerThread(current);
return true; }
}
// 正在有线程持有锁,并且这个线程是自己(t1)
else if (current == getExclusiveOwnerThread()) {
// t1 已经获取到锁,无需再次获取锁,只需把锁的次数增加即可
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置锁的次数
setState(nextc);
return true;
}
return false;
}
公平锁的实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors: 当线程尝试获取锁时,不是直接去抢,
// 而是先判断是否存在队列,如果存在就不抢了,返回抢锁失败
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;
}
// 是否存在队列并且(下一个待唤醒的线程不是本线程(准备重入锁))
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
JDK1.5以前只有synchronized同步锁,并且效率非常低,大神Doug Lea自己写了一套并发框架,这套框架的核心就在于AbstractQueuedSynchronizer类(即AQS),性能非常高,所以被引入JDK包中,即JUC。那么AQS是怎么实现的呢?本篇就是对AQS及其相关组件进行分析,了解其原理。
AQS 的应用
我们经常使用并发包中的阻塞队列(ArrayBlockingQueue), 可重入锁(ReentrantLock),线程栅栏(CountDownLatch)等一些工具底层都是由AQS实现的
AQS 大致结构
ReentrantLock 实现原理
ReentrantLock 使用简单,我们就以这个类为切入口,学习一下如何利用 AQS 实现加锁释放锁的功能,以及公平和非公平锁实现的差别.
加锁
查看 ReentrantLock 的构造方法:
由此可知,ReentrantLock 的公平锁和非公平锁分别是由
FairSync
和NonfairSync
实现的,由下面的结构图可知,
FairSync
和NonfairSync
都是继承至Sync
,而Sync
又是继承 AQS非公平锁加锁的代码:
accquire(1) 实现:
大致意思就是,线程再抢一次锁,如果失败了,就构造一个线程节点,然后把节点放入队列,将线程挂起,等待被唤醒
再次抢锁代码: tryAcquire(arg):
公平锁的实现
于非公平锁相比,只有
tryAcquire
方法的区别,为什么需要再次抢锁?
因为抢锁失败有两种原因,1是当前线程确实没有获取到锁。2是当前线程之前已经获取到锁了,还想再获取一次。
对于1这种情况,让线程再抢一次,可能会抢到锁,就不用调用系统api把线程挂起,提高性能
对于2, 只需改变加锁的次数,就可以标记当前线程已经加锁的次数了,再释放锁时,对应的减成0就可以认为当前线程已经完全释放锁了,这就是可重入锁的实现原理
构造队列节点及入队
下面看一下构造线程节点的实现:
addWaiter()
经过
addWaiter(Node node)
方法后,队列中至少存在两个节点,第一个就是必须的空节点,不包含线程信息,第二个才是真正待执行的线程节点,作者为什么这么做呢?我认为,队列中存放的不仅是待唤醒的线程节点,而是所有等待运行和正在运行的线程节点,因为已经拿到锁的正在运行的线程不需要被唤醒,所以也就不需要存储线程信息了。并且这个正在运行的线程节点是队列中的头节点
线程挂起
下面就要看
acquireQueued
方法了shouldParkAfterFailedAcquire(Node pred, Node node)
释放锁
释放锁的逻辑比较简单,
获取锁的流程图
队列中节点状态