我们调用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原理的更多相关文章

  1. ReentrantReadWriteLock 源码分析以及 AQS 共享锁 (二)

    前言 上一篇讲解了 AQS 的独占锁部分(参看:ReentrantLock 源码分析以及 AQS (一)),这一篇将介绍 AQS 的共享锁,以及基于共享锁实现读写锁分离的 ReentrantReadW ...

  2. 面经手册 · 第18篇《AQS 共享锁,Semaphore、CountDownLatch,听说数据库连接池可以用到!》

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

  3. Java 中队列同步器 AQS(AbstractQueuedSynchronizer)实现原理

    前言 在 Java 中通过锁来控制多个线程对共享资源的访问,使用 Java 编程语言开发的朋友都知道,可以通过 synchronized 关键字来实现锁的功能,它可以隐式的获取锁,也就是说我们使用该关 ...

  4. 一文搞懂AQS及其组件的核心原理

    @ 目录 前言 AbstractQueuedSynchronizer Lock ReentrantLock 加锁 非公平锁/公平锁 lock tryAcquire addWaiter acquireQ ...

  5. AQS的数据结构及实现原理

    接下来从实现角度来分析同步器是如何完成线程同步的.主要包括:同步队列.独占式同步状态获取与释放.共享式同步状态获取与释放以及超时获取同步状态等. 1.同步队列 同步器依赖内部的一个同步队列来完成同步状 ...

  6. AQS之CountDownLatch、Semaphore、CyclicBarrier

    CountDownLatch A synchronization aid that allows one or more threads to wait until a set of operatio ...

  7. Java并发——结合CountDownLatch源码、Semaphore源码及ReentrantLock源码来看AQS原理

    前言: 如果说J.U.C包下的核心是什么?那我想答案只有一个就是AQS.那么AQS是什么呢?接下来让我们一起揭开AQS的神秘面纱 AQS是什么? AQS是AbstractQueuedSynchroni ...

  8. CountDownLatch、CyclicBarrier和Semaphore 使用示例及原理

    备注:博客园的markDown格式支持的特别不友好.也欢迎查看我的csdn的此篇文章链接:CountDownLatch.CyclicBarrier和Semaphore 使用示例及原理 CountDow ...

  9. 深入浅出AQS之共享锁模式

    在了解了AQS独占锁模式以后,接下来再来看看共享锁的实现原理. 原文地址:http://www.jianshu.com/p/1161d33fc1d0 搞清楚AQS独占锁的实现原理之后,再看共享锁的实现 ...

随机推荐

  1. 使用 sftp 向linux服务器传输文件

    sftp是加密的文件传输. 登陆 sftp name@123.21.331.1 1 2.把本地文件name1传到服务器name2下 put /name1.html /name2/ 1 把服务器name ...

  2. HDU 1253:胜利大逃亡(简单三维BFS)

    pid=1253">胜利大逃亡 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/ ...

  3. 怎样制作gif图片?怎样制作你项目的动态效果图到你的csdn?

    怎样制作gif图?怎样上传你项目的动态效果图到你的csdn? 这仅仅是笔者用的方法.有其它方法的欢迎分享. 一张或几张展示了你的项目的功能及效果的动态图放在博客文章开头会为你的文章润色不少. 相信非常 ...

  4. mysql的安装、C++訪问mysql数据库、编码设置问题

    一.mysql的安装.这个相对简单,直接去官网下载mysql安装程序.就能够完毕安装过程,网上有非常多安装教程,这个没什么注意事项. 二.C++訪问mysql.主要是用到mysql定义的头文件,内部定 ...

  5. mysql 分表的3种方法

    http://blog.51yip.com/mysql/949.html       CSDN - Mysql MERGE分表对大数据量的处理     实战经验: 要分表的表引擎必须是myisam类型 ...

  6. C#应该掌握的一些东西

    C#应该掌握的一些东西   随着培训机构的增多,越来越多的人进入IT行业.那么对于我们这些自学出来,经验不够丰富的转行者来说,我们需要掌握最起码的一些东西,这对于面试很有用,而且在工作中也很常用.本人 ...

  7. 【Bootstrap】一个兼容IE8、谷歌等主流浏览器的受众门户式风格页面

    上一次写的<[Bootstrap]一个兼容IE8.谷歌等主流浏览器的受众巨幕式风格页面>(点击打开链接) 部分老一辈的需求可能对这样的后现代的风格并不惬意, 没关系,我们全然能够改变布局 ...

  8. VS2012+Win7站点公布具体步骤

    VS2012+Win7站点公布详细步骤 本机环境: 本文分三个部分介绍Web项目公布的常规方法,大神级别能够略过,主要是为了方便一些刚開始学习的人. 第一部分:VS2012把项目公布到文件系统. 第二 ...

  9. WPF02(concept)

    (转自http://www.cnblogs.com/huangxincheng/archive/2012/06/17/2552322.html)这些天从项目上接触到了wpf,感觉有必要做一个笔记,首篇 ...

  10. 宜人贷蜂巢API网关技术解密之Netty使用实践

    一.背景 宜人贷蜂巢团队,由Michael创立于2013年,通过使用互联网科技手段助力金融生态和谐健康发展.自成立起一直致力于多维度数据闭环平台建设.目前团队规模超过百人,涵盖征信.电商.金融.社交. ...