Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
一、方法摘要
- Semaphore(int permits):构造方法,创建具有给定的许可数和非公平锁的Semaphore。
- Semaphore(int permits, Boolean fair):构造方法,创建具有给定许可数和给定公平设置的Semaphore。
- void acquire():从此信号量获取一个许可,在提供一个许可前将阻塞,否则线程被中断。
- void acquire(int permits):从此信号量获取给定数目的许可,在提供这些许可前阻塞,或者线程已被中断。
- void acquireUninterruptibly():获取许可,无视中断。
- Boolean tryAcquire():仅在调用此信号量时存在一个许可,才从信号量获取许可。
- void release():释放一个许可,将其返回给信号量。
二、使用方法
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors
.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
s.acquire(); //获取许可
System.out.println("save data");
s.release(); //释放许可
} catch (InterruptedException e) {
}
}
});
}
threadPool.shutdown();
}
}
在代码中,虽然有30个线程在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证。还可以用tryAcquire()方法尝试获取许可证。
三、实现原理
结构类图
信号量内含了两个队列同步器,分别是公平的和非公平的。通过队列同步器来控制线程同步。
构造函数
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Semaphore内置了两个队列同步器,分别为公平的和非公平的。根据构造参数的不同,决定实现哪一个同步器。
acquire 方法
acquire()方法用来获取许可,这里的许可其实关联到队列同步器AQS的状态state。源码如下:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1); //获取共享锁
}
AQS中,该方法的实现为:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) //尝试获取同步
doAcquireSharedInterruptibly(arg); //尝试失败 则 继续等待
}
该方法为模板方法,需要对tryAcquireShared方法进行重写。对于非公平锁,源码如下:
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires); //调用了父类方法
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState(); //获取状态
int remaining = available - acquires; //更新状态
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining; //返回新的状态值
}
}
对于公平锁,则需要加上判断,该线程是否为同步队列的头结点才可。acquire本质上是交出同步器的state给线程。当state消耗完时(<=0)就无法再获得共享锁。
release 方法
release方法用来归还信号量。源码如下:
public void release() {
sync.releaseShared(1); //调用同步器的释放共享锁
}
该方法调用了同步器的模板方法:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { //尝试释放锁
doReleaseShared();
return true;
}
return false;
}
在Semaphore中,重写了tryReleaseShared方法:
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState(); //获取state值
int next = current + releases; //归还信号量
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next)) //更新state
return true;
}
}