版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gyx_2110/article/details/84893513
1. 概述
Semaphore类表示信号量。Semaphore内部主要通过AQS(AbstractQueuedSynchronizer)实现线程的管理。线程在运行时首先获取许可,如果成功,许可数就减1,线程运行,当线程运行结束就释放许可,许可数就加1。如果许可数为0,则获取失败,线程位于AQS的等待队列中,它会被其它释放许可的线程唤醒。
在创建Semaphore对象的时候还可以指定它的公平性。
- 非公平信号量是指在尝试获取许可时,不必关心是否还有需要获取许可的线程位于等待队列中。
- 公平的信号量在获取许可时首先要查看等待队列中是否已有线程,如果有则不能进行获取(即保证了FCFS)。
一般常用非公平的信号量,可以避免线程被CPU选中执行而又被加入等待队列,提高了效率。
2. 源码分析
Semaphore有两个构造函数:
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits)
: new NonfairSync(permits);
}
permits表示许可数,它最后传递给了AQS的state值。
Sync(int permits) {
setState(permits);
}
fair即为表示线程获取信号量是否保证公平性。
可以看到在等待队列中若有线程在等待,FairSync是不会获取信号量的。
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
// The point
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
而NonfairSync则不然:
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);
}
}
这就验证了上文于公平性的讨论。
扫描二维码关注公众号,回复:
4428422 查看本文章
3. 一个例子
public class SemaphoreDemo {
private static Semaphore semp = new Semaphore(5);
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int index = 0; index < 20; index++) {
final int id = index;
Runnable task = new Runnable() {
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println(Thread.currentThread().getName() + " Accessing: " + id + " " + " 当前 等待队列大小:"
+ semp.getQueueLength());
Thread.sleep((long) (Math.random() * 3000));
semp.release();
} catch (InterruptedException e) {
}
}
};
exec.execute(task);
}
exec.shutdown();
}
}
需要注意的一点是,信号量可以由一个线程使用,然后由另一个线程来进行释放,如下所示:
public class UseSemaphore {
private static Semaphore semp = new Semaphore(5);
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
for (int index = 0; index < 20; index++) {
final int id = index;
Runnable task = new Runnable() {
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println(Thread.currentThread().getName() + " Accessing: " + id + " " + " 当前 等待队列大小:"
+ semp.getQueueLength());
// Thread.sleep((long) (Math.random() * 3000));
} catch (InterruptedException e) {
}
}
};
exec.execute(task);
}
while (semp.availablePermits() != 5) {
Thread.sleep(1000);
semp.release();
}
exec.shutdown();
}
}
4. 总结
Semaphore适合用来控制同时并发访问数,通过设置合理的信号量许可数就可以进行有效的控制客户端同时访问的数目。