网站首页 > 精选教程 正文
由于篇幅原因,小编已将Semaphore学习以及其他Java系列知识都整理出来了,有需要的私信我关键词 “Java”,回复获取免费下载原文件的方式。
什么是Semaphore
Semaphore也叫信号量,信号的含义是指,通过约定好的某种途径,用来指挥行动或进行指示。对应于Semaphore针对的使用场景,是如何并发地占用有限的公共资源。
考虑一种现实的场景——餐厅的椅子,我们保证绝不让客人坐在同一张椅子上。那么只有当可用椅子数量满足下一批要进食的客人数量时,才接待这些客人。
Semaphore的特点为:
- 不可重入
- 支持公平锁与不公平锁
- 可以申请与释放多个锁
- 继承了AQS的其他特性
如果你所面对的场景与上相似,那么Semaphore将所帮助。
例子
Semaphore的使用也非常简单,上面的列子,一个简单程序为
public static void main(String[] args) {
// 假设餐厅有20张椅子
Semaphore semaphore = new Semaphore(20 , true);
Random random = new Random();
// 10是假设单位时间的单子量
for (int i=0; i<10; i++){
Thread t = new Thread(){
@Override
public void run() {
try {
for (;;){
// count是这个单子的客人要占用多少把椅子
int count = random.nextInt(9) + 1;
System.out.println(getName() + " need " + count + " chairs.");
// 为客人安排椅子
semaphore.acquire(count);
System.out.println(getName() + " 进食,占用 " + count + " chairs.");
// 安排上了,假设了进食时间
sleep(1000);
System.out.println(getName() + " 离开 。");
// 空出来椅子
semaphore.release(count);
}
} catch (Exception e){
}
}
};
t.setName("Thread --> " + i);
t.start();
}
}
程序将一直执行下去,不会漏单,也不会出现椅子占用数量大于20的情况。
AQS基础
Semaphore是一种共享锁,实现依赖于AQS。对于锁,包含两部分知识,一部分是如何加解锁,另一部分是把锁分配给谁。AQS解决了把锁分配给谁的问题,Semaphore就可以聚焦于如何加解锁上。
掌握AQS将使Semaphore如何运转变得非常简单,没掌握也不影响对于本文的理解。
AQS原理可以参考:一文了解AQS
这里,需要了解AQS是如何运转的:
- 当申请锁,即调用了与acquire()类似语义的方法时,AQS将询问子类是否上锁成功,成功则继续运行。否则,AQS将以Node为粒度,记录这个申请锁的请求,将其插入自身维护的CLH队里中并挂起这个线程
- 在CLH队列中,只有最靠近头节点的未取消申请锁的节点,才有资格申请锁
- 当线程被唤醒时,会尝试获取锁,如果获取不到继续挂起;获取得到则继续运行
- 当一个线程释放锁,即调用release()类似语义的方法时,AQS将询问子类是否解锁成功,有锁可以分配,如果有,AQS从CLH队列中主动唤起合适的线程,过程为2、3
- 如果需要等待条件满足再去申请锁,即调用了wait()类似语义的方法时,在AQS中表现为,以Node为粒度,维护一个单向等待条件队列,把Node所代表的线程挂起
- 当条件满足时,即调用了signal()类似语义的方法时,唤醒等待条件队列最前面的未取消等待的Node,执行1
- 子类可以维护AQS的state属性来记录加解锁状态,AQS也提供了CAS的方法compareAndSetState()抢占更新state
简要来说,AQS分配锁时,当前线程可能会被挂起,接着被唤醒继续尝试申请锁,重复此过程直到获取到锁或取消等待。从外部看,就如入口方法被阻塞并在未来被恢复了一样。
Sync
Seamphore以内部类Sync继承AQS,并完成AQS所需子类实现的方法语义,Seamphore使用Sync即可完成各项工作。Sync应答加解锁的方法分别为nonfairTryAcquireShared()与tryReleaseShared()。
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 读取当前剩余资源
int available = getState();
// 获取 acquires数量 后剩余多少资源
int remaining = available - acquires;
if (remaining < 0 /*①*/||
// CAS 更新状态,失败说明有竞争条件,重新进入循环
compareAndSetState(available, remaining))
// 告诉AQS结果,大于等于0说明加锁成功
return remaining;
}
}
acquires的数量表示当前的线程要获取的资源数量,针对于AQS,就可以理解为需要多少把锁。在①处,说明锁已经分配出去很多了,不够分配了,因此分配不成功。
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 当前还剩下的锁
int current = getState();
// 解releases个锁后,剩余的锁数量
int next = current + releases;
if (next < current)
// 进到这里说明,某个获取到锁的线程释没有正确释放锁
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
// CAS 更新锁状态,失败就重新进入循环
// ②
// 告诉AQS释放锁成功,有更多的锁可以分配
return true;
}
}
②处,tryReleaseShared()总是释放成功是因为,除非使用方式不当,否则没有理由到时释放失败。而当出现这种情况时,应直接抛出异常,避免程序进一步地错误下去。
实例化时,需要指定锁的数量。
Sync(int permits) {
setState(permits);
}
不公平锁的实现
Semaphore默认为不公平锁,可以通过Semaphore的实例化参数,决定Semaphore是否公平。不公平锁的实现,以Sync的子类NonfairSync实现。
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
直接使用Sync的方法,不赘述。
公平锁的实现
公平锁以Sync的子类FairSync实现。
static final class FairSync extends Sync {
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
// 如果AQS的CLH队列中,有代表线程在等待的Node在排队,直接告诉AQS加锁失败
if (hasQueuedPredecessors())
return -1;
// 当前可获取的锁数
int available = getState();
// 获取acquires个锁后,还剩下多少
int remaining = available - acquires;
if (remaining < 0 /*与nonfairTryAcquireShared()的说明一样*/||
compareAndSetState(available, remaining) /*CAS更新锁数量,失败重新进入循环*/)
// 告诉AQS加锁成功与否,>= 0视为成功
return remaining;
}
}
}
从AQS基础一节的图中,在首次申请锁时,是有机会插队的。那么,公平与不公平的区别在于,当有新的线程进行请求时,是否忽略CLH队列中正在排队的情况。而FairSync就考虑了CLH队列中的情况,如果CLH队列中有正在排队的线程,则直接入队等待。
总结
Semaphore的入口方法均是对于Sync的操作,一目了然,足够简单,且支持的AQS的特性也没有过多封装,因此这部分的代码不做贴出。
以上面的内容来看,Semaphore是不支持重入的,如果获得锁的线程再次请求锁,可能需要排队而挂起,甚至造成死锁,不要这么使用!简单的证明为
// 将“例子”一节中的for循环改成如下
for(;;){
semaphore.acquire(5);
semaphore.acquire(5);
sleep(1000);
semaphore.release(10);
}
整体来看,Semaphore亦无晦涩难懂之处,核心仅为,当前申请锁时,通过CAS,在加锁时判断锁的数量是否足够申请,在释放锁时更新锁的数量的状态。这也说明了AQS的重要性,学会AQS更能掌握Java并发的核心知识。
注意一下咯:由于篇幅原因,小编已将这些相关的知识都集结整理出来了,有需要的老铁私信下我关键词 “Java”,回复获取免费下载原文件的方式。
作者:MxsQ
链接:https://juejin.cn/post/6950219938929836063
来源:掘金
猜你喜欢
- 2024-11-06 信号量限流,高并发场景不得不说的秘密
- 2024-11-06 面试卡在多线程?那就分享几道Java多线程高频面试题,面试不用愁
- 2024-11-06 Java并发系列之Semaphore源码分析
- 2024-11-06 Java多线程与并发 java的并发,多线程,线程模型
- 2024-11-06 66.java并发编程之Semaphore和CountDownLatch使用
- 2024-11-06 Java基础笔试练习(十五) java基础知识试题
- 2024-11-06 72道Java线程面试题,这些面试官必问
- 2024-11-06 Java并发基础-锁详细分析(可重入锁、读写锁、信号量等)
- 2024-11-06 Java并发工具:CountDownLatch CyclicBarrier Semaphore快速掌握
- 2024-11-06 死磕 java同步系列之Semaphore源码解析
你 发表评论:
欢迎- 04-11Java面试“字符串三兄弟”String、StringBuilder、StringBuffer
- 04-11Java中你知道几种从字符串中找指定的字符的数量
- 04-11探秘Java面试中问的最多的String、StringBuffer、StringBuilder
- 04-11Python字符串详解与示例(python字符串的常见操作)
- 04-11java正则-取出指定字符串之间的内容
- 04-11String s1 = new String("abc");这句话创建了几个字符串对象?
- 04-11java判断字符串中是否包含某个字符
- 04-11关于java开发中正确的发牌逻辑编写规范
- 最近发表
-
- Java面试“字符串三兄弟”String、StringBuilder、StringBuffer
- Java中你知道几种从字符串中找指定的字符的数量
- 探秘Java面试中问的最多的String、StringBuffer、StringBuilder
- Python字符串详解与示例(python字符串的常见操作)
- java正则-取出指定字符串之间的内容
- String s1 = new String("abc");这句话创建了几个字符串对象?
- java判断字符串中是否包含某个字符
- 关于java开发中正确的发牌逻辑编写规范
- windows、linux如何后台运行jar(并且显示进程名)
- 腾讯大佬私人收藏,GitHub上最受欢迎的100个JAVA库,值得学习
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)