synchronized的语义是互斥锁,就是在同一时刻,只有一个线程能获得执行代码的锁。但是现实生活中,有好多的场景,锁不止一把。

比如说,又到了十一假期,买票是重点,必须圈起来。在购票大厅里,有5个售票窗口,也就是说同一时刻可以服务5个人。要实现这种业务需求,用synchronized显然不合适。

查看Java并发工具,发现有一个Semaphore类,天生就是处理这种情况的。

先用Semaphore实现一个购票的小例子,来看看如何使用

package semaphore;import java.util.concurrent.Semaphore;public class Ticket {public static void main(String[] args) {Semaphore windows = new Semaphore(5);  // 声明5个窗口for (int i = 0; i < 8; i++) {new Thread() {@Overridepublic void run() {try {windows.acquire();  // 占用窗口System.out.println(Thread.currentThread().getName() + ": 开始买票");sleep(2000);  // 睡2秒,模拟买票流程System.out.println(Thread.currentThread().getName() + ": 购票成功");windows.release();  // 释放窗口} catch (InterruptedException e) {e.printStackTrace();}}}.start();}}
}

运行结果

Thread-1: 开始买票
Thread-3: 开始买票
Thread-4: 开始买票
Thread-2: 开始买票
Thread-0: 开始买票
Thread-1: 购票成功
Thread-5: 开始买票
Thread-3: 购票成功
Thread-2: 购票成功
Thread-4: 购票成功
Thread-7: 开始买票
Thread-6: 开始买票
Thread-0: 购票成功
Thread-7: 购票成功
Thread-5: 购票成功
Thread-6: 购票成功

View Code

从结果来看,最多只有5个线程在购票。而这么精确的控制,我们也只是调用了acquire和release方法。下面看看是如何实现的。

从acquire方法进去,又可以看到老套路:具体调用的还是AbstractQueuedSynchronizer这个类的逻辑

    public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}

而tryAcquireShared方法留给了子类去实现,Semaphore类里面的两个内部类FairSync和NonfairSync都继承自AbstractQueuedSynchronizer。这两个内部类,从名字来看,一个实现了公平锁,另一个是非公平锁。这里多说一句,所谓公平和非公平是这个意思:假设现在有一个线程A在等待获取锁,这时候又来了线程B,如果这个时候B不考虑A的感受,也去申请锁,显然不公平;反之,只要A是先来的,B一定要排在A的后面,不能马上去申请锁,就是公平的。
Semaphore默认是调用了NonfairSync的tryAcquireShared方法,主要逻辑:
        final int nonfairTryAcquireShared(int acquires) {for (;;) {int available = getState();int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}

这又是一个经典的CAS操作加无限循环的算法,用来保证共享变量的正确性。另外,此处的getState()方法很是迷惑人,你以为是获取状态,实则不然。我们先看看Semaphore的构造方法:

    public Semaphore(int permits) {sync = new NonfairSync(permits);}// 内部类NonfairSync(int permits) {super(permits);}// 内部类,NonfairSync的父类Sync(int permits) {setState(permits);}

我们传进去的参数5,最终传给了setState方法,而getState和setState方法都在AbstractQueuedSynchronizer类里面

    /*** The synchronization state.*/private volatile int state;protected final int getState() {return state;}protected final void setState(int newState) {state = newState;}

也就是说父类定义了一个属性state,并配有final的get和set方法,子类只需要继承该属性,想代表什么含义都可以,比如Semaphore里面的内部类Sync就把这个属性当作最大允许访问的permits,像CountDownLatch和CyclicBarrier都是这么干的。这种方式似乎不太好理解,为什么不是每个子类都定义自己的具有明确语义的属性,而是把控制权放在父类???我猜是出于安全的考虑。反正,大师的思考深度,我们揣摩不了。

再回到tryAcquireShared方法,这个方法是有参数的---int型的acquires,代表你要一次占几个坑。我们调用的无参的acquire方法,默认是传入1作为参数调用的这个方法,一次只申请一个坑。但是有的情况下,你可能一次需要多个,比如高富帅需要同时交多个女朋友。方法的返回值是剩余的坑的数量,如果数量小于0,执行AbstractQueuedSynchronizer这个类的doAcquireSharedInterruptibly方法。

    /*** Acquires in shared interruptible mode.* @param arg the acquire argument*/private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

这个方法的逻辑与独占模式下的逻辑差不多,可以看看之前讲Condition的那篇,出门一路左拐。当所有的坑都被占着的时候,再来的线程都会被封装成节点,添加到等待的队列里面去。不同的是,这里的节点都是共享模式,而共享模式是实现多个坑同时提供服务的核心。

再来看看坑的释放,从release方法进去,核心逻辑在tryReleaseShared方法:

        protected final boolean tryReleaseShared(int releases) {for (;;) {int current = getState();int next = current + releases;if (next < current) // overflowthrow new Error("Maximum permit count exceeded");if (compareAndSetState(current, next))return true;}}

CAS、无限循环,熟悉的配方,熟悉的味道。同获取一样,这里也可以一次释放多个坑。然而,这里考虑到了next小于current的情况,我是绞尽脑汁也没想出来。传进来的releases一般都是大于0的整数(大部分情况下就是1),最终还是会造成next小于current,实在是想不出来,而且还是抛出Error。但是这种情况,通过代码可以精确的再现。好吧,是在下输了。如果读者中有高人,请指点一二,不胜感激!!!

前面这么多都只是分析了非公平模式下的处理逻辑,而公平模式下的逻辑多了一个判断,就是看看前面还有没有线程在等待(节点有没有前驱)。具体的细节,希望读者自己玩味。

最后总结一下:所有的并发核心控制逻辑都在AbstractQueuedSynchronizer这个类中,只有理解了这个类的设计思路,才能真正理解衍生出来的工具类的实现原理。

 

转载于:https://www.cnblogs.com/cz123/p/7500900.html

Semaphore实现原理分析相关推荐

  1. Semaphore 源码分析

    需要提前了解的知识点: AbstractQueuedSynchronizer 实现原理 类介绍 Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公 ...

  2. Java并发编程—AQS原理分析

    目录 一.AQS原理简述 二.自定义独占锁及共享锁 三.锁的可重入性 四.锁的公平性 五.惊群效应 AQS全称AbstractQueuedSynchronizer,它是实现 JCU包中几乎所有的锁.多 ...

  3. 并发编程之 Semaphore 源码分析

    前言 并发 JUC 包提供了很多工具类,比如之前说的 CountDownLatch,CyclicBarrier ,今天说说这个 Semaphore--信号量,关于他的使用请查看往期文章并发编程之 线程 ...

  4. Quartz应用与集群原理分析

    一.问题背景 美团CRM系统中每天有大量的后台任务需要调度执行,如构建索引.统计报表.周期同步数据等等,要求任务调度系统具备高可用性.负载均衡特性,可以管理并监控任务的执行流程,以保证任务的正确执行. ...

  5. 原理剖析(第 009 篇)ReentrantReadWriteLock工作原理分析

    2019独角兽企业重金招聘Python工程师标准>>> 原理剖析(第 009 篇)ReentrantReadWriteLock工作原理分析 一.大致介绍 1.在前面章节了解了AQS和 ...

  6. 操作系统原理分析实验

    操作系统原理分析要点 https://www.cnblogs.com/huyufeng/p/5400639.html 进程的调度时机与进程切换机制 ------------- 调度时机 中断处理过程( ...

  7. java并发包线程池原理分析锁的深度化

    java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素 ...

  8. 嵌入式操作系统多任务调度原理分析与RUST参考实现

    操作系统多任务调度原理分析与RUST参考实现 作为一名在软件领域工程师,在职业生涯的尽头能有幸接触到一部分硬件产品是我莫大的荣幸.秉承我一贯刨根问底,不搞清楚问题本质不罢休的作风和态度,结合基本的计算 ...

  9. java signature 性能_Java常见bean mapper的性能及原理分析

    背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...

最新文章

  1. 强化学习(十二) Dueling DQN
  2. webpackJsonp is not defined?
  3. mysql drop库_Mysql 删除数据库drop database详细介绍
  4. ASP.NET Core MVC 授权的扩展:自定义 Authorize 和 IApplicationModelProvide
  5. mysql 创建查询 删除_MYSQL数据库查询删除创建企业基本知识
  6. HTTP / HTTPS抓包工具-Fiddler
  7. Nature调查再聚焦读博压力:超1/3博士生焦虑抑郁,大学有没有能哭的地方?
  8. 老齐python-基础7(文件操作、迭代)
  9. 突破性能极限,阿里云神龙最新ASPLOS论文解读
  10. linux内核分析及应用 -- 文件系统
  11. 冰点还原精灵、影子系统区别哪个好
  12. Sublime格式化代码快捷键
  13. html表格边框线怎么加粗,CAD表格边框如何加粗?CAD表格边框加粗的方法
  14. 亲密接触Redis-第一天
  15. xmos xu208加密
  16. linux区分物理机和虚拟机,如何判断linux服务器是虚拟机还是物理机
  17. 阿里数据仓库-数据模型建设方法总结(全)
  18. 叮当健康明日港股上市:拟募资3.4亿港元 单季期内亏损4亿
  19. 曹大带我学 Go(2)—— 迷惑的 goroutine 执行顺序
  20. C语言实现归并排序——2路归并排序

热门文章

  1. 银行死都不告诉你的10个秘密
  2. NLP《词汇表示方法(六)ELMO》
  3. mount挂载时 no such device_mount系统调用(vfs_kern_mount-gt;mount_fs-gt;fill_super)
  4. 深入理解傅里叶变换的性质:实函数、卷积、相关、功率谱、频响函数
  5. toj 4613 Number of Battlefields
  6. python3.4新特性_Python3中的新特性(1)——新的语言特性
  7. linux系统如何创建python文件_请问linux下如何创建pycharm的快捷方式?
  8. android控件单位,Android控件相对位置及长度单位
  9. mysql 32k 限制,MySQL之最大和最小
  10. mysql编译和yum安装哪个好_Centos7下PHP源码编译和通过yum安装的区别和以后的选择...