概述
AQS中获取锁和释放锁有两种模式:独占锁和共享锁。两者之间的区别就是共享锁允许多个线程获取同步状态。
注意:waitStatus为0并不只是默认值,而是代表当前节点的下一个节点目前处于活跃状态,只有挂起了才会将当前节点设置为-1。
独占锁
独占锁获取的流程:
1、获取锁失败(tryAcquire返回false)先加入到阻塞队列中
2、循环获取同步状态获取不到就挂起,直到被唤醒抢占头节点并继续执行。
独占锁释放的流程:
1、释放锁成功(tryRelease返回true)
2、判断head节点不为空,并且waitStatus不为0,此时会唤醒阻塞队列中的头节点的下一个节点。
共享锁
共享锁获取的流程:
1、获取失败(tryAcquire返回小于0)先加入到阻塞队列中
2、循环获取同步状态获取不到就挂起,直到被唤醒抢占头节点,并尝试继续唤醒其他被阻塞的节点。
共享锁释放的流程:
1、释放锁成功tryReleaseShared返回true
2、判断head节点不为空,并且waitStatus==-1,尝试唤醒下一个节点
以上是一个父类里面总结了AQS的共享锁和独占锁的获取和释放流程。AQS为每个不同的锁的组件预留了tryAcquire和tryAcquireShared以及tryRelease和tryReleaseShared,实现AQS的子类可以通过重写以上方法来实现对同步状态的控制,进而实现不同锁的行为。
ReentrantLock
可重入锁,独占锁
获取锁
1、判断当前state是否为0,如果是0就可以获取锁,获取锁就是将state置为1,并且记录独占线程。
2、如果state不为0,那么判断独占线程是否为自己,如果是,state+1。
3、否则就需要加入到阻塞队列中并挂起。
释放锁
1、判断当前的线程是否是独占线程,如果不是,抛出异常。
2、将state-1,判断state-1后是否为0,如果是就返回true代表释放锁成功,并且将独占线程清除。
3、否则就是重入锁的原因没有清零,什么都不做。
另外ReetrantLock支持Condition条件队列,这里先不展开了
CountDownLatch
闩锁,共享锁的一种实现
获取锁
1、判断state是否为0,如果是0就返回1>=0代表着获取到锁了。
2、否则就返回-1<-代表获取锁失败,需要将线程放入到阻塞队列中,并且挂起线程。
释放锁
1、将state-1并执行CAS操作,如果state-1不为0那么就什么不做。
2、如果state-1为0,则需要唤醒阻塞队列中所有阻塞的线程。
Semaphore
可以控制并发数,共享锁的一种实现
获取锁
1、将state-1如果小于0那么需要将当前线程挂起。
2、如果大于0,则state-1执行CAS操作,返回还有多少资源可以获取。
释放锁
1、将state+1,执行CAS操作。会返回true,后续唤醒阻塞队列中的线程。
ReentrantReadWriteLock
读锁:
共享锁实现
加锁:每次加锁判断队列中是否有独占锁,如果有,那么需要阻塞,然后判断读模式是否需要阻塞,如果是公平的就是判断队列中是否有元素,如果是不公平的就是判断队列中的头节点的下一个节点是否是独占锁,如果是的话,则有可能会获取锁失败然后放入阻塞队列,否则,则在高16位+1;
释放锁:每次释放锁都在高16位-1,如果变为0,则开始通过共享模式唤醒队列中的Node。
写锁:
独占锁实现
加锁:每次加锁都在判断state是否等于0或者独占线程为空,如果是,那么获取成功,否则,阻塞。
这里不等于0有两种情况:
1、有读锁,需要阻塞
2、没有读锁但是有写锁,需要阻塞
释放锁:每次释放锁都会将独占锁state-1并且判断state的低16位是否为0,如果是则唤醒队列中的下一个节点。