Semaphore
,一个计数信号量,JDK 1.5 开始提供的一个同步工具。Semaphore 信号量被用来限制对某些资源同时访问的线程数量。例如接口限流,控制一个文件允许的并发访问数等等。
从概念上讲,Semaphore 维护着一组许可证,每一个需要访问资源的线程都需要从 Semaphore 拿到许可证。
其实,没有真正意义上的许可证对象,Semaphore 是通过维护一个数字来代表可获取的许可证数量,并进行加减操作。每一个调用 acquire
方法的线程会阻塞直到拿到许可证为止;调用 release
方法则会向 Semaphore 增加一个许可证。
而且使用 Semaphore 时,锁还可以被其他线程释放(即增加许可证),这在某些特定情况下非常有用,例如可用于死锁的解除。
package com.chenpi;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
-
@Description
-
@Author 陈皮
-
@Date 2021/7/11
-
@Version 1.0
*/
public class ChenPiMain {
public static void main(String[] args) {
// 许可证数量为2,即代表同时只能有2个线程能访问资源
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + “开始执行…”);
TimeUnit.SECONDS.sleep(new Random().nextInt(5) + 1);
System.out.println(Thread.currentThread().getName() + “结束执行…”);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
// 输出结果如下
Thread-1开始执行…
Thread-0开始执行…
Thread-1结束执行…
Thread-3开始执行…
Thread-3结束执行…
Thread-2开始执行…
Thread-0结束执行…
Thread-4开始执行…
Thread-4结束执行…
Thread-2结束执行…
只有一个许可证的信号量,可用作互斥锁(类似于 synchronized ),也通常被称为二进制信号量,因为它只有两种状态,一种状态是当前有一个许可证可被获取,另一种状态是当前没有许可证可被获取。
package com.chenpi;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
/**
-
@Description
-
@Author 陈皮
-
@Date 2021/7/11
-
@Version 1.0
*/
public class ChenPiMain {
// 共享资源
private static int count = 0;
// 线程数量
private static final int MAX_THREAD = 1000;
public static void main(String[] args) throws InterruptedException {
// 许可证数量为1,充当互斥锁
Semaphore semaphore = new Semaphore(1);
List threads = new ArrayList<>(MAX_THREAD);
for (int i = 0; i < MAX_THREAD; i++) {
Thread thread = new Thread(() -> {
try {
semaphore.acquire();
// 每一个线程都对共享资源操作
count++;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
threads.add(thread);
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println(count); // 输出结果1000
}
}
Semaphore 的类结构图如下:
Semaphore 底层主要通过 AQS(AbstractQueuedSynchronizer)来实现的。我们在构造 Semaphore 时,传入的许可证数量,最终传递给了 AQS 的 state
属性,许可证数量即代表允许同时多少线程访问资源。
public class Semaphore implements java.io.Serializable {
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
…
}
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits);
}
…
}
…
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private volatile int state;0
protected final void setState(int newState) {
state = newState;
}
…
}
Semaphore 的构造方法接收的许可证数量参数,值可以为负数,这样的话需要保证在调用 acquires 方法之前调用过 releases 方法来增加许可证,不然会导致没有线程能获取到许可证。
// 许可证数量为负数
Semaphore semaphore = new Semaphore(-1);
// 所以需要先调用release,那么acquires方法才有意义
semaphore.release();
semaphore.release();
…
semaphore.acquire();
其实 Semaphore 的重点还是 AQS(AbstractQueuedSynchronizer),它只是对 AQS 的封装调整而已。Semaphore 类内部定义了一个抽象静态内部类 Sync
,它负责 Semaphore 的同步实现,Sync 还继承了抽象类 AQS,所以间接使用了 AQS 类的相关方法。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) {
setState(permits);
}
…
}
Semaphore 的构造方法不仅接收许可证数量,还可以接收一个布尔类型的参数 fairness
,代表是否为公平锁模式还是非公平锁模式。
如果为公平锁则可以使最先调用 acquire 方法的线程优先获取许可证,即阻塞队列中排在前面的线程优先获取到许可证(FIFO)。
如果是非公平锁,则不保证线程获得许可证的顺序,即使阻塞队列中有等待的线程,一个新调用 acquire 的线程会先尝试获取许可证,如果许可证数量不满足才插入队列中进行排队。即一个线程 a 调用 acquire 方法尝试获取许可时,这时刚好有线程 b 释放了许可,并唤醒阻塞队列中第一个等待的线程 b,此时线程a 和线程 b 会共同竞争释放出来的许可证,即线程 a 没有进阻塞队列等待就和线程 b 一起竞争许可证了。
注意,FIFO 排序必须适用于这些方法中的特定内部执行点。 因此,一个线程可能在另一个线程之前调用 acquire 方法,但可能在另一个线程之后到达排序点,类似地,从方法返回时也是如此。所以不能绝对认为先调用 acquire 方法的线程一定先获取到许可证。
还有,不计时的 tryAcquire 方法不会遵循公平原则,它也是会获取目前可用的许可证,而不管前面是否有线程在排队。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
NonfairSync
和 FairSync
两个 final 类都是 Sync 的子类,即公平版本和非公平版本。他们都重写了 tryAcquireShared
方法,这个是定义在 AQS 类的方法(方法实现只抛出异常,即要求让实现类重写),
非公平锁模式下尝试获取共享锁的源代码如下,会发现线程直接尝试获取许可,如果许可不够才进入阻塞队列。
// 自旋 + CAS 尝试获取锁
final int nonfairTryAcquireShared(int acquires) {
for (;😉 {
// 可获得的许可证数量
int available = getState();
// 预期剩余的许可证数量
int remaining = available - acquires;
// 如果许可证数量小于0或者cas设置state成功,则退出
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平锁模式下尝试获取共享锁的源代码如下,会发现线程尝试获取许可之前,会先判断队列中是否有线程在等待。
// 自旋 + CAS 尝试获取锁
protected int tryAcquireShared(int acquires) {
for (;😉 {
// 判断等待队列是否有线程
if (hasQueuedPredecessors())
return -1;
// 可获得的许可证数量
int available = getState();
// 预期剩余的许可证数量
int remaining = available - acquires;
// 如果许可证数量小于0或者cas设置state成功,则退出
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
通常情况下,semaphore 被用于控制资源的线程访问数量时,一般初始化为公平锁,这样不会产生饥饿线程。当然,当 semaphore 用于其他类型的同步控制的时候,非公平锁的吞吐量优于公平锁的。
对于释放许可证,源码如下,也是基于 cas 机制进行释放许可证。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
写在最后
为了这次面试,也收集了很多的面试题!
以下是部分面试题截图
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
存中…(img-U36sn6Sg-1712729386234)]
[外链图片转存中…(img-1SmMGeP6-1712729386234)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-VHtiIOnc-1712729386235)]
写在最后
为了这次面试,也收集了很多的面试题!
以下是部分面试题截图
[外链图片转存中…(img-oCrFNE4n-1712729386235)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-5252lhyL-1712729386235)]