
想要理解好信号量这个概念你可以从「限制资源」这几个字开始。在并发编程的领域里会有一些限制资源使用的工具比如我们熟悉的Java线程池就是但是我们今天不讲线程池我们讲信号量。信号量也是限制资源使用的一种很好的手段。对于初学并发编程的程序员我特别想说的是在并发编程的领域里限制资源使用是我们的核心命题之一。这个不急着懂等后续你经历的实战项目多了/故障也处理多了自然会对这句话深有体会的。理解信号量大多数文章都从里面的「许可」这个基础概念开始但是我们今天不是先讲这个。因为许可只是基础特性最关键的特性其实是「解耦」。你没有看错就是解耦。什么意思呢?就是说信号量是只认数量不认线程的任何线程都可以往这个计数器里加数或减数信号量本身既不记录、也不关心、更不校验这些操作来自哪个线程。它就是一个共享的公共计数器而已。我们用java 17写段代码来验证这一点。importjava.util.concurrent.Semaphore;publicclassSemaphoreDecouplingDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{// 初始许可数为0强制形成跨线程的释放-获取依赖SemaphoresemaphorenewSemaphore(0);// 线程A只负责消费获取许可ThreadconsumernewThread(()-{try{System.out.println([消费者线程Consumer]尝试获取许可...阻塞等待中);semaphore.acquire();// 阻塞等待直到有许可可用System.out.println([消费者线程Consumer]成功获取许可开始处理任务);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}},Consumer);// 线程B只负责生产释放许可与线程A完全无关ThreadproducernewThread(()-{try{// 模拟异步准备资源的耗时Thread.sleep(500);System.out.println([生产者线程Producer]资源准备完毕释放许可);semaphore.release();// 由另一个线程创建许可}catch(InterruptedExceptione){Thread.currentThread().interrupt();}},Producer);consumer.start();producer.start();// 等待两个线程执行完毕consumer.join();producer.join();}}执行结果如下[消费者线程Consumer]尝试获取许可...阻塞等待中 [生产者线程Producer]资源准备完毕释放许可 [消费者线程Consumer]成功获取许可开始处理任务注意上面的代码consumer线程里只有一个acquire操作而producer线程里只有一个release操作。这就是解耦它并不像ReentrantLock那样得自己lock和unlock也即是ReentrantLock是需要遵守「谁加锁谁解锁」这个规则的。不信的话我们将上面的代码换成ReentrantLock一执行立刻报错代码如下importjava.util.concurrent.locks.ReentrantLock;publicclassLockCouplingDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{ReentrantLocklocknewReentrantLock();// 线程A只负责加锁ThreadlockernewThread(()-{System.out.println([Locker] 尝试获取锁...);lock.lock();System.out.println([Locker] 成功获取锁);},Locker);// 线程B只负责解锁与加锁线程完全无关ThreadunlockernewThread(()-{try{// 等待确保Locker已经持有锁Thread.sleep(500);System.out.println([Unlocker] 尝试释放锁...);lock.unlock();// ⚠️ 跨线程解锁必然抛出异常System.out.println([Unlocker] 成功释放锁);}catch(IllegalMonitorStateExceptione){System.out.println([Unlocker] 释放锁失败了异常是: e.getClass().getSimpleName());System.out.println( 异常信息: e);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}},Unlocker);locker.start();unlocker.start();locker.join();unlocker.join();}}运行的结果如下[Locker] 尝试获取锁... [Locker] 成功获取锁 [Unlocker] 尝试释放锁... [Unlocker] 释放锁失败了异常是: IllegalMonitorStateException 异常信息: java.lang.IllegalMonitorStateException从上面的运行结果你就可以知道unlocker这个线程是无法释放锁的因为这个锁只能有之前加锁的locker线程本人去释放。好理解了信号量这个解耦特性后我们可以来开始说一下「许可」这个基础特性了。所谓「许可」本质上就是信号量内部维护的那个整数计数器。它不代表任何具体的物理资源。获取许可就是让计数器减一释放许可就是让计数器加一当计数器归零时后续的获取操作就会被阻塞直到有新的许可被释放。这个我们上面提到的信号量只认数量不认线程的。信号量的应用是非常广泛的或者信号量的这种思路应用非常的广泛比如我们熟悉的限流功能就是利用这个特性来实现的。比如阿里的限流框架Sentinel其底层原理正是使用Semaphore记录某个资源或API接口当前的并发调用数。当并发数达到设定阈值时直接拒绝新的请求或者走降级逻辑。而像Dubbo/Tomcat等只要涉及到资源限制使用的也都会采用类似的思路。另外在这里我想跟初入程序员这行的新手说一个学习技术的方法。就是你得「一块一块知识去体系化的学习」而不是每次只学习一个点而已。比如说关于java里各种同步工具你要知道是在处理线程安全的问题而像线程饥饿活锁你要知道它是活跃性的问题。。。。。。。按照主题去学习相关的知识点然后再总结和思考和记录长期坚持下去你的技术就会慢慢的进步了。且当你遇到类似主题相关的难题时你的解决思路会宽广很多也会敏捷很多的。技术高手解决问题快正是这个原因加油吧年轻的程序员。