volatile,CAS,队列问题。
一、AQS
AQS本质就是Java中的AbstractQueuedSynchronizer
在AbstractQueuedSynchronizer类中,有几个属性和一个双向队列(CLH队列)
AQS就是并发包下的一个基类
CountDownLatch,ReentrantLock,信号量……
static final class Node {
// 排他锁的标识
static final Node EXCLUSIVE = null;
// 如果带有这个标识,证明是失效了~~
static final int CANCELLED = 1;
// 具有这个标识,说明后继节点需要被唤醒
static final int SIGNAL = -1;
// Node对象存储标识的地方
volatile int waitStatus;
// 指向上一个节点
volatile Node prev;
// 指向下一个节点
volatile Node next;
// 当前Node绑定的线程
volatile Thread thread;
// 返回前驱节点,如果前驱节点为null,抛出NPE
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
}
ReentrantLock可以实现公平锁也可以实现非公平锁,也是互斥锁以及是可重入锁
二、加锁
一、从lock进入
public void lock() {
// sync分为了公平和非公平
sync.lock();
}
二、从非公平锁进去
final void lock() {
// 通过CAS的方式尝试将state从0修改为1,如果返回true,代表修改成功,如果修改失败,返回false
if (compareAndSetState(0, 1)){
// 将一个属性设置为当前线程,这个属性是AQS的父类提供的
setExclusiveOwnerThread(Thread.currentThread());
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} else {
acquire(1);
}
}
三、查看acquire方法
public final void acquire(int arg) {
// tryAcquire再次尝试获取锁资源,如果尝试成功,返回true
if (!tryAcquire(arg) &&
// 获取锁资源失败后,需要将当前线程封装成一个Node,追加到AQS的队列中
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 线程中断
selfInterrupt();
}
四、查看tryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取AQS的state的值
int c = getState();
// 如果state为0,代表,尝试再次获取锁资源
if (c == 0) {
// CAS尝试修改state,从0-1,如果成功,设置ExclusiveOwnerThread属性为当前线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 当前占有锁资源的线程是否是当前线程
else if (current == getExclusiveOwnerThread()) {
// 将state + 1
int nextc = c + acquires;
// 如果加1后,小于0,超所锁可重入的最大值,抛出Error
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 没问题,就重新对state进行复制
setState(nextc);
// 锁重入成功
return true;
}
return false;
}
五、查看addWaiter(Node.EXCLUSIVE)
// 说明前面获取锁资源失败,放到队列中等待。。。
private Node addWaiter(Node mode) {
// 创建Node类,并且设置thread为当前线程,设置为排它锁
Node node = new Node(Thread.currentThread(), mode);
// 获取AQS中队列的尾部节点
Node pred = tail;
// 如果tail != null,现在队列有人排队
if (pred != null) {
// 将当前节点的prev,设置为刚才的尾部节点
node.prev = pred;
// 基于CAS的方式,将tail节点设置为当前节点
if (compareAndSetTail(pred, node)) {
// 将之前的为节点的next,设置为当前节点
pred.next = node;
return node;
}
}
// 查看下面~
enq(node);
return node;
}
//-------------------------------
// 现在没人排队,我是第一个~~, 如果前面CAS失败,也会进到这个位置重新往队列尾巴塞。
private Node enq(final Node node) {
// 死循环~~
for (;;) {
// 重新获取当前的tail节点为t
Node t = tail;
if (t == null) {
// 现在没人排队, 我是第一个,没头没尾,都是空
if (compareAndSetHead(new Node())) // 初始化一个Node作为head,而这个head没有意义。
// 将头尾都指向了这个初始化的Node
tail = head;
} else {
// 有人排队,往队列尾巴塞
// 当前节点的上一个指向tail。
node.prev = t;
// 基于CAS的方式,将tail节点设置为当前节点
if (compareAndSetTail(t, node)) {
// 将之前的为节点的next,设置为当前节点
t.next = node;
return t;
}
}
}
}
六、查看acquireQueued方法
// 已经将node加入到了双向队列中,然后执行当前方法
final boolean acquireQueued(final Node node, int arg) {
// 标识!!!!
boolean failed = true;
try {
// 标识!!!!
boolean interrupted = false;
for (;;) {
// 获取当前节点的上一个节点p
final Node p = node.predecessor();
// 如果p是头,再次尝试获取锁资源(state从0-1,锁重入操作),成功返回true,失败返回false
if (p == head && tryAcquire(arg)) {
// 拿到锁资源设置head节点为当前节点,将thread,prev设置为null,因为拿到锁资源了,排队跟我没关系
setHead(node);
p.next = null; // 帮助GC回收
failed = false; // 将标识修改为false
return interrupted; // 返回interrupted
}
// 保证上一个节点是-1,才会返回true,才会将线程阻塞,等待唤醒获取锁资源
if (shouldParkAfterFailedAcquire(p, node) &&
// 基于Unsafe类的park方法,挂起线程~~~
parkAndCheckInterrupt()) // 针对fail属性,这里是唯一可能出现异常的地方,JVM内部出现问题时,可以这么理解,fianlly代码块中的内容,执行的几率约等于0~
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// -------------------------------------
// node是当前节点,pred是上一个节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取上一个节点的状态
int ws = pred.waitStatus;
// 如果上一个节点状态为SIGNAL,一切正常!
if (ws == Node.SIGNAL)
return true;
// 如果上一个节点已经失效了
if (ws > 0) {
do {
// 将当前节点的prev指针指向了上一个的上一个!
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0); // 一致找到小于等于0的
// 将重新标识好的最近的有效节点的next
pred.next = node;
} else {
// 小于等于0,不等于-1,将上一个有效节点状态修改为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// ----------------------------
// cancelAcquire方法
private void cancelAcquire(Node node) {
// 如果当前节点为null,结束,健壮性判断
if (node == null)
return;
// node不为null的前提下执行
// 将当前node的线程置位null , 竞争锁资源跟我没有关系了,
node.thread = null;
// 获取当前节点的前驱节点
Node pred = node.prev;
// 前驱节点的状态 > 0
while (pred.waitStatus > 0)
// 找到前驱中最近的非失效节点
node.prev = pred = pred.prev;
// 将第一个不是失效节点的后继节点声明出来
Node predNext = pred.next;
// 将当前节点置位失效节点。给别的Node看的。
node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,将尾节点设置为最近的有效节点(如果当前节点为尾节点的操作)
if (node == tail && compareAndSetTail(node, pred)) {
// 用CAS方式将尾节点的next设置null
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 中间节点操作
// 如果上一个节点不是头节点
if (pred != head &&
获取上一届点状态,是不是有效
((ws = pred.waitStatus) == Node.SIGNAL || // pred需要唤醒后继节点的
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next); // 尝试将pred的前驱节点的next指向当前节点的next(必须是有效的next节点)
} else {
// 头结点,唤醒后继节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}