介绍了ReentrantLock与CountDownLatch,今天介绍第三个AQS下的并发工具类。
主要作用
先说一下Semaphore的作用吧,比喻一下就像火锅店店里面只有那么多的座位,进去一个消耗一个座位,坐满了其他人就只能在外面等着,有人出来了那么就有一个人可以进去。
就好像ReentrantLock只允许一个线程进去,而Semaphore最多只允许n个线程进去。
主要方法
有两个构造方法“Semaphore(int permits)“、”Semaphore(int permits, boolean fair)”参数permits设置最大许可数,fair表示是否公平,和ReentrantLock一样可以创建公平与非公平获取资源方式。
acquire()、acquire(int permits)方法是获取许可,无参的是获取1,也就是AQS的state-1,也可以state-permits,计算的结果小于0则会阻塞线程。
tryAcquire()、tryAcquire(int permits)会直接返回获取的结果,和ReentrantLock的tryAcquire一样。
tryAcquire(long timeout, TimeUnit unit)会尝试一段时间,如果这段时间都没有获取会返回失败。
release()、release(int permits)释放许可,释放后会唤醒其他等待线程。
还有一些其他的次要的方法比如acquireUninterruptibly(int permits)在获取许可的时候不响应线程的中断信号,比较类似或简单这里就不再赘述了。
Semaphore与AQS
Semaphore与ReentrantLock一样中有三个AQS的子类Sync、NonfairSync、FairSync,最主要在于Sync中一些方法的实现,主要方法如下图:
Semaphore与ReentrantLock和CountDownLatch实现的AQS不同点在于这几个方法的实现中都用了for循环,因为Semaphore的获取、释放资源会有多次,而ReentrantLock要么从0变到1,然后其他线程就会阻塞,而Semaphore则不一样,不再是从0到1或者1到0的变化了。
比如一个非公平的Semaphore的许可设置为10。有可能多个线程会同时调用acquire()方法,最终都会调用nonfairTryAcquireShared方法去判断是否获取许可成功。当两个线程进来拿到state都是10,然后两个线程去compareAndSetState更新state的值,其中一个更新成功那么另外一个就会失败,然后就不得不重新计算state去设置。要安全的修改state或者说一个变量如果不用锁就必须采用这种for循环和compareAndSetState组合的方式。
两个子类NonfairSync、FairSync只是对tryAcquireShared有不同的实现,NonfairSync的是直接调用Sync的nonfairTryAcquireShared,而FairSync则要先判断AQS中阻塞链表是否有数据,有的话返回-1,没有才回去尝试修改state,他们两个的区别就在于NonfairSync可能出现插队获取许可,而FairSync必须根据顺序来获取。
acquire过程
已经讲了ReentrantLock、CountDownLatch对state状态的维护和对线程的阻塞,却没有梳理一个具体的过程,这次结合Semaphore的acquire来看下state状态的变化与线程的阻塞关系。
1、首先acquire(n)方法会调用属性sync的acquireSharedInterruptibly(n)方法,这个方法AQS的模板方法;
2、AQS的acquireSharedInterruptibly(n)会调用tryAcquireShared(n)方法,tryAcquireShared(n)需要具体的子类实现,目的就是对state尝试修改,无论修改是否成功都会返回计算后的值,公平锁可能直接返回-1。
3、如果tryAcquireShared(n)返回值大于等于0表示获取成功,那么acquire(n)方法执行结束,线程继续执行。
4、如果tryAcquireShared(n)返回值小于0表示获取失败,则会调用AQS的doAcquireSharedInterruptibly(n)方法。
5、doAcquireSharedInterruptibly(n)会创建一个节点并拼接到AQS链表的末尾,然后就是for循环验证节点的前一个节点是否是头节点,如果不是则阻塞线程。
6、如果是则再次调用tryAcquireShared(n)返回大于0则方法结束那么acquire(n)方法执行结束,线程继续执行。否则阻塞线程。
7、当其他线程释放时会唤醒线程并且继续for循环再次进行上两步的验证,直到成功。
总结
Semaphore与ReentrantLock比较类似,只不过ReentrantLock一个时刻只运行一个线程通过,而Semaphore的acquire则可以允许最多指定数量线程通过。
而Semaphore与CountDownLatch的不同点在于CountDownLatch在state变为0的时候就不再起阻塞作用了,而Semaphore还可以通过释放state继续起作用。
由于state存在多个线程同时修改,所以采用for循环与compareAndSetState组合使用实现state的线程安全。
Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!
领取专属 10元无门槛券
私享最新 技术干货