“J.U.C”:Semaphore

信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个“共享锁”。

Java并发提供了两种加锁模式:共享锁和独占锁。前面LZ介绍的ReentrantLock就是独占锁。对于独占锁而言,它每次只能有一个线程持有,而共享锁则不同,它允许多个线程并行持有锁,并发访问共享资源。

独占锁它所采用的是一种悲观的加锁策略, 对于写而言为了避免冲突独占是必须的,但是对于读就没有必要了,因为它不会影响数据的一致性。如果某个只读线程获取独占锁,则其他读线程都只能等待了,这种情况下就限制了不必要的并发性,降低了吞吐量。而共享锁则不同,它放宽了加锁的条件,采用了乐观锁机制,它是允许多个读线程同时访问同一个共享资源的。

Semaphore简介

Semaphore,在API中是这样介绍的,一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。下面LZ以理发为例来简述Semaphore。

为了简单起见,我们假设只有三个理发师、一个接待人。一开始来了五个客人,接待人则安排三个客人进行理发,其余两个人必须在那里等着,此后每个来理发店的人都必须等待。一段时间后,一个理发师完成理发后,接待人则安排另一个人(公平还是非公平机制呢??)来理发。在这里理发师则相当于公共资源,接待人则相当于信号量(Semaphore),客户相当于线程。

进一步讲,我们确定信号量Semaphore是一个非负整数(>=1)。当一个线程想要访问某个共享资源时,它必须要先获取Semaphore,当Semaphore >0时,获取该资源并使Semaphore – 1。如果Semaphore值 = 0,则表示全部的共享资源已经被其他线程全部占用,线程必须要等待其他线程释放资源。当线程释放资源时,Semaphore则+1;

当信号量Semaphore = 1 时,它可以当作互斥锁使用。其中0、1就相当于它的状态,当=1时表示其他线程可以获取,当=0时,排他,即其他线程必须要等待。

Semaphore源码分析

Semaphore的结构如下:

从上面可以看出,Semaphore和ReentrantLock一样,都是包含公平锁(FairySync)和非公平锁(NonfairSync),两个锁都是继承Sync,而Sync也是继承自AQS。其构造函数如下:

信号量的获取:acquire()

在ReentrantLock中已经阐述过,公平锁和非公平锁获取锁机制的差别:对于公平锁而言,如果当前线程不在CLH队列的头部,则需要排队等候,而非公平锁则不同,它无论当前线程处于CLH队列的何处都会直接获取锁。所以公平信号量和非公平信号量的区别也一样。

对于公平信号量和非公平信号量,他们机制的差异就体现在traAcquireShared()方法中:

公平锁

tryAcquireShared是尝试获取 信号量,remaining表示下次可获取的信号量。

对于hasQueuedPredecessors、compareAndSetState在ReentrantLock中已经阐述了,hasQueuedPredecessors用于判断该线程是否位于CLH队列列头,compareAndSetState用于设置state的,它是进行原子操作的。代码如下:

doAcquireSharedInterruptibly源代码如下:

doAcquireSharedInterruptibly主要是做两个工作;1、尝试获取共享锁,2、阻塞线程直到线程获取共享锁。

addWaiter(Node.SHARED):创建”当前线程“的Node节点,且Node中记录的锁的类型是”共享锁“(Node.SHARED);并将该节点添加到CLH队列末尾。

shouldParkAfterFailedAcquire:如果在尝试获取锁失败之后,线程应该等待,返回true;否则返回false。

parkAndCheckInterrupt:当前线程会进入等待状态,直到获取到共享锁才继续运行。

对于addWaiter、shouldParkAfterFailedAcquire、parkAndCheckInterruptLZ在“【Java并发编程实战】-----“J.U.C”:ReentrantLock之二lock方法分析”中详细介绍了。

非公平锁

对于非公平锁就简单多了,她没有那些所谓的要判断是不是CLH队列的列头,如下:

在非公平锁中,tryAcquireShared直接调用AQS的nonfairTryAcquireShared()。通过上面的代码我可看到非公平锁并没有通过if (hasQueuedPredecessors())这样的条件来判断该节点是否为CLH队列的头节点,而是直接判断信号量。

信号量的释放:release()

信号量Semaphore的释放和获取不同,它没有分公平锁和非公平锁。如下:

release()释放线索所占有的共享锁,它首先通过tryReleaseShared尝试释放共享锁,如果成功直接返回,如果失败则调用doReleaseShared来释放共享锁。

tryReleaseShared:

doReleaseShared:

在这里有关的方法,请参考:【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析。

实例

该实例来源于《java7并发编程实战手册》

打印任务:

Job:

Test:

运行结果:

本文来自企鹅号 - 云时代架构媒体

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Jerry的SAP技术分享

SAP云平台CloudFoundry中的用户自定义变量

CloudFoundry应用的manifest.xml里的env区域,允许用户自定义变量,如下图5个变量所示。

23330
来自专栏LEo的网络日志

python i18n实现

40770
来自专栏函数式编程语言及工具

Akka-Cluster(1)- Cluster Singleton 单例节点

关于cluster-singleton我在前面的博文已经介绍过,在这篇我想回顾一下它的作用和使用方法。首先,cluster-singleton就是集群某个节点上...

13230
来自专栏黑泽君的专栏

day32_Hibernate学习笔记_04

  缓存(Cache):是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写硬盘(永久性数...

10520
来自专栏老码农专栏

原 荐 RESTFul 服务测试自动化的艺术

17030
来自专栏北京马哥教育

Linux环境下 LVM 逻辑卷的建立、扩容和减容操作

LVM是逻辑盘卷管理(Logical Volume Manager)的简称,它是Linux环境下对磁盘分区进行管理的一种机制,LVM是建立在硬盘和分区之上的一个...

34520
来自专栏IT技术精选文摘

Spring Boot使用过滤器和拦截器分别实现REST接口简易安全认证

95220
来自专栏代码拾遗

Spring Boot 2.0 教程 - 配置详解

Spring Boot 可以通过properties文件,YAML文件,环境变量和命令行参数进行配置。属性值可以通过,@Value注解,Environment...

12830
来自专栏钟绍威的专栏

锁的实现原理解锁的实现加锁的实现

 锁在多线程中是必不可少的,他给多线程提供了同步的功能,让多线程可以互斥的执行同步块,并具有可见性。  本文将从happens-before关系出发,结合Ree...

24270
来自专栏xingoo, 一个梦想做发明家的程序员

如何在Java应用中提交Spark任务?

最近看到有几个Github友关注了Streaming的监控工程——Teddy,所以思来想去还是优化下代码,不能让别人看笑话啊。于是就想改一下之前觉得最丑陋的一...

79860

扫码关注云+社区

领取腾讯云代金券