1 同样的AQS
也是一个缩写,指的是Java中的一个类AbstractQueuedSynchronizer
,这是一个抽象父类,可以用于实现各种同步工具,例如ReentrantLock、Semaphore、CountDownLatch
2 AQS
统一规范了锁的实现,屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等
是一切锁和同步组件实现的----公共基础部分
3 AQS
使用一个volatile的int类型变量state
来表示同步状态,默认是0,代表资源没有被占用,是空闲状态
通过内置的FIFO队列来完成资源获取的排队工作,将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对锁的修改
通过以上,知道了AQS基本概念,那么现在看一下一个用AQS实现的锁ReentrantLock
,当它调用lock方法时
public void lock() {
sync.lock();
}
其实是使用了Sync
的lock方法,下面可以很清晰的看到:Sync 继承了AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
当构造方法中不传参数
,默认到以下,默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
当构造方法中传参数true
,默认到以下,默认是公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
//即传true 就 new FairSync() 是公平锁,否则 new NonfairSync() 非公平锁
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
可以看到,这里还是一个CAS操作compareAndSetState(0, 1)
,将状态0改为1,即代表抢到锁
并且通过 setExclusiveOwnerThread(Thread.currentThread())
方法,将持有锁的线程设置为当前线程
如果获取锁失败,则进入 acquire(1)
方法,接下来会介绍
和非公平锁竞争失败一样,这里之间调用了acquire(1)
方法
final void lock() {
acquire(1);
}
那现在我们看看这个acquire(1)到底做了什么事情
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
可以看到这里首先调用了tryAcquire(arg)
方法,这个方法对于公平锁
和非公平锁
的实现是不同的,但是两个方法间主要差别在
hasQueuedPredecessors()
方法,这个方法下面的公平锁使用到
,但是非公平锁就没有
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;
}
}
第三节中已经谈到非公平锁和公平锁的差别主要是 hasQueuedPredecessors()
方法,那么这个方法具体做了什么呢?
public final boolean hasQueuedPredecessors()
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());
}
这里主要是判断前面有没有排队的线程
如果返回true
说明队列中有等待的线程
在当前线程之前, 返回false
,说明当前线程是头节点,队列为空
在调用tryAcquire
方法时,如果返回false,即没有获得锁
,则进入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;
}
}
enq(node);
return node;
}
当第一个线程
进入方法时,此时tail为null,所以会进入enq(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;
}
}
}
}
1 因为此时tail==null
,就进入compareAndSetHead(new Node())
方法,主要这里新创建了一个Node
对象,即虚拟节点
2 compareAndSetHead
的作用就是将传入的虚拟节点设置为头节点
,又赋值给tail
3 由于for(::)
是个循环,此时第二次进来,此时的tail
已经在上一步被赋值
,则进入else
代码块
4 将t
作为当前节点的前置节点 , 将当前节点设置为尾节点
, 将当前节点作为t的后继节点
第五节中enq方法入队后返回一个node,此时就进入了 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;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以看到中途又尝试获取锁,后调用了parkAndCheckInterrupt
方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
可以看到,此时使用了中断机制LockSupport.park(this)
中断线程