AQS框架:全称AbstractQueuedSynchronizer。(抽象队列同步)
AQS是一个用来构建锁和同步器的(各种基类)框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
总结:手撕核心类AbstractQueuedSynchronizer,你会对AQS认识更加深刻,然后Count
类图分析(下面会详细说明)
基本锁的特性:
加锁、解锁
以ReentrantLock为例:
可重入锁,公平锁,非公平锁
可重入锁,公平锁,非公平锁概念自行知乎
上述概念结合源码进行分析(以ReentrantLock为例)
private final Sync sync;
// ReentrantLock的加锁方法
public void lock() {
sync.lock();
}
//ReentrantLock 释放锁
public void unlock() {
sync.release(1);
}
// 以下代码是ReentrantLock实现公平锁和非公平锁的构造方法
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
* 默认非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
* 公平锁
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
实现公平,非公平的详细过程后续会详细讲到
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// 唤醒queue中的线程;addWaiter向队列中添加线程
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
...
// 继承类重写该方法,自定义尝试获取锁操作
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
...
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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);
}
}
...
/**
* Creates and enqueues node for current thread and given mode.
* 线程在Node的排队方式
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;
}
acquire获取锁方法:
tryAcquire(arg)先尝试获取锁,获取失败则acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 进行排队; addWaiter(Node.EXCLUSIVE)将当前线程放进Node(双向链表)的表尾。
类图如下:
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
// Node类静态常量---线程状态
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
// volatile修饰保证其在多线程情况下的可见性
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
概念介绍:
上文提到的acquireQueued方法就是把放入队列中的这个线程不断进行“获锁”,直到它“成功获锁”或者“不再需要锁(如被中断)“。
注:盗张网上的图
上图解释说明:(1)获取当前Node的前驱Node -->(2)前驱Node是否是head节点,并且是否获锁成功--->(3)是则删除head节点,将当前节点设置为head节点,不是则判断是否要阻塞当前node,如果不阻塞继续执行(1)操作。
“不管公平还是非公平模式下,ReentrantLock对于排队中的线程都能保证,排在前面的一定比排在后面的线程优先获得锁”但是,非公平模式不保证“队列中的第一个线程一定就比新来的(未加入到队列)的线程优先获锁”因为队列中的第一个线程尝试获得锁时,可能刚好来了一个线程也要获取锁,而这个刚来的线程都还未加入到等待队列,此时两个线程同时随机竞争,很有可能,队列中的第一个线程竞争失败(而该线程等待的时间其实比这个刚来的线程等待时间要久)。
细看上图会发现:“如果就是那么不恰巧,就是不符合这个唯一跳出循环的条件(p是head节点,并且当前线程获锁成功)”,那就一直在循环里面空跑了吗!那CPU使用率不就会飙升?!
答:将线程阻塞,就不会出现死循环了。
关键在于CAS会设置pred节点的状态为SIGNAL;则第二次循环时线程就会被ws == SIGNAL判断成功而阻塞掉。
// ReentrantLock类释放锁
public void unlock() {
sync.release(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;
}
/**
* Wakes up node's successor, if one exists.
* 如果队列中存在线程则唤醒线程
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
// CAS机制
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//如果head节点的下一个节点它是null或者已经被cancelled了(status>0)
//那么就从队列的尾巴往前找,找到一个最前面的并且状态不是cancelled的线程
//至于为什么要从后往前找,不是从前往后找,谁能跟我说一下,这点我也不知道为什么
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;
}
// 如果node存在则唤醒线程
if (s != null)
LockSupport.unpark(s.thread);
}
/**
* CAS waitStatus field of a node.
* 多线程下保证waitStatus的原子性
*/
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
}
// 唤醒线程 unsafe类
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
自定义同步器时需要重写下面几个AQS提供的模板方法:
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
今天被docker部署分布式项目弄得心态爆炸,老婆镇楼,开心下,明天继续搬砖