网站建设与推广实训报告,阿里云云服务器官网,网站怎样注册,做网站头部为什么很多代码JUC工具类: Semaphore详解 本文是JUC第二十八讲#xff0c;JUC工具类: Semaphore详解。Semaphore底层是基于AbstractQueuedSynchronizer来实现的。Semaphore称为计数信号量#xff0c;它允许n个任务同时访问某个资源#xff0c;可以将信号量看做是在向外分发使用资源的许可证…JUC工具类: Semaphore详解 本文是JUC第二十八讲JUC工具类: Semaphore详解。Semaphore底层是基于AbstractQueuedSynchronizer来实现的。Semaphore称为计数信号量它允许n个任务同时访问某个资源可以将信号量看做是在向外分发使用资源的许可证只有成功获取许可证才能使用资源。 文章目录 JUC工具类: Semaphore详解1、带着BAT大厂的面试问题去理解2、Semaphore源码分析2.1、类的继承关系2.2、类的内部类2.3、类的内部类 - Sync类2.4、类的内部类 - NonfairSync类2.5、类的内部类 - FairSync类2.6、类的属性2.7、类的构造函数2.8、核心函数分析 - acquire函数2.9、核心函数分析 - release函数 3、Semaphore示例4、更深入理解4.1、单独使用Semaphore是不会使用到AQS的条件队列的4.2、场景问题1、semaphore初始化有10个令牌11个线程同时各调用1次acquire方法会发生什么?2、semaphore初始化有10个令牌一个线程重复调用11次acquire方法会发生什么?3、semaphore初始化有1个令牌1个线程调用一次acquire方法然后调用两次release方法之后另外一个线程调用acquire(2)方法此线程能够获取到足够的令牌并继续运行吗?4、semaphore初始化有2个令牌一个线程调用1次release方法然后一次性获取3个令牌会获取到吗? 5、参考文章 1、带着BAT大厂的面试问题去理解 请带着这些问题继续后文会很大程度上帮助你更好的理解相关知识点。 什么是Semaphore 计数信号量允许n个任务同时访问某个资源Semaphore内部原理Semaphore常用方法有哪些? 如何实现线程同步和互斥的Semaphore适合用在什么场景单独使用Semaphore是不会使用到AQS的条件队列Semaphore中申请令牌(acquire)、释放令牌(release)的实现Semaphore初始化有10个令牌11个线程同时各调用1次acquire方法会发生什么Semaphore初始化有10个令牌一个线程重复调用11次acquire方法会发生什么Semaphore初始化有1个令牌1个线程调用一次acquire方法然后调用两次release方法之后另外一个线程调用acquire(2)方法此线程能够获取到足够的令牌并继续运行吗Semaphore初始化有2个令牌一个线程调用1次release方法然后一次性获取3个令牌会获取到吗
2、Semaphore源码分析
2.1、类的继承关系
public class Semaphore implements java.io.Serializable {}说明: Semaphore实现了Serializable接口即可以进行序列化。
2.2、类的内部类
Semaphore总共有三个内部类并且三个内部类是紧密相关的下面先看三个类的关系。 说明: Semaphore与ReentrantLock的内部类的结构相同类内部总共存在Sync、NonfairSync、FairSync三个类NonfairSync与FairSync类继承自Sync类Sync类继承自AbstractQueuedSynchronizer抽象类。下面逐个进行分析。
2.3、类的内部类 - Sync类
Sync类的源码如下
// 内部类继承自AQS
abstract static class Sync extends AbstractQueuedSynchronizer {// 版本号private static final long serialVersionUID 1192457210091910933L;// 构造函数Sync(int permits) {// 设置状态数setState(permits);}// 获取许可final int getPermits() {return getState();}// 共享模式下非公平策略获取final int nonfairTryAcquireShared(int acquires) {for (;;) { // 无限循环// 获取许可数int available getState();// 剩余的许可int remaining available - acquires;if (remaining 0 ||compareAndSetState(available, remaining)) // 许可小于0或者比较并且设置状态成功return remaining;}}// 共享模式下进行释放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;}}// 根据指定的缩减量减小可用许可的数目final void reducePermits(int reductions) {for (;;) { // 无限循环// 获取许可int current getState();// 可用的许可int next current - reductions;if (next current) // underflowthrow new Error(Permit count underflow);if (compareAndSetState(current, next)) // 比较并进行设置成功return;}}// 获取并返回立即可用的所有许可final int drainPermits() {for (;;) { // 无限循环// 获取许可int current getState();if (current 0 || compareAndSetState(current, 0)) // 许可为0或者比较并设置成功return current;}}
}说明: Sync类的属性相对简单只有一个版本号Sync类存在如下方法和作用如下。 2.4、类的内部类 - NonfairSync类
NonfairSync类继承了Sync类表示采用非公平策略获取资源其只有一个tryAcquireShared方法重写了AQS的该方法其源码如下:
static final class NonfairSync extends Sync {// 版本号private static final long serialVersionUID -2694183684443567898L;// 构造函数NonfairSync(int permits) {super(permits);}// 共享模式下获取protected int tryAcquireShared(int acquires) {return nonfairTryAcquireShared(acquires);}
}说明: 从tryAcquireShared方法的源码可知其会调用父类Sync的nonfairTryAcquireShared方法表示按照非公平策略进行资源的获取。
2.5、类的内部类 - FairSync类
FairSync类继承了Sync类表示采用公平策略获取资源其只有一个tryAcquireShared方法重写了AQS的该方法其源码如下。
protected int tryAcquireShared(int acquires) {for (;;) { // 无限循环if (hasQueuedPredecessors()) // 同步队列中存在其他节点return -1;// 获取许可int available getState();// 剩余的许可int remaining available - acquires;if (remaining 0 ||compareAndSetState(available, remaining)) // 剩余的许可小于0或者比较设置成功return remaining;}
}说明: 从tryAcquireShared方法的源码可知它使用公平策略来获取资源它会判断同步队列中是否存在其他的等待节点。
2.6、类的属性
public class Semaphore implements java.io.Serializable {// 版本号private static final long serialVersionUID -3222578661600680210L;// 属性private final Sync sync;
}说明: Semaphore自身只有两个属性最重要的是sync属性基于Semaphore对象的操作绝大多数都转移到了对sync的操作。
2.7、类的构造函数
Semaphore(int)型构造函数
public Semaphore(int permits) {sync new NonfairSync(permits);
}说明: 该构造函数会创建具有给定的许可数和非公平的公平设置的Semaphore。
Semaphore(int, boolean)型构造函数
public Semaphore(int permits, boolean fair) {sync fair ? new FairSync(permits) : new NonfairSync(permits);
}说明: 该构造函数会创建具有给定的许可数和给定的公平设置的Semaphore。
2.8、核心函数分析 - acquire函数
此方法从信号量获取一个(多个)许可在提供一个许可前一直将线程阻塞或者线程被中断其源码如下
public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}说明: 该方法中将会调用Sync对象的acquireSharedInterruptibly(从AQS继承而来的方法)方法而acquireSharedInterruptibly方法在上一篇CountDownLatch中已经进行了分析在此不再累赘。
最终可以获取大致的方法调用序列(假设使用非公平策略)。如下图所示。 说明: 上图只是给出了大体会调用到的方法和具体的示例可能会有些差别之后会根据具体的示例进行分析。
2.9、核心函数分析 - release函数
此方法释放一个(多个)许可将其返回给信号量源码如下。
public void release() {sync.releaseShared(1);
}说明: 该方法中将会调用Sync对象的releaseShared(从AQS继承而来的方法)方法而releaseShared方法在上一篇CountDownLatch中已经进行了分析在此不再累赘。
最终可以获取大致的方法调用序列(假设使用非公平策略)。如下图所示: 说明: 上图只是给出了大体会调用到的方法和具体的示例可能会有些差别之后会根据具体的示例进行分析。
3、Semaphore示例
下面给出了一个使用Semaphore的示例。
import java.util.concurrent.Semaphore;class MyThread extends Thread {private Semaphore semaphore;public MyThread(String name, Semaphore semaphore) {super(name);this.semaphore semaphore;}public void run() { int count 3;System.out.println(Thread.currentThread().getName() trying to acquire);try {semaphore.acquire(count);System.out.println(Thread.currentThread().getName() acquire successfully);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(count);System.out.println(Thread.currentThread().getName() release successfully);}}
}public class SemaphoreDemo {public final static int SEM_SIZE 10;public static void main(String[] args) {Semaphore semaphore new Semaphore(SEM_SIZE);MyThread t1 new MyThread(t1, semaphore);MyThread t2 new MyThread(t2, semaphore);t1.start();t2.start();int permits 5;System.out.println(Thread.currentThread().getName() trying to acquire);try {semaphore.acquire(permits);System.out.println(Thread.currentThread().getName() acquire successfully);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release();System.out.println(Thread.currentThread().getName() release successfully);} }
}运行结果(某一次):
main trying to acquire
main acquire successfully
t1 trying to acquire
t1 acquire successfully
t2 trying to acquire
t1 release successfully
main release successfully
t2 acquire successfully
t2 release successfully说明: 首先生成一个信号量信号量有10个许可然后maint1t2三个线程获取许可运行根据结果可能存在如下的一种时序。 说明: 如上图所示首先main线程执行acquire操作并且成功获得许可之后t1线程执行acquire操作成功获得许可之后t2执行acquire操作由于此时许可数量不够t2线程将会阻塞直到许可可用。之后t1线程释放许可main线程释放许可此时的许可数量可以满足t2线程的要求所以此时t2线程会成功获得许可运行t2运行完成后释放许可。下面进行详细分析。
main线程执行semaphore.acquire操作。主要的函数调用如下图所示。 说明: 此时可以看到只是AQS的state变为了5main线程并没有被阻塞可以继续运行。
t1线程执行semaphore.acquire操作。主要的函数调用如下图所示。 说明: 此时可以看到只是AQS的state变为了2t1线程并没有被阻塞可以继续运行。
t2线程执行semaphore.acquire操作。主要的函数调用如下图所示。 说明: 此时t2线程获取许可不会成功之后会导致其被禁止运行值得注意的是AQS的state还是为2。
t1执行semaphore.release操作。主要的函数调用如下图所示。 说明: 此时t2线程将会被unpark并且AQS的state为5t2获取cpu资源后可以继续运行。
main线程执行semaphore.release操作。主要的函数调用如下图所示。 说明: 此时t2线程还会被unpark但是不会产生影响此时只要t2线程获得CPU资源就可以运行了。此时AQS的state为10。
t2获取CPU资源继续运行此时t2需要恢复现场回到parkAndCheckInterrupt函数中也是在should继续运行。主要的函数调用如下图所示。 说明: 此时可以看到Sync queue中只有一个结点头节点与尾节点都指向该结点在setHeadAndPropagate的函数中会设置头节点并且会unpark队列中的其他结点。
t2线程执行semaphore.release操作。主要的函数调用如下图所示。 说明: t2线程经过release后此时信号量的许可又变为10个了此时Sync queue中的结点还是没有变化。
4、更深入理解
4.1、单独使用Semaphore是不会使用到AQS的条件队列的
不同于CyclicBarrier和ReentrantLock单独使用Semaphore是不会使用到AQS的条件队列的其实只有进行await操作才会进入条件队列其他的都是在同步队列中只是当前线程会被park。
4.2、场景问题
1、semaphore初始化有10个令牌11个线程同时各调用1次acquire方法会发生什么?
答案拿不到令牌的线程阻塞不会继续往下运行。
2、semaphore初始化有10个令牌一个线程重复调用11次acquire方法会发生什么?
答案线程阻塞不会继续往下运行。可能你会考虑类似于锁的重入的问题很好但是令牌没有重入的概念。你只要调用一次acquire方法就需要有一个令牌才能继续运行。
3、semaphore初始化有1个令牌1个线程调用一次acquire方法然后调用两次release方法之后另外一个线程调用acquire(2)方法此线程能够获取到足够的令牌并继续运行吗?
答案能原因是release方法会添加令牌并不会以初始化的大小为准。
4、semaphore初始化有2个令牌一个线程调用1次release方法然后一次性获取3个令牌会获取到吗?
答案能原因是release会添加令牌并不会以初始化的大小为准。Semaphore中release方法的调用并没有限制要在acquire后调用。
具体示例如下如果不相信的话可以运行一下下面的demo在做实验之前笔者也认为应该是不允许的。。
public class TestSemaphore2 {public static void main(String[] args) {int permitsNum 2;final Semaphore semaphore new Semaphore(permitsNum);try {System.out.println(availablePermits:semaphore.availablePermits(),semaphore.tryAcquire(3,1, TimeUnit.SECONDS):semaphore.tryAcquire(3,1, TimeUnit.SECONDS));semaphore.release();System.out.println(availablePermits:semaphore.availablePermits(),semaphore.tryAcquire(3,1, TimeUnit.SECONDS):semaphore.tryAcquire(3,1, TimeUnit.SECONDS));}catch (Exception e) {}}
}5、参考文章
【JUC】JDK1.8源码分析之SemaphoreSemaphore原理浅析和相关面试题分析