通过前面几章的学习,我的脑海里回顾最多的一句话就是:多个线程争抢一把锁,抢到锁的线程执行下面的逻辑,没抢到锁的线程则阻塞。
下面来看一段ReentrantLock的简单代码:
private Lock lock = new ReentrantLock();
private void sayHello() {
lock.lock();
System.out.println("hello");
lock.unlock();
}
这段代码只需要lock和unlock一下就能保证多线程环境下数据的安全性,其实它的内部实现就是通过AQS。
从ReentrantLock走进AQS
我不会把里面的源码都贴出来看,而是尽可能的通过故事情节来描述,也就是所谓的从业务角度来分析。(情节采用的是默认的非公平锁)
情节1: 从前有三个线程:线程A、线程B和线程C。他们都访问了sayHello方法,并且执行了代码:lock.lock();
情节2: compareAndSetState(0, 1)就是CAS操作,类似数据库的乐观锁。多个线程同时去CAS,但是只会有一个成功。举例:AQS里有个共享变量state=0(可以理解为锁标记:0表示无锁,1表示第一次获得锁,大于1表示多次获得锁,也就是重入锁次数),线程A首先会去看state是否等于0,如果等于则更新为1。此时线程B再去CAS时,发现state=1,说明已经有其他线程进来了,所以线程B CAS失败。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
假设,线程A CAS成功了,那么就会将线程所有者设置为线程A,宣誓线程A获得锁,然后线程A继续执行System.out.println(“hello”);
情节3: 线程A获得锁后,就开始执行后续代码了,线程B和线程C就只能走acquire(1);
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 没获得锁的线程想再去尝试获得锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 表示重入锁,增加可重入次数
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;
}
线程B/C的侥幸心理认为:万一线程A获得锁很快就释放了呢?所以我应该再去尝试一次,于是乎读取int c = getState();如果c=0表示线程A确实释放锁了,那么线程B/C再去CAS尝试获得锁。
情节4: 然而,线程B/C的侥幸心理并没有实现,此时的线程A还是没有释放锁。因此,线程B/C都进入自旋状态。
private Node addWaiter(Node mode) {
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) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return 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;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
执行Node node = new Node(Thread.currentThread(), mode);线程B/C都被会封装成Node节点。而执行到compareAndSetHead(new Node())时,这里又有一个CAS操作,就看谁先成功,谁就先创建一个空的Node节点。
情节5: 假设线程B CAS成功了,线程B会创建一个空的Node结点,并且线程节点B和空节点Node会形成双向链表。
情节6: 类似的,线程节点C会跟在线程节点B的后面,并且tail指针指向线程节点C。
情节7: 此时,线程B和线程C都已经加入同步队列了,但是不可能一直就在里面自旋着。他们有2个选择:第一,尝试再去获取锁。第二,如果还是获取锁失败,那么就只能阻塞了。
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);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
这里会将head节点以及它的next节点waitStatus设置为SIGNAL,并且调用LockSupport.park(this);将队列里的线程挂起。这时候,线程B和线程C趋于稳定,后续就等待线程A释放锁了。
情节8: 线程A执行完System.out.println(“hello”);并且调用了lock.unlock();释放锁。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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;
}
线程A释放锁实际上也分为两步:第一,设置exclusiveOwnerThread = null以及state=0。第二,唤醒一个线程。这里会直接找到头结点,然后唤醒它的next结点,也就是唤醒线程B。线程B被唤醒后,重新去CAS,由于此时state=0,所以CAS成功,线程B最终获得了锁。至于为啥唤醒的是线程B而不是线程C,原因就在于之前线程B优先CAS成功,并创建了空的Node结点,使得它可以成为头结点的next结点。所以,这个链表也是FIFO的双向链表。
情节9: 线程B被唤醒后,就会沿着它原来阻塞的位置继续执行,链表里的线程节点B也就失去了意义。这里会将线程节点B的thread设置为null,并且head指针指向节点B。原来的头结点被删除了,节点B成为了新的头结点。
以上就是ReentrantLock的非公平锁获得锁和释放锁的过程。
公平锁和非公平锁的区别
以上的原理分析针对的是非公平锁,它还有公平锁的实现。公平锁的AQS逻辑基本跟非公平锁一致,下面来看下两者的区别。
公平锁: 严格按照执行顺序来,也就是等待时间越长的线程越先获得锁。
非公平锁: 允许插队,线程获得锁的几率是个随机事件。
lock方法差异
公平锁:调用acquire(1);
final void lock() {
acquire(1);
}
非公平锁:一上来就CAS,谁先CAS成功谁获得锁,完全是个概率时间。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
tryAcquire方法差异
公平锁:多了!hasQueuedPredecessors(),其他跟公平锁完全一致。解释:如果当前队列已经有其他线程阻塞了,就不让当前线程CAS。更通俗的说:能执行到(!hasQueuedPredecessors()说明当前没有任何一个线程获得锁,此时新来的线程想要CAS获得锁,那不明摆着插队么?因此加这个判断,就是会先让已经存在队列里的线程优先去CAS获得锁,这样才能体现公平性。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
非公平锁:不管是新来的线程还是队列里的线程,都有机会CAS获得锁。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
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;
}
补充非公平插队
假设线程A释放锁了,然后头结点的next节点线程B被唤醒了,他重新去CAS,结果被突然而来的线程D先CAS成功了。很苦逼,尽管线程B被唤醒,但是由于再次争抢锁失败,所以还是只能继续待着同步队列里。