前言
记得当年在学习《操作系统》时,对信号量和P,V操作这一块一直搞不太清楚,书中的伪代码着实令人费解(看得懂,就是下笔就忘)。上学时未解决的问题,现在终究还是要解决呀。今天就聊聊java.util.concurrent.Semaphore。
概念
信号量(Semaphores)机制是一种卓有成效的进程同步工具,由荷兰学者Dijkstra提出的(这哥们貌似还提出过图的最短路径算法)。
信号量的值仅能由PV操作来改变。
p操作(wait):申请一个单位资源,进程进入。
v操作(signal):释放一个单位资源,进程出来。
一般来说,信号量S≥0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;若S≤0,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。
通俗来说就是信号量会有一个初始值S(S≥0),这时有线程申请资源,S就会减1(S--);当S==0时,再来申请资源的线程就会阻塞;已申请到资源的进程执行完毕后,会释放资源,S会加1(S++);此时S>0,阻塞的线程就会获取到资源,获取到资源的同时,S会进行减1操作。也就是说,最多只能有S个线程同时占用资源。
Java中的Semaphore
先上一张Semaphore的方法图
由图可以看出在构造方法里会传入一个int类型的参数,
/**
* Creates a {@code Semaphore} with the given number of
* permits and nonfair fairness setting.
*
* @param permits the initial number of permits available.
* This value may be negative, in which case releases
* must occur before any acquires will be granted.
*/
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
由注释可以看出该permits可以为负数,但是在这种情况下释放资源(releases)必须在申请资源(acquires)之前调用。
/**
* Acquires a permit from this semaphore, blocking until one is
* available, or the thread is {@linkplain Thread#interrupt interrupted}.
* 从信号量中获得一个许可,线程阻塞直到获取到许可,或者线程被关闭
* <p>Acquires a permit, if one is available and returns immediately,
* reducing the number of available permits by one.
* 如果有一个许可是可用的,就获取到该许可,并使可用许可数目减少1
* <p>If no permit is available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until
* one of two things happens:
* <ul>
* <li>Some other thread invokes the {@link #release} method for this
* semaphore and the current thread is next to be assigned a permit; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread.
* </ul>
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting
* for a permit,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* @throws InterruptedException if the current thread is interrupted
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* Releases a permit, returning it to the semaphore.
* 释放一个许可,把它交给信号量
* <p>Releases a permit, increasing the number of available permits by
* one. If any threads are trying to acquire a permit, then one is
* selected and given the permit that was just released. That thread
* is (re)enabled for thread scheduling purposes.
*
* <p>There is no requirement that a thread that releases a permit must
* have acquired that permit by calling {@link #acquire}.
* Correct usage of a semaphore is established by programming convention
* in the application.
*/
public void release() {
sync.releaseShared(1);
}
那显而易见,releases()方法和acquires()方法就分别执行释放资源和申请资源的操作了。
代码
下面来用代码来说明信号量(Semaphore)在Java中的使用
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(3);
ExecutorService executorService=Executors.newCachedThreadPool();
for (int i=0;i<20;i++){
executorService.execute(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" 进入!");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(Thread.currentThread().getName()+" 释放!");
}
});
}
executorService.shutdown();
}
20个线程都在请求信号量,但信号量只允许有3个线程进入,所以只有获得信号量的线程结束释放信号量后才能有新的线程进入。如果我们不释放信号呢,就只能打印出3句log了。
pool-1-thread-1 进入!
pool-1-thread-5 进入!
pool-1-thread-2 进入!
控制台打印的结果和我们设想的一致。
应用
Semaphore可以用于流量控制,特别是对并发数有限制的场景。如数据库同时只允许有20个线程访问,就可以使用信号量来实现。