同步器

同步器整体架构

image-20201119032411643

六个同步器

  1. ReentrantLock
  2. Semaphore
  3. CyclicBarrier
  4. CountDownLatch
  5. Phaser
  6. Exchager

ReentrantLock和Synchronized异同

相同点:

  • 临界区保护(提供锁/解锁的能力)
  • 可重入
  • 都提供线程间的协作
    • Synchronized基于Monitor,monitor提供Object.wait(),Object.notify()
    • ReentrantLock基于AQS,提供Condition Object,提供await(), signal()等方法
  • 提供锁升级的逻辑
    • Synchronized:偏向锁->轻量级锁->重量级锁
    • ReentrantLock:CAS竞争->休眠+排队竞争
  • 都提供等待队列
    • Synchronized:monitor:entryList,waitList
    • ReentrantLock:CLH队列

区别点:

  • 基于AQS vs 基于Monitor

  • Java生态 vs 非Java生态

    • Synchronized是非Java的实现
  • 响应线程中断(InterruptException) vs 不响应

  • 提供tryLock vs 不提供

  • 跨Block vs 单Block

    • Synchronized只是一个大括号,是单Block
    • ReentrantLock可以在任何地方触发
  • 可配置公平性 vs 不可配置

Semaphore 信号量

Semaphore的作用

控制并发量

具体的作用

可以实现生产者消费者

ArraryBlockingQueue为什么不用Semaphore实现

条件变量性能更高(Condition),Semaphore本身是用来控制并发量而不是进程间的协作,所以条件变量性能更好

那Semaphore的价值是什么
  1. 抽象价值:降低心智负担
  2. 其他场景:比如流量控制

CyclicBarrier

Barrier叫做屏障,是一种非常重要的同步元语。 Java提供了多种屏障能力,比如CyclicBarrier让线程在一个屏障上等待,然后执行同步快,然后分开执行,然后进入下一个屏障。

img

实例场景

假设有1000w个订单要处理,每个订单数据要获取关联的商品和发货单进行分析。

img

可以考虑每代CyclicBarrier处理1W条订单数据,开10个线程,每个获取1000个商品信息,再开10个线程,每个获取1000个发货单信息,然后利用CyclicBarrier进行一次同步计算。

CyclicBarrier解决了什么问题?

CyclicBarrier提供了一套协作机制,解决的是多个线程间协作(也可以认为是通信),一起处理任务的问题。如果不提供CyclicBarrier我们可以用ReentrantLock+Condition实现类似的能力,但是CyclicBarrier覆盖的场景确实具有通用性,因此抽象成数据结构非常有价值。

CountDownLatch

CountDownLatch的逻辑简单一些,本质上也是一个Barrier,相当于只有一代CyclicBarrier。比如说实现一个返回首页数据的服务,需要请求多个的微服务——A、B、C、D的数据。这个时候可以用CountDownLatch来形成一个同步点。

CountDownLatcher

Phaser

Phaser提供的也是屏障能力,可以把Phaser理解成一种实现屏障能力的框架。 可以用来实现CyclicBarrier和CountDownLatch。后面我们学习TaskForkPoll的时候,也会用到Phaser去实现。

作为框架,最重要的就是思考清楚Barrier这个领域有哪些领域知识,下面我们来总结一下:

  1. 屏障(Barrier):合作的线程们在屏障上等待, 然后进入同步点

  2. 同步点(Synchronization Point),通过屏障后执行的同步代码

    image-20201213144829225
  3. 合作方数量(paties),就是互相等待的线程数量,只有当等待数量达到parties,才会进入同步点

  4. 到来(arrive),代表一个线程到达屏障,并等待,每次有线程到来,arrives + 1

  5. 到达数量(arrives),带到的线程数量

  6. 等待(wait),代表线程在barrier上等待

  7. 进步(advance),一个线程通过屏障,称为进步,代表工作有进度

  8. 开动/下一阶段(tripping/next pharse):到来的线程数量=parties,开始进入同步点

  9. 阶段(phase number):类似CyclicBarrier中的代,每次完成一次开动,phase number加1

Phaser除了抽象出了上面这些概念,还提供了一个更灵活的能力,让parties 可以随时变更。一个线程可以声明自己是一个合作方。

考虑对单个线程,Phaser可以从这几个方面思考:

  • arrive (到达),在屏障上等待其他合作方, 到达线程数(arrives)增1
  • register(注册),相当于声明自己是一个合作方,将parties 增1
  • waitAdvance(等待进步),在屏障上等待其他线程,数量够了就进入同步点
  • deregister(注销),相当于注销自己,parties减1

以上操作有一些是放在一起用的,比如说arriveAndWaitAdvance 方法,相当于增加了phaser number,又同时等待进步。比如说arriveAndDeregister 相当于,到来,但是不等待,并注销自己。

Exchanger

Exchanger帮助我们在两个线程间交换数据。交换数据本身不是一个原子操作。比如交换a和b,需要一个临时变量t:

1
2
3
t = a;
a = b;
b = t;

上面的程序不是一条原子操作,因此线程间如果要交换数据,需要提供一个专门的能力。

同步器解决了什么问题?

其实核心就是一个问题,同步器提供基于同步的线程间协作。 有交换数据,有互相等待,有控制并发量等等。