java锁 aqs_Java锁机制浅析之 AQS

一、内部原理

类继承结构

Lock package相关API继承结构,忽略掉了一些类,以便观察其特点:

ReentrantLock和 ReentrantReadWriteLock都是借助内部类Sync来实现Lock接口。ReentrantReadWriteLock没有直接实现Lock接口而是内置了读锁-ReadLock和写锁-WriteLock分别实现Lock接口。

Sync包含两个子类:FairSync(公平锁),NonfairSync(非公平锁);在构建ReentrantLock和 ReentrantReadWriteLock对象时,可通过带有参数的构造函数指定是否采用公平锁;不指定时默认是非公平锁。Sync类是从·AbstractQueuedSynchronizer继承而来。

抽象队列同步器AQS

AbstractQueuedSynchronizer简称AQS,Doug Lea大师创作,用来构建锁或者其他同步组件的基础框架类,除了ReentrantLock和ReentrantReadWriteLock还有很多其他的并发工具类的实现都依赖于AQS;CountDownLatch、CyclicBarrier、Semaphore等都是基于AQS实现。(本章主要分析AQS的源码,下一章可以分析下这些类的用法)。

AQS为同步状态的原子性管理、线程的阻塞和解除阻塞以及排队提供了一种通用机制。基于模板方法模式设计,在使用时需要通过组合一个AQS的子类并重写其同步资源获取和释放逻辑方法tryAcquire()和tryRelease()。

AQS的使用思路很简单,在有线程获取锁时,调用子类tryAcquire()尝试获取锁,在获取失败时,将线程加入到一个基于CLH锁结构实现的等待队列中,当持有锁的线程执行完操作释放锁时,调用子类的tryRelease()释放锁资源,并唤醒等待队列中的下一个线程开始获取锁。

主要API

同步状态管理

int getState(): 获取同步状态

void setState(): 设置同步状态

boolean compareAndSetState(int expect, int update):基于CAS,原子设置当前状态

两组用于实现独占模式和共享模式的抽象方法。

独占模式锁操作

acquire(int) :尝试获取争用资源,如果未获取到,当前线程将进入等待队列。

tryAcquire():争用资源逻辑的具体实现,必须由具体的AQS使用者去实现。

acquireInterruptibly():支持中断的获取acquire操作

release():当前线程释放争用资源,并唤醒等待队列中的其他线程进入争抢。

tryRelease():释放争用资源的具体实现,必须由具体的AQS使用者去实现

共享模式锁操作

acquireShared():acquire的共享模式版本。

tryAcquireShared():tryAcquire的共享模式版本。

acquireSharedInterruptibly():acquireInterruptibly的共享模式版本

releaseShared():release的共享模式版本

tryReleaseShared():tryRelease的共享模式版本

源码

内部等待队列Node

static final class Node {

static final Node SHARED = new Node(); //共享模式

static final Node EXCLUSIVE = null; //独占模式

Node nextWaiter; //独占或共享模式标记

//等待状态常量定义

static final int CANCELLED = 1; //取消

static final int SIGNAL = -1; //通知

static final int CONDITION = -2; //条件等待

static final int PROPAGATE = -3; //传播

volatile int waitStatus; //等待状态属性

volatile Node prev; //前驱节点

volatile Node next; //后继节点

volatile Thread thread; //节点所封装的线程

//判断节点是否处于共享模式

final boolean isShared() {

return nextWaiter == SHARED;

}

//获取前驱节点,为空时抛出异常

final Node predecessor() throws NullPointerException {

Node p = prev;

if (p == null) {

throw new NullPointerException();

} else {

return p;

}

}

//以独占或共享模式构造一个线程节点,由AQS的addWaiter调用

Node(Thread thread, Node mode) {

this.nextWaiter = mode;

this.thread = thread;

}

//Condition模式下使用此构造函数

Node(Thread thread, int waitStatus) {

this.waitStatus = waitStatus;

this.thread = thread;

}

}

独占锁的获取acquire()

/**

* !tryAcquire(arg):

* 首先调用其子类(实际的AQS使用者)尝试获取锁,获取成功函数就直接返回了。

* acquireQueued(addWaiter(Node.EXCLUSIVE), arg)): 子类未获取到锁时:

* 通过addWaiter() 以独占模式将当前线程封装成为Node节点并执行必要的初始化。

* 通过acquireQueued()根据节点在队列中的位置,执行获取锁或者是阻塞线程操作

*/

public final void acquire(int arg) {

if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

/**

* 1. 将当前线程封装为node节点。

* 2. 如队列未初始化,则初始化队列,并把当前线程节点加入队列末尾。

* 3. 如果队列已经初始化(已经存在等待锁的其他线程节点),则将当前线程节点直接加入到队里末尾

* 4. 返回代表当前线程的Node节点。

*/

private Node addWaiter(Node mode) {

Node node = new Node(Thread.currentThread(), mode); // 将当前线程封装成为Node节点。

Node pred = tail; // pred指向队列末尾

if (pred != null) { // 如果队列末尾元素不为空

node.prev = pred;

// CAS更新队列末尾指向当前线程节点。更新成功后,将旧队列末尾的后驱指向当前线程节点,返回当前线程节点。

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

enq(node); //如果队列为空,则初始化队列,然后将当前线程节点加入队列

return node;

}

/**

* 如果队列未初始化,则使用一个无指向的Node作为队列头,将当前线程节点链接到head作为队列尾。

* 此处使用轻量级的自循环+Cas更新的方式,来处理enq函数被多线程并发执行时的问题,

* 想一想如果没有循环那么enq函数被多线程并发时可能带来什么问题?

*/

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;

}

}

}

}

/**

* 如果节点前驱是队列头,则尝试获取锁。

* 此时:node就是队列首个有效节点,head在初始化时是一个无指向的Node,

* 而node在获取锁后通过setHead(node)被设置成为head,但同时置空了node指向的Thread和前驱节点,head仍然是个无指向的节点)

* 如果节点前驱不是队列头,则将前驱状态更改为SIGNAL后通过LockSupper.park()阻塞当前线程。

* 此时:在node之前至少有两个有效节点(node.prev和node.prev.prev)node.prev.prev此时可能已经获取锁,也可能在等待。但是node.prev

* 一定在等待,那么当前节点应该进入阻塞等待,而前驱节点已经处于等待状态可以被设置设置成待通知状态。

*/

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;

}

// shouldParkAfterFailedAcquire(): 保证当前节点前驱节点状态为Node.SIGNAL后

// parkAndCheckInterrupt(): 通过LockSupport.park() 阻塞当前线程。

if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())

interrupted = true;

}

} finally {

if (failed)

//获取成功后,

cancelAcquire(node);

}

}

/**

* 如果当前节点前驱节点状态为Node.SIGNAL,则返回true,否则,将前驱节点状态设置为Node.SIGNAL后

* 如果遇到前驱节点状态为取消的情况,则从前驱节点向前遍历,找到最后一个未被取消的节点,将当前节点连接在该节点后面。

*/郑州哪家医院人流好 http://www.120csfkyy.com/

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

int ws = pred.waitStatus;

// 已经设置了状态,可以安全

if (ws == Node.SIGNAL) return true;

if (ws > 0) {

//从当前线程节点向前遍历,把当前线程节点放到队列最靠后的一个有效等待的节点。

do {

node.prev = pred = pred.prev;

} while (pred.waitStatus > 0);

pred.next = node;

} else {

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

}

return false;

}

独占锁的释放release()

/**

* 首先调tryRelease()对子类资源进行释放。

* 如果 head!=null 或者 head的等待状态不为零,说明仍然有后续节点在等待。此时需要唤醒后续节点。

*/

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

return true;

}

return false;

}

/**

* 将当前节点状态重置为零,并从队列尾部向前找到第一个有效节点,通过unpark通知该节点线程。,

* 为何不是从当前节点向后查找?

*/

private void unparkSuccessor(Node node) {

int ws = node.waitStatus;

if (ws < 0)

compareAndSetWaitStatus(node, ws, 0);

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;

}

if (s != null)

LockSupport.unpark(s.thread);

}

共享锁的获取 acquireShare

有了独占模式的分析后,共享模式就简单多了。

/**

* 调用子类tryAcquireShare尝试获取锁,tryAcquireShare()函数返回值小于零时获取失败。

* 获取失败时,调用doAcquireShared方法处理

*/

public final void acquireShared(int arg) {

if (tryAcquireShared(arg) < 0)

doAcquireShared(arg);

}

/**

* 与独占模式流程相似,但是封装节点时采用的是SHARED模式。

*/

private void doAcquireShared(int arg) {

final Node node = addWaiter(Node.SHARED);

boolean failed = true;

try {

boolean interrupted = false;

for (;;) {

final Node p = node.predecessor();

if (p == head) {

int r = tryAcquireShared(arg);

if (r >= 0) {

setHeadAndPropagate(node, r);

p.next = null; // help GC

if (interrupted)

selfInterrupt();

failed = false;

return;

}

}

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} finally {

if (failed)

cancelAcquire(node);

}

}

/**

* 调用子类释放锁,然后将当前节点从队列

*/

public final boolean releaseShared(int arg) {

if (tryReleaseShared(arg)) {

doReleaseShared();

return true;

}

return false;

}

共享锁的释放doReleaseShared

/**

* 实际执行释放锁的操作,

* 由于是共享模式,在释放锁时也存在多个线程并发释放的情况,因此采用循环+CAS的机制

* 来使并发释放的多个线程顺序unpark队列中的后续节点。

*/

private void doReleaseShared() {

for (;;) {

Node h = head;

if (h != null && h != tail) {

int ws = h.waitStatus;

if (ws == Node.SIGNAL) {

if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))

continue; // loop to recheck cases

unparkSuccessor(h);

}

else if (ws == 0 &&

!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))

continue; // loop on failed CAS

}

if (h == head) // loop if head changed

break;

}

}

可中断模式 acquireSharedInterruptibly

中断模式也获取的也是独占锁,只是会在尝试获取锁前先检查线程的中断状态,如果线程被中断,那么会抛出InterruptedException而不会继续尝试获取锁。中断模式获取的锁也通过release()进行释放。

/**

* 在获取锁时会首先判断线程是否被中断,如果中断则直接抛出异常。

* 未被中断,则调用子类tryAcquire()尝试获取锁。获取失败时,通过doAcquireInterruptibly()进行处理。

*/

public final void acquireInterruptibly(int arg) throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

if (!tryAcquire(arg))

doAcquireInterruptibly(arg);

}

/**

* 与doAcquire()同样的流程,区别只在于,线程进入等待队列,而后被其他节点唤醒后,会根据线程中断状态决定后续操作:

* 如果线程被中断,则直接抛出InterruptedException而不会继续获取锁。

*/

private void doAcquireInterruptibly(int arg) throws InterruptedException {

final Node node = addWaiter(Node.EXCLUSIVE);

boolean failed = true;

try {

for (;;) {

final Node p = node.predecessor();

if (p == head && tryAcquire(arg)) {

setHead(node);

p.next = null; // help GC

failed = false;

return;

}

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

throw new InterruptedException();

}

} finally {

if (failed)

cancelAcquire(node);

}

}

到此AQS的源码基本就完了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值