前言
CountDownLatch、CyclicBarrier、Sempahore,这三个利器都是基于aqs(AbstractQueuedSynchronizer抽象类)来实现的。
AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。
java高并发编程基础三大利器之一Semaphore
概念
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
总结
当信号量Semaphore初始化设置许可证为1 时,它也可以当作互斥锁使用。其中0、1就相当于它的状态,当=1时表示其他线程可以获取,当=0时,排他,即其他线程必须要等待。
Semaphore是JUC包中的一个很简单的工具类,用来实现多线程下对于资源的同一时刻的访问线程数限制
Semaphore中存在一个【许可】的概念,即访问资源之前,先要获得许可,如果当前许可数量为0,那么线程阻塞,直到获得许可
Semaphore内部使用AQS实现,由抽象内部类Sync继承了AQS。因为Semaphore天生就是共享的场景,所以其内部实际上类似于共享锁的实现。
由于共享锁同一时刻可以被多个线程持有,因此当头节点获取到共享锁时,可以立即唤醒后继节点来争锁,而不必等到释放锁的时候。因此,共享锁触发唤醒后继节点的行为可能有两处,一处在当前节点成功获得共享锁后,一处在当前节点释放共享锁后。
semaphore的突刺现象
采用semaphore来进行限流的话会产生突刺现象。
★指在一定时间内的一小段时间内就用完了所有资源,后大部分时间中无资源可用。比如在限流方法中的计算器算法,设置1s内的最大请求数为100,在前100ms已经有了100个请求,则后面900ms将无法处理请求,这就是突刺现象。
公平锁和非公平锁
公平锁
多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,队列中第一个才能获得到锁。
- 优点:等待锁的线程不会饿死,每个线程都可以获取到锁。
- 缺点:整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁
多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
- 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必唤醒所有线程,会减少唤起线程的数量。
- 缺点:处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
Java高并发编程基础三大利器之CountDownLatch
概念
CountDownLatch是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就减1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上(调用await方法的线程)等待的线程就可以恢复工作了。
CountDownLatch 实现原理
CountDownLatch是通过AQS的state字段来实现的一个计数器,计数器的初始值(state的值)为new CountDownLatch设置的数量,每次调用countDown的时候,state的值会进行减1,最后某个线程将state值减为0时,会把调用了await()进行阻塞等待的线程进行唤醒。
CountDownLatch与join()的比较
- CountDownLatch不能重新初始化或者修改CountDownLatch内部计数器的值。
- CountDownLatch和Semaphore在使用AQS的方式上很相似,在同步状态中都是保存的是当前的计数值。
- CountDownLatch的作用就是允许一个或多个线程等待其他线程完成操作,看起来有点类似join() 方法,但其提供了比 join() 更加灵活的API。
- CountDownLatch可以手动控制在n个线程里调用n次countDown()方法使计数器进行减一操作,也可以在一个线程里调用n次执行减一操作。
- join() 的实现原理是不停检查join线程是否存活,如果 join 线程存活则让当前线程永远等待。所以两者之间相对来说还是CountDownLatch使用起来较为灵活。
Java高并发编程基础三大利器之CyclicBarrier
CyclicBarrier对于CountDownLatch的进化
CountDownLatch它有一个缺点,就是它的计数器只能够使用一次,也就是说当计数器(state)减到为 0的时候,如果再有线程调用去 await() 方法,该线程会直接通过,不会再起到等待其他线程执行结果起到同步的作用。为了解决这个问题CyclicBarrier就应运而生了。
概念
它的主要作用其实和CountDownLanch差不多,都是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障会被打开,所有被屏障阻塞的线程才会继续执行,它是可以循环执行的。
为什么CyclicBarrier可以进行循环计数?
CyclicBarrier采用一个内部类Generation来维护当前循环,每一个await方法都会存储当前的generation,获取到相同generation对象的属于同一组,每当count的次数耗尽就会重新new一个Generation并且重新设置count的值为parties(指栅栏开启需要的到达线程总数),表示进入下一次新的循环。
CyclicBarrier与CountDownLanch的比较
- CountDownLanch是为计数器是设置一个值,当多次执行countdown后,计数器减为0的时候所有线程被唤醒,然后CountDownLanch失效,只能够使用一次。
- CyclicBarrier是当count为0时同样唤醒全部线程,同时会重新设置count为parties,重新new一个generation来实现重复利用。
ReentranLock里的公平锁和非公平锁
继承关系
- AbstractQueuedSynchronizer(抽象类)—>被 Sync 继承(ReentranLock里的静态抽象类)
- Sync 被 FairSync 公平锁(ReentranLock内部类) 及 nonFairSync 非公平锁(ReentranLock内部类) 实现
公平锁和非公平锁源码实现上的区别
公平锁的 tryAcquire 在第18行多出了一个条件,即 !hasQueuedPredecessors(),这个方法的目的是判断是否有其他线程比当前线程在同步队列中等待的时间更长。有的话,返回 true,否则返回 false。