一、ReentrantLock原理分析
ReentrantLock中有个成员变量Sync,提供同步机制。Sync继承了AbstractQueuedSynchronizer。Sync有两种实现,公平和非公平。可以在构造ReentrantLock的时候传递一个布尔值来决定实现公平或非公平。默认为非公平。以下都是基于非公平的实现。
AbstractQueuedSynchronizer中包含了锁的状态state(volatile修饰的),提供一个阻塞队列(FIFO、双向链表),保存了获得锁的线程。在AQS中有很多变量用volatile修饰。
lock()方法
当线程进行加锁的时候,先通过CAS比较state的值是否是0,是就修改为1,表示抢占到锁,然后调用setExclusiveOwnerThread()方法将当前线程保存起来,说明当前线程独占了这个锁。
CAS操作保证只有一个线程能够对state修改成功
非公平体现在当有新的线程调用lock()时,刚好state为0,会去跟阻塞队列中的线程竞争,新的线程CAS成功抢占到锁,而阻塞队列中的线程还得继续等待。新的线程是没有进阻塞队列的。性能会更高,省去了阻塞跟唤醒操作,减少CPU开销。最坏的情况下,阻塞队列中的线程会一直拿不到锁。
公平的实现则是,线程在加锁之前会先去看阻塞队列是否有线程,有则加入到阻塞队列中去,而不是去抢占锁。这样先来的线程就会先获得锁。
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
CAS失败的则调用acquire()方法,参数传了1。
1、tryAcquire()方法,尝试着继续抢占锁。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//无锁状态,抢占锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//锁的持有者等于当前线程,即重入,state+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
2、addWaiter()方法,将线程加入到阻塞队列中。
private Node addWaiter(Node mode) {
//new一个Node节点,mode是Node.EXCLUSIVE, 表示锁是独占的
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {//尾插法,队列已经初始化了,跟enq()方法类似
node.prev = pred;
//尝试一次就将节点插入到队列中,失败了,再调用enq()方法去重复的尝试插入队列
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
在最开始的时候队列是没有初始化的,head、tail节点都是null,if判断不成立,调用enq(node)方法将节点加入到队列中。
private Node enq(final Node node) {
for (;;) { //自旋,直到插入队列成功才结束
Node t = tail;
//队列还未初始化,第一次循环
if (t == null) { // 初始化节点,head、tail指向一个空的Node
if (compareAndSetHead(new Node()))//将head节点指向空的Node节点
tail = head;//将tail节点也指向空的Node节点
} else { //队列已经初始化,若head、tail节点都指向一个空的Node节点
//这里的t是空的节点
node.prev = t;//将代表线程的节点的前节点指向tail节点,即空的节点
if (compareAndSetTail(t, node)) {//修改tail节点指向代表线程的节点
t.next = node;//将空的节点的下个节点指向代表线程的节点
return t;
}
}
}
}
就是将节点插入尾部,跟tail节点指向的原来的尾部节点连接起来,再将tail节点指向新的节点,新的 节点成为了尾节点。返回插入节点的前节点(即原tail节点)。
3、acquireQueued()方法
node当前线程节点,arg为1
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)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
for循环自旋,拿到当前线程节点的前一个节点。第一个判断,如果前一个节点为头节点,且tryAcquire()抢占锁成功,则把当前线程节点设置为head节点,将线程节点设置为null(已经抢到锁有执行权了,就不需要保存线程了)。第二个判断,shouldParkAfterFailedAcquire()判断当前线程节点在抢占锁失败后是否应该被挂起(节点的状态默认为0,第一次调用这个方法会将节点状态修改成SIGNAL,返回false,后面自旋再执行到这个方法时就会返回true了),是则调用parkAndCheckInterrupt()将线程挂起。意味着当前线程已被阻塞在这个地方(不会再执行了)。
unlock()方法
unlock()方法调用的release()方法
线程执行完后需要去释放锁,并且唤醒阻塞队列中的线程。
唤醒阻塞队列中的线程都是由head节点来唤醒的,FIFO,所以release()方法拿的是head节点
1、tryRelease()方法
参数releases为1
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//不为0,则代表重入了
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
将state值减一,如果state为0,则释放锁,将持有锁的线程设置为null。如果不为0,则不会去唤醒阻塞队列中的线程。
if (h != null && h.waitStatus != 0) unparkSuccessor(h);
释放锁成功后,进行判断,头节点不为空且头节点的状态不为0(前面shouldParkAfterFailedAcquire()方法将前一个节点的waitStatus设置为SIGNAL了,-1),则调用unparkSuccessor()方法去唤醒阻塞队列中的线程。
2、unparkSuccessor()
唤醒head节点的下一个节点的线程。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus; // -1
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); //修改head节点的状态
Node s = node.next; //拿到下个节点,即需要被唤醒的线程节点
if (s == null || s.waitStatus > 0) { //节点为空或节点状态为CANCELLED,则去除无效节点
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //唤醒线程
}
线程被唤醒后会继续去执行上面lock()方法中的acquireQueued()方法(这个方法是会一直自旋的,被挂起阻塞了就暂停,等着被唤醒后继续循环,直到抢占到锁才会结束)。
无限套娃,不说了
中断处理
线程在抢占锁失败后被阻塞了,在别的线程中去中断这个线程,中断线程会唤醒线程。为了响应中断,在parkAndCheckInterrupt()中会返回当前线程的中断标记。将这个中断标记往上层返回,在acquire()方法中会调用selfInterrupt()方法,进行自我中断。在这个线程获得锁后可以根据这个中断标记去做相应的处理。
实际上,就是为了告诉这个线程在阻塞期间被中断过,等到获得锁有执行权的时候线程再自行对这个中断信号进行处理。可以结束当前线程,也可以啥都不做。取决于线程是怎么处理的。
二、Condition分析
Condition是搭配锁来使用的,提供了await()和signal()方法
AbstractQueuedSynchronizer中有一个类ConditionObject,继承了Condition。
相对于AQS队列中的head、tail节点,condition队列中有firstWaiter、lastWaiter节点。AQS队列是同步队列,双向链表;condition队列是等待队列,单向链表。
await()方法
前提是调用这个方法的线程已经获得了锁。
当满足某种条件的时候可以调用await()方法让当前线程阻塞。
1、addConditionWaiter()
将线程添加到condition等待队列中。
private Node addConditionWaiter() {
Node t = lastWaiter;
//如果尾节点是无效的,则清除掉
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
将当前线程包装成一个节点,节点状态为 CONDITION。同样是尾插法,如果condition队列中没有线程节点,则将firstWaiter和lastWaiter都指向加入的节点;如果已经存在线程节点了,将加入的节点设置为lastWaiter指向的线程节点的next,然后再将lastWaiter指向加入的节点。可以看出这里只是从前指向后,是单向的。
2、fullyRelease()
加入到condition等待队列中后,需要释放锁。
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
int savedState = getState(); // 获得state的值
if (release(savedState)) //将state的值传过去
这里release方法的参数是state的值而不是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;
}
跟上面unlock()中的方法是一样的操作。释放锁,并唤醒AQS队列中head的下一个节点。
3、isOnSyncQueue()
判断加入condition队列的节点的状态是CONDITION,返回false。然后调park()方法阻塞当前线程。线程就被阻塞在这个地方了,当线程被唤醒的时候,继续执行。继续while循环,调用isOnSyncQueue(),节点状态已经被改为0,且加入了AQS队列,返回ture,结束while循环。
4、checkInterruptWhileWaiting()
线程被唤醒之后,判断是否有被中断,有则break,没有则继续while循环。
5、acquireQueued()
上面已经分析过了,即自旋抢占锁。
await()方法最后面的两个判断,一个是为了清除无效节点,一个跟中断处理有关。
signal()方法
获取到等待队列中的firstWaiter,不为空则调用doSignal()方法,参数为firstWaiter。
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
doSignal()中是do…while循环,先把first的下个节点赋给firstWaiter,判断是否为null,是则把lastWaiter设为null(说明等待队列中没有其他节点了)。first.nextWaiter = null,将first节点在等待队列中断开,方便GC。然后进入while判断,如果transferForSignal()方法返回false,则表明当前的first节点有异常,然后将等待队列中的下个节点赋给first,再进行循环(如果下个节点为null,则结束循环)。
接下来看看transferForSignal()的实现。
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
transferForSignal()方法首先进行一个CAS操作,将节点状态从CONDITION改为0,如果失败则返回false,回到doSignal()方法继续执行(修改失败代表当前first节点是CANCELLED状态)。修改成功则调用enq()方法将节点加入到AQS队列中(enq()方法上面已经讲过,不再赘述)。enq()方法返回原tail节点 p (原tail节点有可能是head节点指向的节点,即AQS队列中没有阻塞的线程,或者是其他线程节点)。获取到p节点的状态进行判断,如果大于0(即CANNELLED状态)或者CAS修改为SIGNAL状态失败,则调用unpark()方法唤醒上面加入AQS队列的线程(在这个地方唤醒,是为了提升性能。无效节点会遍历队列进行清除,AQS队列线程会被唤醒)。
v1.1 AQS在ReentrantLock中的实现
v1.2 Condiotion的实现
v1.3 待补充。。。