AQS 与ReentrantLock ,ReentrantReadWriteLock 解析
ReentrantLock 与 AQS 框架源码解析
预备知识aqs 大致内部结构如下图所示
CAS 机制
内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时(条件),将内存值修改为B并返回true,否则条件不符合返回false。条件不符合说明该变量已经被其它线程更新了。
名词解释:
node head/tail: 维护的是线程的一个FIFO队列;
state: volatile修饰的一个常量(内存可见性,防止指令重排序);
exclusiveOwnerThread: 当前线程持有者如t1线程;
重要方法:
tryAcquire :尝试加锁抽象方法需要自己实现重点看下面 reentrantlock解析;
acquire:当尝试加锁失败,会进入FIFO队列排队;
tryRelease: 抽象方法子类实现;
release: 解锁操作;
ReentrantLock 解读
ReentrantLock 分为公平与非公平锁 默认非公平锁
创建公平锁
ReentrantLock lock =new ReentrantLock(true);
//公平锁
ReentrantLock lock =new ReentrantLock();
//非公平锁
加锁过程:
/**
* 加锁
*/
final void lock() {
acquire(1);
}
/**
* 调用此方法加锁
*/
public final void acquire(int arg) {
// 尝试加锁 加锁失败进入队列排队
if (!tryAcquire(arg)&&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
selfInterrupt();
}
}
/**
* 公平锁 分析tryAcquire 方法
* 非公平与公平锁的区别是缺少hasQueuedPredecessors()此方法
* 非公平锁直接设置值抢锁
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();//获取当前线程
int c = getState();//获取AQS state 值
if (c == 0) {//当线程第一次进来 c==0
/**
* 1、首先 判断当前线程前面还有没有线程在排队有排队返回true 否则false
* 2、第一条件false ,没有则设置成功拿到锁 并把线程持有者为当前线程
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前线程是否等于自己 为自己 表示锁的可重入 state + 1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
/**
* 1、第一种可能如果线程第一次进来 前面没有元素 那么返回false
* 2、如果 h != t 为true 则表示队列里面>=2个元素
* 3、看自己是否为第二个节点,若是第二个或者是下一个(不是当前线程)则排队不拿锁
*/
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾部节点
Node h = head; //头部节点
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
/**
* mode 传过来的值 ==null
* 只有拿锁失败 才会加入队列等待唤醒
* 这里可以看成t1 抢锁成功 t2 失败
*/
private Node addWaiter(Node mode) {
//获取当前线程内存创建一个node
Node node = new Node(Thread.currentThread(), mode);
// 第一个线程进来tail 肯定为null 走enq方法
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
/**
* 循环比较
* 形成队列
* node 代表t2 进来
*/
private Node enq(final Node node) {
for (;;) {
//第一进来 tail ==null
Node t = tail;
if (t == null) {
//当 tail ==null 的时候new node 并赋值给t
if (compareAndSetHead(new Node()))
tail = head; //t.tail =head
} else {
// t2.prev 指向前一个t
node.prev = t;
// 并把当前的tail 指向node t2
if (compareAndSetTail(t, node)) {
t.next = node; // t.next 指向t2 .node
return t;
}
}
}
}
/**
* 判断线程是否需要睡眠
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//自旋
for (;;) {
// 这儿可以理解为t2 前一个节点t
final Node p = node.predecessor();
//t是head尝试加锁失败走下面shouldParkAfterFailedAcquire方法
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//第一次 false 第二次遍历时候true 则走&&后面方法park 当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 抢锁失败 后则需要线程park
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//第一次ws ==0
if (ws == Node.SIGNAL) //-1
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 0的时候 设置前一个节点t.waitStatus ==-1 下次循环遍历返回true
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
最终enq 执行后的示意图如下
t.next 指向t2 t2.prev指向t 构成双向队列
补充 常见几种锁的区别
- 公平锁 : 需要排队依次进行
- 非公平锁:竞争抢锁
- 重量级锁:如synchronized
- 轻量级锁:如reentrantlock
- 自旋锁:如reentrantlock /reentrantreadwritelock
- 可重入锁:reentrantlock、synchronized锁的可重入解释是当一个方法用synchronized修饰了method1 里面调用了synchronized 修饰的method2 那么方法二是可以执行的。
- 读写锁:reentrantreadwritelock 读读并行 ,读写互斥 ,写写互斥,读锁不能升级为写锁,
下一篇
讲解 reentrantlock 锁的唤醒