AQS的概览
- Lock的AQS流程
- AQS主要流程有哪些
1.自旋锁
2.队列
3.CAS - CAS原理
当一个线程要更新一个成员变量的值时,首先,该线程会从内存中复制一个变量副本到线程的栈帧中(X=0),此时要将变量X进行自增操作(X=X+1),此时在线程栈帧中实际上会产生一个临时变量update=X+1。在刷新回内存时,会首先拿着变量X和主内存中的变量X进行比较,如果线程栈中的变量值等于主内存个中的变量值,那么就把自增之后的变量update刷新到主内存中去。当第二个线程再刷新时,会出现线程栈中的变量值和主内存中的变量值不一致的情况(此时主内存中的变量已经成为上一次修改后的变量1,对比失败),如果对比失败,则直接返回,重新获取数据进行操作。 - 自旋锁
当线程没有获取到锁的时候,会进行自旋等待,一直到获取到锁之后跳出 - 队列
没有获取到锁的线程会在队列中等待被唤醒去争夺锁的控制权 - LockSupport
如果没有获取到锁的线程始终在自旋获取锁,那么就会一致占用CPU的资源,极大的浪费资源,所以需要让线程释放锁的资源。LockSupport.lock(),和LockSupport.unlock()两个方法可以很好的解决这个问题,在获取锁失败的时候执行lock()方法,再获取到锁的线程释放锁的时候执行unlock方法唤醒后面的锁。
- AQS主要流程有哪些
AQS的重要方法
AQS的重要参数
waitStatus参数:
SIGNAL:等待唤醒
CANCELLED:当前节点已经废弃
CONDITION:条件队列
PROPAGATE:广播
- ReentrantLock类并没有继承AQS类,他是利用一个Sync内部类继承的AQS类。然后再利用FiarSync和UnfairSync来实现这个Sync内部类,定义具体的方法。
- Lock.lock() 方法详解
Lock.lock()方法里面调用了acquire(int i)方法,然后acquire方法里面会调用tryAcquire方法(尝试获取锁)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 获取当前执行的线程
int c = getState(); // 获取当前要获取锁的节点的状态,入锁几次
if (c == 0) { // 判断当前要获取锁的节点是否是刚刚创建出来的
if (!hasQueuedPredecessors() && // 判断当前节点是否是队列中的唯一节点,即队列初始化的节点
compareAndSetState(0, acquires)) { // 如果上面的不成立,即队列中存在多个节点,则CAS执行状态变更,将状态变更为1
// 这个地方就是判断等待队列中是否存在线程节点,如果等待队列中不存在线程节点,则说明当前等待队列是个空的,直接设置状态为废弃就可以了
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 如果当前进来的节点是当前获取锁的节点
int nextc = c + acquires; // 直接将锁的次数+1,此处即重入的逻辑
if (nextc < 0)// 如果此时nextc还小于0,则说明status小于-1,但是锁状态最小为0
throw new Error("Maximum lock count exceeded");
setState(nextc);// 设置锁的状态,入锁几次
return true;
}
return 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) { // 判断尾巴节点是否为null,如果不是则说明队列中有节点元素
node.prev = pred; // 将当前线程节点的前指针指向原本的尾巴节点
if (compareAndSetTail(pred, node)) { // CAS的方式设置当前线程节点到尾巴节点,因为可能会存在多个线程争夺设置尾巴的资格,所以如果不用CAS保证原子性,则会出现刚设置一个尾巴,然后下一个线程就将尾巴替换了的情况,所以要用CAS保证原子性
pred.next = node; // 设置原本的尾巴,即刚才设置的上一个节点的下一个节点为当前节点,形成双向链表
return node; // 将当前线程的node返回
}
}
// 如果尾巴不是null,则说明原本的等待队列中是存在值的
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)) { // 再次CAS将当前线程节点设置为尾巴节点
t.next = node; // 设置原本的尾巴节点的下一个节点为当前节点,形成双向链表
return t;
}
}
}
}
加入队列后,会再执行acquireQueued方法。这个方法不会有多个线程同时进来,因为前面是CAS的方式把节点加入到尾部
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)) { // 判断前一个节点是否为头节点,因为是独占模式,所以只有头节点后面的一个节点可以获取锁,如果前一个节点是头节点,那么会再次对当前节点获取锁,如果执行下面的节点入队后,会再执行一次这个判断,即如果入队的节点是头结点后面的第一个节点,那么会再次获取一次锁,因为线程放弃CPU后会从激发态转为用户态,会比较耗资源
setHead(node); // 如果前一个节点是头节点,那么说明当前节点可以唤醒,将头节点设置为当前节点,将该节点中的线程和前指针都制空,因为此时线程已经获取到锁了,不在需要记录在节点中
p.next = null; // help GC // 断开老的头结点的next指针,彻底断开老的头节点
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 修改当前节点的前一个节点的状态,并且将废弃的节点删除掉,如果原本的状态不是可唤醒,返回的是个false,如果原本的状态已经是可唤醒了,则返回true
parkAndCheckInterrupt()) // 执行LockSupport.park()并且消除中断信号
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) { // 如果状态大于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); // CAS的方式设置节点状态为可唤醒状态
}
return false; // 返回false
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 上面的acquireQueued里面如果是线程有中断信号的,会将线程的中断信号清除,在这里再补回来
}