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独占锁的实现原理之后,再看共享锁的实现 ...
随机推荐
- 转:在CentOS下编译安装GCC
转:https://teddysun.com/432.html 在CentOS下编译安装GCC 技术 秋水逸冰 发布于: 2015-09-02 更新于: 2015-09-02 6519 次围观 ...
- django_session
基于cookie做用户验证时:敏感信息不适合放在cookie中 session依赖cookie session原理 cookie是保存在用户浏览器端的键值对 session是保存在服务器端的键值对 s ...
- Push flow
自动移库规则push flow可以用来规划物流 比如产品A如果进入到picking区,按照仓储的规则,系统可以自动生产调拨单,将产品A 从picking区调拨到保存的库位货架A1E1 设置步骤 ...
- 广告制胜无它,顺应人性尔——leo鉴书63
近期看了几本怎样写文案的书.对广告有了些兴趣.查了下相关销量排行,位置比較高的是本叫<科学的广告+我的广告生涯>的书,是同一作者(Claude C. Hopkins)两本书的合集.前者是他 ...
- 程序猿的量化交易之路(30)--Cointrader之ConfigUtil(17)
转载须注明出处:viewmode=contents">http://blog.csdn.net/minimicall?viewmode=contents.http://cloudtra ...
- C语言--函数篇
1-1.函数简单调用 1 #include<stdio.h> 2 #include<string.h> 3 #include<windows.h> 4 int ...
- HMM MEMM CRF 差别 联系
声明:本文主要是基于网上的材料做了文字编辑,原创部分甚少.參考资料见最后. 隐马尔可夫模型(Hidden Markov Model.HMM),最大熵马尔可夫模型(Maximum Entropy Mar ...
- Qt在线讲座之QML脚本书写规范
时间:2016年3月1日晚7:30 在线讲座:http://qtdream.com主页处就可以收看直播(详见主页提示) 參与对象:对Qt跨平台开发框架感兴趣的朋友们.当然了,假设你是大牛.也可以旁听一 ...
- C# 之 集合ArrayList
.NET Framework提供了用于数据存储和检索的专用类,这些类统称集合. 这些类提供对堆栈.队列.列表和哈希表的支持.大多数集合类实现系统的接口.以下我们主要来讲一下ArrayList. ...
- WinCE下使用C#的几个小技巧
1.我们知道,在使用Windows的开发机上用C#启动一个外部程序的方法有很多,但这些方法用在使用WinCE的目标工控机上都无能为力,现在小嫚儿以打开一个IE为例,介绍如何在WinCE下使用C#来打开 ...