Semaphore 学习小记
Semaphore是JDK提供的一个同步工具,它通过维护若干个许可证来控制线程对共享资源的访问。
信号量的作用是维护一个 “许可证” 的计数,线程可以获取 “许可证”,那信号量剩余的 “许可证” 减一,线程可以释放一个许可证,那 “许可证” 的数量就加一, 当信号量所拥有的许可证是0 时候,那么下一个获取许可证的线程就需要等待,只能有另外的线程释放了许可证。
Semaphore 信号量的使用流程
1.初始化Semaphore并许可证的数量
- Semaphore(int permits); // 生成指定数量许可证, 排队策略为非公平
- Semaphore(int permits, boolean fair) // 生成指定数量的许可证,并设置排队策略是否为公平的
2.在需要被许可的地方加 acquire() 相关的方法获取许可证
- acquire() // 获取许可证并可以相应中断
- acquireUninterruptibly() // 获取许可证并不可以相应中断
- tryAcquire() // 如果当前有空闲的许可证直接获取,如果没有如果没有我不必阻塞,我可以先做别的事情
- tryAcquire(long timeout, TimeUnit unit) // 和tryAcquire()一样,但多了一个超时间的时间,如在三秒内获取不到,我就做别的事情
3.在任务执行结束的时候调用 release() 方法释放许可证
Semaphore 使用
public class SemaphoreDemo {
// 生成三个许可证
static Semaphore semaphore = new Semaphore(3);
public static void main(String[] args) {
// 定义一个固定数量为 30 个的线程池
ExecutorService executorService = Executors.newFixedThreadPool(30);
for (int i = 1; i <= 30; i++) {
// 生成 30 个任务提交给线程池
executorService.submit(new Task());
}
executorService.shutdown();
}
static class Task implements Runnable {
@Override
public void run() {
try {
// 获取许可证
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 拿到许可证... ");
try {
// 暂定任务耗时 2s
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 释放许可证... ");
// 释放许可证
semaphore.release();
}
}
}
运行结果
Semaphore 信号量的特殊使用
一次性获取或释放多个许可证。比如TaskA会调用很消耗资源的method1(),而TaskB调用的是不太消耗资源的method2(),假设我们一共有5个许可证。那么我们就可以要求TaskA获取5个许可证才能执行而TaskB只需要获取到一个许可证就能执行,这样就避免了A和B同时运行的情况,我们可以根据自己的需求合理分配资源。
Semaphore 信号量使用注意点
- 获取和释放的许可证数量必须一致,否则比如每次都获取2个但是只释放1个甚至不释放,随着时间的推移,到最后许可证数量不够用,会导致程序卡死。(虽然信号量类并不对是否和获取的数量做规定,但是这是我们的编程规范,否则容易出错)
- 在初始化Semaphore 时候公平性设置为 ture 比较合理,要不然会出现线程饥饿的情况
- 并不是必须由获取许可证的线程释放那个许可证,事实上,获取和释放许可证对线程并无要求,也许是A获取了,然后由B释放,只要逻辑合理即可