这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战
学完AQS,了解到有一些基于AQS的组件,本文在了解读写锁、CountDownLatch、CyclicBarrier、Semaphore的功能后,自己设想一下如何实现这几个功能。
AQS为开发者提供了什么?
一开始的我,看了AQS源码,什么CAS,什么CLH自旋转,什么双向队列,说的贼溜,但是为什么要做呢?我完全不知道。
其实AQS是为了我们开发者提供的,AQS组件让继承者重写以下接口:
- tryAcquire: 返回true表示当前线程获取锁成功,false表示获取锁失败,进入同步队列
- tryRelease: 返回true表示释放锁成功
- tryAcquireShared:与tryAcquire区别是,允许多个线程返回true
- tryReleaseShared:允许多个线程返回true
- isHeldExclusively:当前线程是否占有锁
读写锁设计
读写锁功能是:
- 读允许并发
- 写只能独占
先从简单开始,读写锁,顾名思义,我们先搞两把锁出来:
- 读锁
- 写锁
读允许并发,写只能独占。怎么搞?一开始我的想法是两个标志标示正在读,标示正在写。读标志=true,不让其进行write,写标志=true,不让其进行读。但是问题是释放时无法知道何时将标志设置为false。那就只能退而其次,用两个变量进行计数:
- 读数量
- 写数量
当然写的数量最大=1,也可以使用标志进行表示。
下面就是简单设计一下核心流程了:
怎么允许读线程允许同时进入多个?
AQS tryAcquireShare,读计数通过循环+CAS进行+1操作,返回true表示读锁加成功
当有读线程正在读,此时多个线程加写锁怎么处理?
加写锁的线程进入同步队列,等当前的读计数=0,在唤醒需要加写锁的线程
当有读线程在读,后面有多个写线程加写锁,然后又有线程加读锁,后面的读锁如何处理?
让读线程进入同步队列,等待读线线程前面的写执行完后被唤醒。
从以上场景可以看出,读写锁加锁时需要阻塞的线程是进入同个队列的,所以要用同一个sync的对象进行加锁操作,在抽象出一把读锁,一把写锁提供给使用者。
CountDownLatch
CountDownLatch的效果是,单个线程等待多个线程。
其方法wait可以直接调用sync.accquire(),判断state=0?不为0直接进入阻塞队列。
其方法countDown()可以设计为sync.releaseShare(),当state减少到0时,唤醒阻塞队列。
CyclicBarrier
CyclicBarrier的效果是,每个线程执行wait,然后阻塞等待,知道wait线程的数量达到指定的阈值,执行某个特定的操作。
这个可以通过RetreentLock实现,做一个变量初始化=阈值,每次wait时 通过lock.lock进入,如果变量!=0,则condition.wait(),若达到阈值则执行对应的runnable方法。并唤醒其它线程。
本文转载自: 掘金