AQS共享锁应用之Semaphore原理
我们调用Semaphore方法时,其实是在间接调用其内部类或AQS方法执行的。Semaphore类结构与ReetrantLock类相似,内部类Sync继承自AQS,然后其子类FairSync和NoFairSync分别实现公平锁和非公平锁的获取锁方法tryAcquireShared(int arg)
,而释放锁的tryReleaseShared(int arg)
方法则有Sync类实现,因为非公平或公平锁的释放过程都是相同的。
AQS通过state值来控制对共享资源访问的线程数,有线程请求同步状态成功state值减1,若超过共享资源数量获取同步状态失败,则将线程封装共享模式的Node结点加入到同步队列等待。有线程执行完任务释放同步状态后,state值会增加1,同步队列中的线程才有机会获得执行权。公平锁与非公平锁不同在于公平锁申请获取同步状态前都会先判断同步队列中释放存在Node,若有则将当前线程封装成Node结点入队,从而保证按FIFO的方式获取同步状态,而非公平锁则可以直接通过竞争获取线程执行权。
//Semaphore的acquire()
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} /**
* 注意Sync类继承自AQS
* AQS的acquireSharedInterruptibly()方法
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判断是否中断请求
if (Thread.interrupted())
throw new InterruptedException();
//如果tryAcquireShared(arg)不小于0,则线程获取同步状态成功
if (tryAcquireShared(arg) < 0)
//未获取成功加入同步队列等待
doAcquireSharedInterruptibly(arg);
}
//Semaphore中非公平锁NonfairSync的tryAcquireShared()
protected int tryAcquireShared(int acquires) {
//调用了父类Sync中的实现方法
return nonfairTryAcquireShared(acquires);
} final int nonfairTryAcquireShared(int acquires) {
//使用死循环
for (;;) {
int available = getState();
int remaining = available - acquires;
//判断信号量是否已小于0或者CAS执行是否成功
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//创建共享模式的结点Node.SHARED,并加入同步队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
//进入自旋操作
for (;;) {
final Node p = node.predecessor();
//判断前驱结点是否为head
if (p == head) {
//尝试获取同步状态
int r = tryAcquireShared(arg);
//如果r>0 说明获取同步状态成功
if (r >= 0) {
//将当前线程结点设置为头结点并传播
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//调整同步队列中node结点的状态并判断是否应该被挂起
//并判断是否需要被中断,如果中断直接抛出异常,当前结点请求也就结束
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
//结束该结点线程的请求
cancelAcquire(node);
}
} private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取当前结点的等待状态
int ws = pred.waitStatus;
//如果为等待唤醒(SIGNAL)状态则返回true
if (ws == Node.SIGNAL)
return true;
//如果ws>0 则说明是结束状态,
//遍历前驱结点直到找到没有结束状态的结点
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果ws小于0又不是SIGNAL状态,
//则将其设置为SIGNAL状态,代表该结点的线程正在等待唤醒。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
} private final boolean parkAndCheckInterrupt() {
//将当前线程挂起
LockSupport.park(this);
//获取线程中断状态,interrupted()是判断当前中断状态,
//并非中断线程,因此可能true也可能false,并返回
return Thread.interrupted();
} //不可中的acquireShared()
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
} 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);
}
} private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);//设置为头结点
/*
* 尝试去唤醒队列中的下一个节点,如果满足如下条件:
* 调用者明确表示"传递"(propagate > 0),
* 或者h.waitStatus为PROPAGATE(被上一个操作设置)
* 并且
* 下一个节点处于共享模式或者为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();
}
} //Semaphore的release()
public void release() {
sync.releaseShared(1);
} //调用到AQS中的releaseShared(int arg)
public final boolean releaseShared(int arg) {
//调用子类Semaphore实现的tryReleaseShared方法尝试释放同步状态
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
} //在Semaphore的内部类Sync中实现的
protected final boolean tryReleaseShared(int releases) {
for (;;) {
//获取当前state
int current = getState();
//释放状态state增加releases
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
//通过CAS更新state的值
if (compareAndSetState(current, next))
return true;
}
} private void doReleaseShared() {
/*
* 保证释放动作(向同步等待队列尾部)传递,即使没有其他正在进行的
* 请求或释放动作。如果头节点的后继节点需要唤醒,那么执行唤醒
* 动作;如果不需要,将头结点的等待状态设置为PROPAGATE保证
* 唤醒传递。另外,为了防止过程中有新节点进入(队列),这里必
* 需做循环,所以,和其他unparkSuccessor方法使用方式不一样
* 的是,如果(头结点)等待状态设置失败,重新检测。
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
// 获取头节点对应的线程的状态
int ws = h.waitStatus;
// 如果头节点对应的线程是SIGNAL状态,则意味着头
//结点的后继结点所对应的线程需要被unpark唤醒。
if (ws == Node.SIGNAL) {
// 修改头结点对应的线程状态设置为0。失败的话,则继续循环。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒头结点h的后继结点所对应的线程
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果头结点发生变化,则继续循环。否则,退出循环。
if (h == head) // loop if head changed
break;
}
} //唤醒传入结点的后继结点对应的线程
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);
}
剖析基于并发AQS的共享锁的实现(基于信号量Semaphore)
AQS共享锁应用之Semaphore原理的更多相关文章
- ReentrantReadWriteLock 源码分析以及 AQS 共享锁 (二)
前言 上一篇讲解了 AQS 的独占锁部分(参看:ReentrantLock 源码分析以及 AQS (一)),这一篇将介绍 AQS 的共享锁,以及基于共享锁实现读写锁分离的 ReentrantReadW ...
- 面经手册 · 第18篇《AQS 共享锁,Semaphore、CountDownLatch,听说数据库连接池可以用到!》
作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...
- Java 中队列同步器 AQS(AbstractQueuedSynchronizer)实现原理
前言 在 Java 中通过锁来控制多个线程对共享资源的访问,使用 Java 编程语言开发的朋友都知道,可以通过 synchronized 关键字来实现锁的功能,它可以隐式的获取锁,也就是说我们使用该关 ...
- 一文搞懂AQS及其组件的核心原理
@ 目录 前言 AbstractQueuedSynchronizer Lock ReentrantLock 加锁 非公平锁/公平锁 lock tryAcquire addWaiter acquireQ ...
- AQS的数据结构及实现原理
接下来从实现角度来分析同步器是如何完成线程同步的.主要包括:同步队列.独占式同步状态获取与释放.共享式同步状态获取与释放以及超时获取同步状态等. 1.同步队列 同步器依赖内部的一个同步队列来完成同步状 ...
- AQS之CountDownLatch、Semaphore、CyclicBarrier
CountDownLatch A synchronization aid that allows one or more threads to wait until a set of operatio ...
- Java并发——结合CountDownLatch源码、Semaphore源码及ReentrantLock源码来看AQS原理
前言: 如果说J.U.C包下的核心是什么?那我想答案只有一个就是AQS.那么AQS是什么呢?接下来让我们一起揭开AQS的神秘面纱 AQS是什么? AQS是AbstractQueuedSynchroni ...
- CountDownLatch、CyclicBarrier和Semaphore 使用示例及原理
备注:博客园的markDown格式支持的特别不友好.也欢迎查看我的csdn的此篇文章链接:CountDownLatch.CyclicBarrier和Semaphore 使用示例及原理 CountDow ...
- 深入浅出AQS之共享锁模式
在了解了AQS独占锁模式以后,接下来再来看看共享锁的实现原理. 原文地址:http://www.jianshu.com/p/1161d33fc1d0 搞清楚AQS独占锁的实现原理之后,再看共享锁的实现 ...
随机推荐
- 何时才使用https访问项目
利用keytools生产证书,然后将证书导入到jvm和tomcat中,则访问该项目的时候就以https访问
- List<InvestInfoDO> invest = advertiseDao6.qryInvestInfo(InvestInfoDO1);怎样获得list的实体类;
List<InvestInfoDO> invest = advertiseDao6.qryInvestInfo(InvestInfoDO1); 怎样获得List的实体类呢,就是怎样获得I ...
- centos创建本地yum仓库
怎样发布自己软件的安装和更新YUM源 在创建之前,我们先了解些相关的内容: yum仓库可以支持三种途径提供给yum在安装的时候下载rpm包 第一种: ftp服务 ftp:// 第二种: http ...
- CentOs中mysql的安装与配置(转)
在linux中安装数据库首选MySQL,Mysql数据库的第一个版本就是发行在Linux系统上,其他选择还可以有postgreSQL,oracle等 在Linux上安装mysql数据库,我们可以去其官 ...
- VS2010配置QT5.5.0开发环境
一.官网下载QT和qtvsaddin插件 网址:http://www.qt.io/download-open-source/ 1. 2. 3. 得到下载的安装包,点击安装就能够了 watermark/ ...
- 一起talk GDB吧(第二回:GDB单步调试)
各位看官们,大家好.我们在上一回中说简单地介绍了GDB.这一回中,我们介绍GDB的调试功能:单步 调试. 闲话休提,言归正转. 让我们一起talk GDB吧! 看官们,我们先说一下什么是单步调试.大家 ...
- 算法NB三人组
#快速排序-除了python自带的sort排序模块之外就这个最好用,只需会这个就行,其他的排序了解就好,能用冒泡,插入..的都可以用快排快速实现 import random from timewrap ...
- 手机pc显示不同的内容
<script type="text/javascript"> // var txt = $('#sjyincang').html(); // alert(txt); ...
- CF 445A(DZY Loves Chessboard-BW填充)
A. DZY Loves Chessboard time limit per test 1 second memory limit per test 256 megabytes input stand ...
- vs2015编译EasyDarwin开源流媒体服务器Linux版本调研
本文转自EasyDarwin团队成员Alex的博客:http://blog.csdn.net/cai6811376/article/details/51843196 之前InfoQ的一篇文章提到用vs ...