ReentrantLock
ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。而且它具有比synchronized更多的特性,比如它支持手动加锁与解锁,支持加锁的公平性。
使用ReentrantLock进行同步
ReentrantLock lock = new ReentrantLock(false);
//false为非公平锁,true为公平锁
lock.lock() //加锁
lock.unlock() //解锁
在ReentrantLock内部定义了一个Sync的内部类,该类继承AbstractQueuedSynchronized,对该抽象类的部分方法做了实现;并且还定义了两个子类:
- 1、FairSync 公平锁的实现
- 2、NonfairSync 非公平锁的实现
- 这两个类都继承自Sync,也就是间接继承了AbstractQueuedSynchronized,所以这一个ReentrantLock同时具备公平与非公平特性。
- AQS具备特性
- 阻塞等待队列
- 共享/独占
- 公平/非公平
- 可重入
- 允许中断
除了Lock外,Java.util.concurrent当中同步器的实现如Latch,Barrier,BlockingQueue等, 都是基于AQS框架实现
-
一般通过定义内部类Sync继承AQS
-
将同步器所有调用都映射到Sync对应的方法
-
AQS内部维护属性volatile int state (32位)
-
state表示资源的可用状态
State三种访问方式
getState()、setState()、compareAndSetState()
AQS定义两种资源共享方式
- Exclusive-独占,只有一个线程能执行,如ReentrantLock
- Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
AQS定义两种队列
-
同步等待队列
-
条件等待队列 不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
-
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
-
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
-
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
-
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
-
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
- 同步等待队列
AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。
条件等待队列
Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤醒,从而重新争夺锁
lock 重点方法
tryAcquire(arg) 尝试获取锁 如果没有获取到锁 addWaiter() 添加到同步队列中 selfInterrupt() 中断当前线程 公平与非公平的 区别 在于 tryAcquire 方法
/** * 加锁行为 */
final void lock() {
/** * 第一步:直接尝试加锁 * 与公平锁实现的加锁行为一个最大的区别在于,此处不会去判断同步队列(CLH队列)中 * 是否有排队等待加锁的节点,上来直接加锁(判断state是否为0,CAS修改state为1) * 并将独占锁持有者 exclusiveOwnerThread 属性指向当前线程 * 如果当前有人占用锁,再尝试去加一次锁 */
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//AQS定义的方法,加锁
acquire(1);
}
/** * 加锁行为 */
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
添加同步队列
private Node addWaiter(Node mode) {
// 1. 将当前线程构建成Node类型
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 2. 1当前尾节点是否为null?
if (pred != null) {
// 2.2 将当前节点尾插入的方式
node.prev = pred;
// 2.3 CAS将节点插入同步队列的尾部
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//放入同步队列中
enq(node);
return node;
}
/**
* 节点加入CLH同步队列
*/
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;
//set尾部节点
if (compareAndSetTail(t, node)) {//当前节点置为尾部
t.next = node; //前驱节点的next指针指向当前节点
return t;
}
}
暂停当前线程
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取前一个节点
final Node p = node.predecessor();
//如果前一个节点是头 尝试再次获取锁 获取成功 当前节点
//为头节点 删除 p节点
if (p == head && tryAcquire(arg)) {
setHead(node);
//回收头节点 因为头节点执行完了
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断前一个节点的 waitStatus
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //中断当前线程 会被
//被下一个线程 unpark 该线程 继续执行
//被唤醒后继续进入for循环 此时 当前节点前节点是头
//进入上面的判断
interrupted = true;
}
} finally {
//当前节点现在是 head 了 该唤醒该线程了
if (failed)
//取消其他正在尝试获取锁的线程 让他们排队去
// 如果这是最后一个节点了,移除当前节点,把当前
//节点的前一个设置成尾 next设置为null,如果不是
//如果不是把后一个节点拼到前一个节点后
cancelAcquire(node);
}
}
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
while (pred.waitStatus > 0){node.prev = pred = pred.prev;}
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;//当前节点信号设置为 1 结束
//如果这是最后一个节点了,移除当前节点把当前节点的前一个设置成尾
if (node == tail && compareAndSetTail(node, pred)) {
//设置成尾之后 尾节点的后一个是null
compareAndSetNext(pred, predNext, null);
} else {
int ws;
//如果前一个不是头 把后面的都等待 重新排列
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//判断前一个是不是head如果是 激活当前线程,
unparkSuccessor(node);
}
node.next = node; // help GC 删除当前节点
}
}
}
1.判断前驱节点 waitStatus ==-1 前驱节点等待中 后面的都应该等待 waitStatus>0 前驱结束 后面的可用节点补上了 否则 因为是同步队列 并且是 ReentrantLock 所以 后面用不到
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
/**
* 标记当前节点的信号量状态
-3同步状态传播 -2条件队列中使用 -1 等待 0初始 1中断 结束
prev:前驱节点
head:头部节点
tail:尾部节点
state:枷锁次数
thread:当前持有线程
* 使用CAS更改状态,volatile保证线程可见性,
高并发场景下 即被一个线程修改后,状态会立马让其他线程可见.
*/
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;
}
}
unlock 重点方法
protected final boolean tryRelease(int releases) {
//修改可用资源
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果 1-1=0 因为每次只能有一个执行 返回 true
if (c == 0) {
free = true;
//取消线程独占
setExclusiveOwnerThread(null);
}
//设置可用资源
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
//node 是头部节点
int ws = node.waitStatus;
// 如果小于0 设置成0 正在运行
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)
//释放下个节点 释放后 acquireQueued()方法继续执行
LockSupport.unpark(s.thread);
}