一. 什么是信号量
使用synchronized或lock锁是确保某一区域 "只能有一个线程执行", 而信号量Semaphore是对这一种模式的拓展, 以确保某个区域 "最多只能由N个线程执行", 假设能有使用的资源个数有N个, 而需要这些资源的线程由多余N个, 这就会导致资源竞争, 因此需要 "交通管制", 这种情况下也需要用到计数信号量
二. 常见的方法
Semaphore是java.util.concurrent包下面提供的类
1. 资源允许个数(permits)将通过Semaphore的构造函数来指定:
java.util.concurrent.Semaphore#Semaphore(int)
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
最终会设置一个同步阻塞队列, 队列的长度就是资源允许的个数
java.util.concurrent.locks.AbstractQueuedSynchronizer#setState
protected final void setState(int newState) {
state = newState;
}
2. Semaphore的acquire()方法用于确保存在可用资源, 当存在可用资源时, 线程会立即从acquire()方法放回true, 同时信号量内部的资源个数会减去1, 如果没有可用资源, 线程会阻塞在acquire()方法内, 直到出现可用资源
java.util.concurrent.Semaphore#acquire()
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
3. Semaphore的release()方法会释放资源, 释放资源后, 信号量内部的资源个数会增加1, 另外, 如果acquire()方法中存在等待线程, 那么其中一个线程会被唤醒, 并从acquire()方法返回
java.util.concurrent.Semaphore#release()
public void release() {
sync.releaseShared(1);
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果存在等待线程, 会唤醒它
if (s != null)
LockSupport.unpark(s.thread);
}
三. 示例程序
首先是资源池
/**
* @author canxiusi.yan
* @description BoundedResource
* @date 2022/5/27 11:57
*/
public class BoundedResource {
private final Semaphore semaphore;
private final int permits;
private final static Random R = new Random();
public BoundedResource(int permits) {
this.semaphore = new Semaphore(permits);
this.permits = permits;
}
public void use() throws InterruptedException {
semaphore.acquire();
try {
doUse();
} finally {
semaphore.release();
}
}
private void doUse() throws InterruptedException {
Log.println("begin : used = " + (permits - semaphore.availablePermits()));
Thread.sleep(R.nextInt(500));
Log.println("end : used = " + (permits - semaphore.availablePermits()));
}
public static class Log {
public static void println(String s) {
System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName() + ": " + s);
}
}
}
其次是使用资源的线程
/**
* @author canxiusi.yan
* @description UserThread
* @date 2022/5/27 12:05
*/
public class UserThread extends Thread {
private final static Random R = new Random();
private final BoundedResource boundedResource;
public UserThread(BoundedResource boundedResource) {
this.boundedResource = boundedResource;
}
@Override
public void run() {
try {
while (true) {
boundedResource.use();
Thread.sleep(R.nextInt(3000));
}
} catch (InterruptedException e) {
//
}
}
public static void main(String[] args) {
// 设置3个资源
BoundedResource boundedResource = new BoundedResource(3);
// 10个线程使用
for (int i = 0; i < 10; i++) {
new UserThread(boundedResource).start();
}
}
}
运行结果, 可以看到, 虽然10个线层交替使用资源, 但是同时使用的资源最多只能是3个
如有错误, 请指正, 谢谢!