先关注LockSupport
/**
* Basic thread blocking primitives for creating locks and other
* synchronization classes.
* ...
*/
一开始这句话能够理解这是一个基本阻塞工具,AQS中实现了队列,但是当一个线程没有获取锁的时候,是如何阻塞的呢,便是基于这个类。
1、LockSupport 重要API
Park
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L); //核心在这句,等待unpark
setBlocker(t, null);
}
/**
当前park的线程, parkBlocker对象,类似于sync(o).
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
*/
public static void park() {
UNSAFE.park(false, 0L); //核心在这句,等待unpark
}
大体上就是加锁等待,其他parkNano等方法类似sleep。
unPark
//解除封印
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
2、 一个互斥锁
基于LockSupport实现,来自于java.util.concurrent.locks.LockSupport.
class FIFOMutex {
// 标识
private final AtomicBoolean locked = new AtomicBoolean(false);
// 等待队列
private final Queue waiters = new ConcurrentLinkedQueue();
// 加锁
public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
// 线程安全的
waiters.add(current);
// 头节点是当前线程,且lock为true ,则代表不需要阻塞.循环的目的就是防止用户执行unpark
while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
// 执行到这里会阻塞/等待
LockSupport.park(this);
if (Thread.interrupted()) // ignore interrupts while waiting
wasInterrupted = true;
}
// 移除头节点,因为当前线程不需要等待.
waiters.remove();
if (wasInterrupted) // reassert interrupt status on exit
current.interrupt();
}
// 解锁
public void unlock() {
// 设置,这里由于就一个线程操作, 所以不需要cas操作.
locked.set(false);
// unPark下一个等待的线程.
LockSupport.unpark(waiters.peek());
}
}
acquireQueue()
我们先从acquire开始分析:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
先调用tryAcquire(自实现)去获取,失败后加入队列,我们再看addWaiter方法:
private Node addWaiter(Node mode) {
//以给定模式构造结点。mode有两种:EXCLUSIVE(独占)和SHARED(共享)
Node node = new Node(Thread.currentThread(), mode);
//尝试快速方式直接放到队尾。
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//上一步失败则通过enq入队。
enq(node);
return node;
}
enq就不贴了,自旋添加入队,重点关注acquireQueue();通过tryAcquire()和addWaiter(),该线程获取资源失败,已经被放入等待队列尾部了。聪明的你立刻应该能想到该线程下一部该干什么了吧:进入等待状态休息,直到其他线程彻底释放资源后唤醒自己,自己再拿到资源,然后就可以去干自己想干的事了。没错,就是这样!是不是跟医院排队拿号有点相似~~acquireQueued()就是干这件事:在等待队列中排队拿号(中间没其它事干可以休息),直到拿到号后再返回。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;//标记是否成功拿到资源
try {
boolean interrupted = false;//标记等待过程中是否被中断过
//又是一个“自旋”!
for (;;) {
final Node p = node.predecessor();//拿到前驱
//如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)。
if (p == head && tryAcquire(arg)) {
setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null。
p.next = null; // setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了!
failed = false; // 成功获取资源
return interrupted;//返回等待过程中是否被中断过
}
//如果自己可以休息了,就通过park()进入waiting状态,直到被unpark()。如果不可中断的情况下被中断了,那么会从park()中醒过来,发现拿不到资源,从而继续进入park()等待。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;//如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为true
}
} finally {
if (failed) // 如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。
cancelAcquire(node);
}
}
这里面有两个值得关注的方法,一个是shouldParkAfterFailedAcquire(),另一个是parkAndCheckInterrupt():
前者是用来检测自己是否真的可以去休息了,也就是进入线程的waiting状态,只是一个检查;后者是去休息;
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//拿到前驱的状态
if (ws == Node.SIGNAL)
//如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了
return true;
if (ws > 0) {
/*
* 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
* 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)!
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//调用park()使线程进入waiting状态
return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的。
}
总结下它的流程吧:
- 调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
- 没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
- acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
- 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
参考原文:Java并发之AQS详解