AQS
属性:用state来表示资源的状态(加锁 state 1)
独占模式
一个线程访问资源
共享模式
多个线程访问资源
- 提供了FIFO的等待队列,未拿到资源的被分配到队列等待
- 条件变量来实现等待,唤醒机制
AQS 内部维护了一个双向链表,头节点是个占位的(waitStatus为-1),用来释放下一个节点(线程)
加锁
代码语言:javascript复制lock
public final void acquire(int arg) {
//如果加锁失败 将该线程加入到队列里面
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
代码语言:javascript复制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)) {
//如果加锁成功 设置当前节点为头节点 头节点的下一个节点为null
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果加锁失败
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//当前获得锁的线程是否中断状态 如果加锁之后线程中断了处理
//和lockInterruptibly() 在这块会抛异常
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
代码语言:javascript复制//加锁失败之后
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//前驱结点是做标记节点 也就是-1 可唤醒线程
return true;
if (ws > 0) {
//如果前驱结点status>0 说明当前节点不可被唤醒,需要被标记线程唤醒
//一直遍历到头节点为占有锁的线程则退出
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//当前标记节点指向占有锁的节点
pred.next = node;
} else {
//将头节点设置为标记状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
代码语言:javascript复制private final boolean parkAndCheckInterrupt() {
//加锁,然后判断当前线程是否中断 区分lock()和lockInterruptibly()方法 是否需要抛异常
//获取当前锁,进入阻塞状态
LockSupport.park(this);
return Thread.interrupted();
}
代码语言:javascript复制AQS#tryAcquire
final boolean nonfairTryAcquire(int acquires) {
//获取当前加锁的状态 第一次上锁 直接加锁 并返回true
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果是重入的 加acquires 然后返回 发欧泽加锁失败
else if (current == getExclusiveOwnerThread()) {
int nextc = c acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
解锁
代码语言:javascript复制//AQS的解锁逻辑
public final boolean release(int arg) {
//调用解锁 如果解锁失败 则返回false
if (tryRelease(arg)) {
//解锁成功,如果当前的头节点不为空 并且等待状态不为0则将头节点的状态改为标记节点
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
代码语言:javascript复制 protected final boolean tryRelease(int releases) {
//获取当前重入锁的次数 如果非当前线程(比如线程中断等原因),抛异常
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//如果等于0 则释放锁了 解锁成功 设置当前自有锁的线程为null
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//也有可能是重入了多次,会解锁失败
setState(c);
return free;
}
然后再来看看unparkSuccessor()
代码语言:javascript复制 //获取当前节点的状态 如果小于0(可能是标记节点)
int ws = node.waitStatus;
if (ws < 0)
//设置为0
compareAndSetWaitStatus(node, ws, 0);
//获取它的后继节点,如果下一个节点为null
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//从最后一个节点开始遍历,找到先驱节点的等待状态<=0的 然后去释放锁
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//释放锁
if (s != null)
LockSupport.unpark(s.thread);