简介:信号量是JUC中重要的一员,常用来做线程同步和通信控制。
I 基础操作:
①初始化
//进行初始化时需要提供一个初始信号量大小
private Semaphore permitTickets = new Semaphore(7);
②获取信号量
//在业务操作前进行获取信号操作,如获取不到则阻塞等待,否则获取到后总信号量数量减一
permitTickets.acquire();
③释放信号量
//业务操作完成后释放信号操作,信号总量加一
permitTickets.release();
④推荐写法
try {
permitTickets.acquire();
// do something
} finally {
if (permitTickets != null) {
permitTickets.release();
}
}
⑤示例如图
II 应用
①生产者消费者场景中,消费者多线程消费控制
- 描述:生产者与消费者问题常使用MQ来处理之间的通信,此时当生产者能力大于消费者时,会造成消息的堆积,此时可采用死信队列延迟少量推送,但终究还是需要加快消息的处理能力,即引入线程和线程池。线程池有几个关键参数:核心线程数、最大线程数、阻塞队列,其能存放的最大线程数量为最大线程数+阻塞队列容量,再次提交时将触发四种拒绝策略之一。
- 存在问题:
- 无法将队列容量设置成较大的准确值
- 如果阻塞队列为无界队列,则大量消息过来时将可能OOM,此时全部消息将直接丢弃
- AbortPolicy,DiscardOledestPolicy、DiscardPolicy拒绝策略触发时导致消息丢弃
- CallerRunsPolicy策略将任务交给主线程将影响主线程继续提交子任务
- 解决方案
使用Semaphore信号量控制线程主线程提交的任务数,信号量初始值大小为最大线程数+阻塞队列容量
private static Semaphore permitTickets = new Semaphore(7);
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));
for (int i = 0;i < 8; i++) {
permitTickets.acquire();
int finalI = i;
threadPoolExecutor.execute(() -> {
try {
System.out.println("【" + finalI + "线程】正在执行!");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
permitTickets.release();
}
});
}
System.out.println("【Main】添加线程完成!");
Thread.sleep(5000);
}