关联文章:
关联文章:
Java并发源码分析之AQS及ReentrantLock
Java并发源码分析之ReentrantReadWriteLock
Java并发源码分析之Condition
Java并发源码分析之CyclicBarrier
Java并发源码分析之CountDownLatch
工作原理概要
AQS是用来构建锁的基础框架,ReentrantLock是基于AQS构建的排它锁,而Semaphore则是基于AQS构建的共享锁。两者对比如下:
对比项 | ReentrantLock | Semaphore |
---|---|---|
锁的类型 | 排它锁 | 共享锁 |
state | 锁的可重入次数 | 锁的剩余个数 |
是否支持可重入 | 支持 | 支持 |
是否支持非公平锁 | 支持 | 支持 |
是否支持公平锁 | 支持 | 支持 |
类图
AbstractOwnableSynchronizer:抽象类,定义了独占锁线程,exclusiveOwnerThread
AbstractQueuedSynchronizer:抽象类,继承了AbstractOwnableSynchronizer,里面包含内部类Node
Semaphore:类,里面包含三个内部类Sync、NonfairSync、FairSync
Sync:类,继承了AbstractQueuedSynchronizer,重写了tryReleaseShared方法
NonfairSync:类,继承了Sync,重写了tryAcquireShared
FairSync:类,继承了Sync,重写了tryAcquireShared
原理概述
Semaphore维护了一个同时许可集(官方解释),其实就是表示同一时间可以访问共享资源的线程数量,底层通过state来维护。每当有一个线程申请锁,就将state-1,直至state为0,则新申请锁的线程就放入同步队列中等待。有线程释放锁之后,由头节点来唤醒队列中的线程。
Semaphore同样可以实现排它锁功能,将许可集的值设置为1,则同一时刻只有一个线程获取到锁,也就不实现了排它锁。
主要入口方法
// 获取共享锁,并响应中断
acquire()
// 获取共享锁,不响应中断
acquireUninterruptibly()
// 尝试获取共享锁,并响应中断
tryAcquire()
// 带有超时时间,尝试获取共享锁,并响应中断
tryAcquire(long timeout, TimeUnit unit)
// 是否共享锁
release()
由于代码都大致相同,这里只解析常用的acquire和release。
Semaphore获取锁流程
构造函数
// permits,许可集,即共享锁最大个数
// 默认为非公平锁
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// fair,是否为公平锁
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
1、Semaphore–acquire获取共享锁入口
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
2、AQS–acquireSharedInterruptibly响应中断获取共享锁
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
// 当前线程有中断标识,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 先尝试获取共享锁,tryAcquireShared>=0,表明获取成功
if (tryAcquireShared(arg) < 0)
// 获取失败,将线程放入同步队列
doAcquireSharedInterruptibly(arg);
}
3、Semaphore–tryAcquireShared尝试共享锁
// 尝试获取共享锁,非公平锁实现
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
// 自旋
for (;;) {
// 获取剩余的共享锁数量
int available = getState();
// 计算获取 acquires个共享锁之后剩余的数量
int remaining = available - acquires;
// 如果剩余数量小于0,返回负值,表示尝试获取锁失败
// 通过CAS修改state的值成功,返回0或者正值,获取锁成功
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 尝试获取共享锁,公平锁实现
protected int tryAcquireShared(int acquires) {
// 自旋
for (;;) {
// 如果队列中有节点并且节点线程不为当前线程,返回-1,表示尝试获取锁失败
if (hasQueuedPredecessors())
return -1;
// 走到这里说明队列里面没有节点或节点为当前线程,逻辑跟上面一致
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平锁实现多了一步先判断同步队列中是否有节点,有节点直接加入到队列尾部。
4、AQS–doAcquireSharedInterruptibly将线程放入同步队列
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 将当前线程封装成共享节点,并加入同步队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
// 自旋
for (;;) {
// 获取node的前驱节点
final Node p = node.predecessor();
// 如果前驱节点为头节点
if (p == head) {
// 尝试获取共享锁
int r = tryAcquireShared(arg);
// r>=0,说明获取共享锁成功
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 将当前线程挂起,如果线程有中断标志,响应中断抛出异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
// 出现异常,取消申请共享锁
if (failed)
cancelAcquire(node);
}
}
// 设置头节点,并传播状态
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
// 设置头节点
setHead(node);
/* 尝试唤醒队列中的下一个节点,如果满足以下条件:
* 调用者明确表示传递(propagate > 0)| 头节点的等待状态 < 0(SIGNAL或PROPAGATE)
* 并且node的后继节点处于共享模式或null
* 这两项检查会导致不必要的唤醒,只有在多个线程申请锁时才会发生,所以大多数情况下会立刻获取需要的信号
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
// 唤醒后继节点,因为是共享模式,所有允许多个线程同时获取同步状态
doReleaseShared();
}
}
addWaiter、shouldParkAfterFailedAcquire、parkAndCheckInterrupt这三个方法之前解析过,这里不做赘述。
Semaphore释放锁流程
1、Semaphore–release释放共享锁入口
public void release() {
sync.releaseShared(1);
}
2、AQS–releaseShared释放共享锁
public final boolean releaseShared(int arg) {
// 尝试释放共享锁
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
3、Semaphore–tryReleaseShared尝试释放共享锁
protected final boolean tryReleaseShared(int releases) {
// 自旋
for (;;) {
// 获取当前剩余共享锁数量
int current = getState();
// 计算释放 releases个共享锁之后的数量
int next = current + releases;
// 释放共享锁之后小于当前数量,抛出异常
if (next < current)
throw new Error("Maximum permit count exceeded");
// 通过CAS更新state
if (compareAndSetState(current, next))
return true;
}
}
4、AQS–doReleaseShared唤醒同步队列中的节点
private void doReleaseShared() {
// 自旋
for (;;) {
Node h = head;
// 同步队列不为空
if (h != null && h != tail) {
// 获取头节点的等待状态
int ws = h.waitStatus;
// 如果等待状态为等待唤醒
if (ws == Node.SIGNAL) {
// 更新等待状态为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒头节点的后继节点
unparkSuccessor(h);
}
// 如果ws=0,说明有线程释放共享锁之后,更新了头节点的等待状态,并且唤醒了后继节点
// 但是后继节点还未获取到共享锁,又一个线程释放了共享锁
// 通过CAS将ws更新成更新成Node.PROPAGATE(-3)
// 如果后继节点获取共享锁失败(即又有两个线程获取到共享锁),还是会将ws更新成Node.SIGNAL(shouldParkAfterFailedAcquire方法里)
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 如果头节点发生变化,跳出循环
// 释放锁之后会有其他线程获取到锁,如果是头节点的后继节点,该节点再获取到锁之后会更新头节点
if (h == head)
break;
}
}