变量
AQS属性
state : 不同的实现有着不同的含义,当使用的是ReentrantLock,0表示锁还未被获取,大于0表示被重入锁的次数;当使用Semaphore时,表示当前还有多少信号量可以被获取。
head/tail:队列头部/尾部节点,这是一个双向队列
Node节点属性
- prev:前驱节点
- next:后继节点
- thread:节点对应的线程
- nextWaiter:表示下一个等待condition的节点
- waitStatus:节点状态,也就是线程状态,有以下状态
cancelled = 1:节点因为超时或对应的线程被interrupt,唯一大于0的状态,会从队列被踢出
signal = -1:节点的继任节点为Blocked状态,一旦当前节点释放锁,则唤醒它的继任节点
condition = -2:节点对应的线程因为不满足Condition被阻塞
PROPAGATE =-3: 使用在共享模式头Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播
0:正常状态
基本原理
线程进入同步代码块之间,首先试图去获取锁,如果获取失败,则将该线程加入等待队列中自旋;当线程获取到锁或者该线程被中断则移出队列。队列的首节点为获取到锁的线程,但该节点并不持有线程的引用,只有未获取到锁的线程节点才持有线程的引用。
源码分析
在了解这个之前,我们先看看它的具体应用类ReentrantLock,ReentrantLock是如下这么使用的
ReentrantLock lock = new ReetranLock();
lock.lock();
try{
doSomething();
}finally{
lock.unlock();
}
这上面的代码能保证了doSomething()是 互斥访问的,我们来看下ReentrantLock是如何实现互斥的,下面代码中我们只抽取了与加锁,释放锁骨架部分。
锁分为共享锁和排它锁,从另一个维度上来讲还能分为公平锁和非公平锁,区别在于公平锁在获取锁之前需要先判断队列节点中当前线程是否的有前驱元素在等待获取锁,如果有则当前节点不能抢占先获取到锁,这里ReentrantLock是排它锁,我们先看是如何实现公平锁的
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
}
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//状态为0,说明此处锁还未被获取
if (c == 0) {
if (!hasQueuedPredecessors() && //如果没有其他线程在等待获取锁,则当前线程能获得锁,也就是按照顺序获取锁,为公平锁。如果没有该条件则表示是非公平锁
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);//设置当前线程获得锁
return true;
}
}
//说明该锁已经被获取了,但是要判断是否是同一线程获取锁,如果是state加1表示重入了一次
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 void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}
显然,加锁其实是 有FairSync的一个实例去实现。而FairSync继承自Sync,FairSync类里面lock()方法逻辑就写了一行代码acquire(1),这个方法来自Sync的父类AbstractQueuedSynchronizer ,子类只是简单的实现而已,acquire()方法更具体的逻辑是在AbstractQueuedSynchronizer实现的 。
来看下AbstractQueuedSynchronizer 的acquire()方法。这个方法的实现的是:线程先尝试获取锁,如果获取锁失败则将线程放入到等待锁的队列中,并调用selfInterrupt()中断当前线程。
public final void acquire(int arg) {//线程未获取到锁在此处阻塞
//A:如果当前线程未能获取到锁 && 线程进入自旋后遇到中断-->线程中断自身
//B:如果当前线程未能获取到锁 && 线程进入自旋后获得锁-->线程执行后续任务
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//Node.EXCLUSIVE表示互斥锁
selfInterrupt();//线程中断自身
}
tryAcquire(arg):这个方法是由子类去实现,上面的代码也贴出了相应的实现。state是一个状态位,如果state=0说明此处还可以获取锁,能获取到锁则返回真否则假。具体已在代码注释。
addWaiter源码如下。
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;
}
}//其实这个if代码块跟enq()方法里面是有重叠的,出于性能考虑独立出来(如减少方法栈的调用?)
enq(node);//尝试入列失败则自旋入列。
return node;
}
private Node enq(final Node node) {//其实这个操作就是上面操作的循环版本
for (;;) {
Node t = tail;
if (t == null) { // 为空时先初始化队列,然后自旋入列
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
再看外部方法acquireQueued(),注意上面有个返回值,返回值为上面刚添加入列的节点。
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;
//返回false,对应acquire()方法块的B情况,获得锁线程继续执行
return interrupted;
}
//如果前驱节点在等待,则当前节点挂起;前驱节点被取消,则将前驱节点移出等待队列
//挂起返回true;继续等待返回false(同时表明该节点的前驱节点为首节点)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;//挂起
}
} finally {
if (failed)
cancelAcquire(node);//获取锁失败
}
}
private void setHead(Node node) {
head = node;
//因为线程已获得锁,去除该节点的引用,也就是说首节点是获取到锁的节点,但是该节点不会持有线程的引用
node.thread = null;
node.prev = null;
}
//pred为刚入列线程的前驱节点,node为刚入列线程节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //如果刚入列线程的前驱节点在等待,则表明该当前线程需要park
return true;
if (ws > 0) {//前驱节点线程被取消,将其移除等待队列。
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);//调用LockSupport.park()中断线程。
return Thread.interrupted();
}
释放锁部分:
public void unlock() {
sync.release(1);
}
同理,还是到AQS中是看如何实现的
public final boolean release(int arg) {
if (tryRelease(arg)) {//这部分由实现类Syc去定义,state=0释放锁成功放回true,否则false
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) {//如果该锁只被获取一次,也就是没有被重入。则直接释放,并将获取的线程置为null
free = true;
setExclusiveOwnerThread(null);
}
setState(c);//设置锁的状态
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)//满足唤醒状态,则唤醒。
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {//后续没有线程或者线程已被取消
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);
}