前言
前面我们学习了AQS以及基于AQS实现的Du占锁ReentrantLock和基于ReentrantLock实现的同步辅助工具CyclicBarrier,本节我们学习JDK提供的另一个类Semaphore。Semaphore翻译过来就是“信号量”,JDK提供的这个Semaphore被称之为计数信号量。根据Java Doc的描述,Semaphore维护一个许可集或者一些资源,然后可以限制同时访问这一组许可(也可以称作资源)的线程数量。
在本质上,其实我们可以将Semaphore理解为一个“共享锁”,而当Semaphore所维护的许可集或者共享资源只有唯一的一个的时候,它就从“共享锁”退变为ReentrantLock的“Du占锁”,这个时候的信号量也称作“互斥信号量”Mutex。还记得我们在Java并发包核心框架AQS之一同步阻塞与唤醒续一章中自定义共享式同步组件实例时举的例子吗?十个人到只有3窗口柜台的银行办理业务的场景,其实Semaphore就是可以用来解决这种场景的一种共享锁,当然Semaphore的用处不仅仅限于此,我们还可以根据Semaphore限制可以访问某些资源的线程数目的特性,完成很多不同场景的不同需求。
使用示例
首先我们还是从使用示例着手了解Semaphore,毕竟深入了解之前,对Semaphore有个直观的初步了解很有必要。我们就拿之前“十个人到只有3窗口柜台的银行办理业务的场景”来举例说明Semaphore的用法。
public static void main(String[] args) { Semaphore semaphore = new Semaphore (3); for (int i = 0; i < 10 ; i++) { new handleThread(semaphore, "线程"+i).start(); } } static class handleThread extends Thread{ private Semaphore semaphore; public handleThread(Semaphore semaphore, String name) { super(); this.semaphore = semaphore; setName(name); } @Override public void run() { System.out.println(Thread.currentThread().getName() +" 开始等候"); try { semaphore.acquire(); //注意这里获取共享资源失败不应该执行资源释放过程。 } catch (InterruptedException e1) { return; } try { System.out.println(Thread.currentThread().getName() +" 开始办理"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() +" 办理结束"); } catch(Exception e){ e.printStackTrace(); }finally{ semaphore.release(); } } }
通过Semaphore构造方法传入3,设定了这一组共享资源数量为3,然后创建了10个线程,每个线程通过semaphore.acquire()申请一个窗口资源,办理完成semaphore.release()释放窗口资源。可见使用Semaphore很方便的达到了让十个线程共享式访问一组(3个)共享资源的作用。值得注意的是,semaphore.acquire()方法是会抛出中断异常的,切记不能再它抛出异常时去释放共享资源,否则将会出错。
Semaphore源码分析
通过简单的了解,我们知道Semaphore其实就是对共享锁的实现,接下来我们看看其源码实现。首先我们从其类的结构开始:
从类结构可以发现,它的实现方式和ReentrantLock几乎是一样的,也是将对同步器的具体实现代理到了抽象静态内部类Sync,在此基础上分别实现了公平和非公平的模式,唯一的不同在于Semaphore并没有实现Lock接口。所以Semaphore就不能称作是同步锁。接着,我们看看Semaphore除开构造方法之外提供的方法列表,
Semaphore主要的方法就这些了,还有几个返回等待队列状态的方法很简单就不列举了。从列举的方法可以看出,其主要集中对在阻塞式和非阻塞式获取许可的实现。下面我们对主要的方法源码进行分析。
构造方法
public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
从构造方法可以看出,Semaphore默认情况下也是使用的非公平的模式,这是出于非公平排序的吞吐量通常要比公平模式要高的考虑。
非公平获取许可
final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState();//当前可用许可 int remaining = available - acquires; //如果剩下的许可数满足申请的许可数,直接尝试通过CAS获取许可 if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
在Semaphore类的中那四个tryAcquire命名的非阻塞式方法以及非公平模式下的四个阻塞式acquire*方法最终在尝试获取共享资源的时候都是执行的nonfairTryAcquireShared()方法,它的逻辑很简单,如果当前有足够的许可剩余,直接通过CAS获取许可,成功就返回,失败才有走AQS的入队逻辑,阻塞起来等待唤醒。
公平式获取许可
protected int tryAcquireShared(int acquires) { for (;;) { //hasQueuedPredecessors方法很简单,表示查看是否有非当前线程的其他线程正在等待获取共享资源 //如果队列为空,或者当前线程是头节点的后继节点就返回false if (hasQueuedPredecessors()) return -1; int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
不同于非公平式获取许可,公平式获取许可的逻辑和ReentrantLock中获取公平锁类似,在尝试获取共享资源之前需要先通过hasQueuedPredecessors()方法判断当前线程是否是FIFO队列中最有资格获取共享资源的线程,否则进入FIFO队列排队。
drainPermits方法的含义
对于查看许可数量的两个方法,分别是availablePermits()方法和drainPermits()方法,单凭注释可能很难区别它们的含义,特别是drainPermits(),而availablePermits()很好理解,它其实就是返回当前还剩余的许可数量,直接调用的是AQS的getState()方法,返回当前state变量的值,而drainPermits()是什么意思呢?它最终调用的是Semaphore的抽象内部类Sync的drainPermits()方法:
final int drainPermits() { for (;;) { int current = getState(); if (current == 0 || compareAndSetState(current, 0)) return current; } }
通过源码我们发现,当剩余许可数为0时直接返回0;当许可数不为0时,使用CAS操作尝试直接将当前许可数设置为0,直到成功才返回原始剩余的许可数。可见,drainPermits()方法其实就是在还剩有许可的时,立即将剩余许可清0,并返回清0之前还剩余的实际许可数。
内存可见性
Semaphore其实就是对共享锁的一种实现,只是他没有实现Lock接口,所以不能直接称之为“锁”,但是它依然满足happens-before中的锁定规则,即“一个unlock操作先行发生于后面对同一个锁的lock操作”。所以,某个线程对release方法的调用happens-before紧接着的另一个线程对aquire方法的成功调用。也就是说,在某个线程执行release方法之前对共享变量的修改,对另一个紧跟着的线程成功执行aquire方法后是立即可见的。
Semaphore其他应用场景
用Semaphore来实现一个有界容量的List:
public class BoundedList<T> { private final List<T> list; private final Semaphore semaphore; public BoundedList(int bound) { list = Collections.synchronizedList(new LinkedList<T>()); semaphore = new Semaphore(bound); } public boolean add(T obj) throws InterruptedException { semaphore.acquire(); boolean addedFlag = false; try { addedFlag = list.add(obj); } finally { if (!addedFlag) { semaphore.release(); } } return addedFlag; } public boolean remove(Object obj) { boolean removedFlag = list.remove(obj); if (removedFlag) { semaphore.release(); } return removedFlag; } // 其他操作委托给底层的List,这里只列举出一个方法 public T get(int index) { return list.get(index); } // 其他方法…… }
在使用Semaphore时,切记不能再semaphore.acquire()方法抛出异常之后释放资源,所以这里 add(T obj)方法直接将中断异常抛出了方法本身。
相关推荐
Semaphore是计数信号量。Semaphore管理一系列许可证。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有...
Java并发编程Semaphore计数信号量详解 Java并发编程中,Semaphore(信号量)是一种高效的同步机制,允许一定数量的线程访问某个资源。在本文中,我们将详细介绍Java并发编程Semaphore计数信号量的工作原理和应用。 ...
基于 Java 的并发信号量的计数信号量。 安装 通过 npm 安装模块: npm install async-semaphore 快速示例 // fairness false var Semaphore = require ( 'async-semaphore' ) ; var semaphore = new Semaphore ( ...
信号量有两种类型:二进制信号量(Binary Semaphore)和计数信号量(Counting Semaphore)。二进制信号量的值只能为0或1,相当于一个互斥锁,确保同一时间只有一个线程能访问资源。计数信号量的值可以为任何非负整数...
信号量Semaphore,这是一个在多线程编程中至关重要的同步机制,尤其在操作系统设计和并发编程领域,它扮演着协调多个线程对共享资源访问的角色。面试中被问及但回答不出,可能会显示出对并发控制理解的不足,因此,...
在Java中,信号量由`java.util.concurrent.Semaphore`类实现,它提供了两种类型:可重用的二进制信号量和计数信号量。 1. **信号量的原理**: - 信号量是一个整数值,表示可用资源的数量。当线程需要使用资源时,...
它有两种类型:二进制信号量(Binary Semaphore)和计数信号量(Counting Semaphore)。二进制信号量只有0和1两个状态,通常用于互斥访问,而计数信号量可以有任意非负整数值,可以允许多个线程同时访问资源,但不...
Semaphore是Java并发编程中的一种重要工具,它是一个计数信号量,可以用来限制对共享资源的访问权限。在Java的`java.util.concurrent`包中,Semaphore类提供了对多个线程并发访问控制的能力,允许我们设置一个固定的...
11. **Semaphore信号量**:Semaphore用于控制同时访问特定资源的线程数量,通过acquire()获取一个许可,release()归还许可。 12. **CompletableFuture**:Java 8引入的异步编程工具,可以链式调用,组合多个异步...
Semaphore是一种计数信号量,它可以有正整数值,用于管理多个线程对公共资源的访问。当信号量的值为正时,线程可以获取一个许可(或称资源),然后继续执行;当值为零时,线程会被阻塞,直到其他线程释放一个许可。...
虽然Java标准库没有直接提供同步互斥结构,但它提供了基于对象锁的`wait()`和`notify()`方法,这使得我们可以实现Dijkstra的计数信号量概念。 信号量是一种在多线程编程中用于控制资源访问的机制。在Java中,可以...
Java 中的 Semaphore 是一个计数信号量,它维护了一个许可集,并对可用许可的号码进行计数。Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。 Semaphore 的...
在Java中,我们可以使用`java.util.concurrent.Semaphore`类来实现信号量。创建一个信号量时,可以指定初始许可证的数量。以下是一个简单的示例: ```java import java.util.concurrent.Semaphore; public class ...
3. **Semaphore**:信号量,用于限制同时访问特定资源的线程数量。 4. **ExecutorService**:线程池服务,管理线程的生命周期,可以提交任务并控制并发级别。 五、并发编程最佳实践 1. **避免共享可变状态**:尽...
9. **并发工具类**:`CountDownLatch`、`CyclicBarrier`、`Semaphore`等并发工具类在多线程编程中用于协调线程间操作,如计数、同步和信号量控制。 10. **原子变量与原子类**:`java.util.concurrent.atomic`包提供...
计数信号量可以有任意非负的初始值,而二进制信号量则等同于一个简单的互斥锁,其初始值为1,意味着同一时间只能有一个线程访问。 **1. QSemaphore的构造与基本操作** QSemaphore可以通过指定初始计数值来创建,...
在Minix中,信号量分为两种类型:二进制信号量和计数信号量。二进制信号量只有两个状态,0表示资源不可用,1表示资源可用;计数信号量则可以有任意非负整数值,表示资源的数量。 sema.patch 文件可能包含了对Minix...
信号量分为两种类型:二进制信号量(Binary Semaphore)和计数信号量(Counting Semaphore)。二进制信号量只有两个状态,0表示不可用,1表示可用,常用于互斥访问。计数信号量可以有任意非负值,用于管理多个资源...
Semaphore 是一种计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不...
信号量(Semaphore)是一种经典的同步机制,用于控制对共享资源的访问。在易语言中,我们可以利用其提供的信号量API来实现线程间的同步与互斥。信号量值可以用来表示资源的可用数量,当线程试图获取一个资源时,如果...