前提
- AQS是基础,需先看懂AQS。
Semaphore
- 信号量主要是通过内部类Sync继承AQS类,运用AQS共享队列机制实现的。
- 信号量通过AQS的state进行计数。
- 信号量通过AQS底层共享机制实现,许可与AQS资源的概念基本一致。
- 信号量通过派发许可限制并发的数量,默认是可中断的,也实现了不可中断的接口(这个恰好和AQS相反,AQS默认方法是不可中断的)
- 信号量没有对许可总量进行控制的操作,即可以出现初始化5个许可,再通过release方法“创造”5个许可的情况。因此许可的数量需要使用者自行维护。
内部类Sync
继承了AQS,重写了尝试获取锁的方法。
Sync方法
- int getPermits() 获取剩余有效许可数量:通过AQS的state记录许可数量
final int getPermits() {
return getState();
}
复制代码
- int nonfairTryAcquireShared(int acquires) 非公平获取许可
final int nonfairTryAcquireShared(int acquires) {
for (;;) { // 自旋
int available = getState(); // 获取当前许可数量
int remaining = available - acquires;
if (remaining < 0 || // 许可耗尽
compareAndSetState(available, remaining)) // CAS 控制原子性
return remaining;
}
}
复制代码
- boolean tryReleaseShared(int releases) 归还许可
for (;;) { // 自旋
int current = getState();
int next = current + releases; // 计算出归还后的许可数量
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next)) // 确保原子性
return true;
}
复制代码
- void reducePermits(int reductions) 减少许可数量:该方法不会阻塞
for (;;) { // 自旋
int current = getState();
int next = current - reductions;
if (next > current) // 避免减少的太多,负数溢出...
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
复制代码
- int drainPermits() 获取剩余所有许可
final int drainPermits() {
for (;;) { // 自旋
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
复制代码
内部类NonfairSync
NonfairSync方法
- int tryAcquireShared(int acquires) 尝试非公平方式获取许可
- 直接调用了Sync内部类的nonfairTryAcquireShared方法。
内部类FairSync
FairSync方法
- int tryAcquireShared(int acquires) 尝试获取许可(严格的先到先获取)
for (;;) {
if (hasQueuedPredecessors()) // 等待队列是否有其他线程排在当前线程前面
return -1; // 如果有则不尝试获取许可
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
复制代码
属性
- Sync sync
方法
获取许可方法
- void acquire() 申请1个许可
- void acquireUninterruptibly() 申请不可中断许可
- boolean tryAcquire() 尝试申请许可
- boolean tryAcquire(long timeout, TimeUnit unit) 尝试指定时间内获取许可
- void acquire(int permits) 申请指定数量
- void acquireUninterruptibly(int permits)
- boolean tryAcquire(int permits)
- boolean tryAcquire(int permits, long timeout, TimeUnit unit)
归还许可方法
- void release() 归还许可
- void release(int permits) 归还指定数量许可
其他对许可的操作
- int drainPermits() 获取当前仅剩的所有许可数量
- void reducePermits(int reduction) 强制减少许可数量,acquire会阻塞,这个不会。
- int availablePermits() 获取剩余可申请的许可数量
- boolean isFair() 是否已公平的方式申请许可(先申请先得)
获取队列信息的方法
- boolean hasQueuedThreads() 是否有线程在等待
- int getQueueLength() 获取等待队列长度
- Collection getQueuedThreads() 获取等待资源的线程集合
相关问题
1. 如果线程acquire了一个许可,却release了5个许可,是否程序是否会抛异常?
- 信号量没有维护每个线程获取的许可数和归还的许可数,因此问题提到的操作并不会导致程序因此抛异常,但可能会导致许可变多了的情况。
- 测试程序:
- 开一个for循环创造10个线程,每次只有两个许可派发给线程,每个线程获取1个许可,线程执行完归还5个许可。
- (对照)开一个for循环创造10个线程,每次只有两个许可派发给线程,每个线程获取1个许可,线程执行完归还1个许可。
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
public class SemaphoreTest implements Runnable{
static SemaphoreTest test = new SemaphoreTest();
static Semaphore semaphore = new Semaphore(2);
static CountDownLatch latch; // 通过countDownLatch阻塞主线程到所有循环结束。
Thread mainThread;
@Override
public void run() {
try {
String name = Thread.currentThread().getName();
System.out.println("子线程开始:" + name);
System.out.println("子线程申请许可:" + name);
semaphore.acquire();
Thread.sleep(500);
System.out.println("子线程获得许可:" + name);
semaphore.release(5); // -----------------测试结果:1004毫秒
// semaphore.release(); // --------------测试结果:2504毫秒
System.out.println("子线程释放许可:" + name);
System.out.println("子线程结束:" + name);
latch.countDown(); // 标识线程结束
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
int num = 10;
latch = new CountDownLatch(num);
long begin = (new Date()).getTime();
for (int i = 0; i < num; i++) {
(new Thread(new SemaphoreTest())).start();
}
latch.await(); // 阻塞到for循环所有的线程执行完
long end = (new Date()).getTime();
System.out.println("耗时:" + (end - begin) + "毫秒");
}
}
复制代码
- 结论:线程获取了1个许可,归还了5个许可,后续的并发量的确增大了许多。