1.AQS(抽象队列同步器)
1.1 类图

1.2 Node
两种模式:独占、共享。
waitStatus 有5种:
0 // 默认值
CANCELED= 1 // 线程已被取消
SIGNAL= -1 // 表示后继节点需要被唤醒
CONDITION= -2 // 表示该节点在条件队列等待
PROPAGATE= -3 // 表示下个共享节点aquire时无条件的传播
1.3 CLH

condition.await 时,会将线程加入到等待队列中,并且释放锁,然后将线程挂起。线程被唤醒后会判断线程是否中断并清除中断标记,若signal之前就中断了则抛InterruptedException,否则执行下中断。
condition.signal或signalAll 会将等待队列的头节点或所有节点转移到同步队列中。
1.4 总结思考
1.4.1 为啥同步队列都是从后向前遍历?

由cancelAcquire() 、unparkSuccessor()可知,
因为同步队列中,任何一个节点都有可能被中断,被中断的Node 会被标记成CANCELED。Node的next指针指向自己。并发场景这个Node要去唤醒后继节点,并发被取消了。如果用next 遍历就死循环了,但是pred 倒着遍历不会有问题,因为pred指针一直没变。
2.ReentrantLock(重入锁)
2.1 作用
控制资源竞争。
2.2 核心思想
1. AQS的state 存储重入次数,state=0 时可获取锁.
2. 独占线程重入时,state自增;释放时,state自减。
3. 默认非公平锁,aquire 时:
3.1 非公平,
state=0时,cas获取锁,若获取失败线程会进入同步队列,并挂起、唤醒后继续自旋。
3.2 公平,
state=0时,cas成功且没有前驱节点才获取锁,若获取失败线程会进入同步队列,并挂起、继续自旋。
4. release 时,当state=0,唤醒队头线程,重新竞争。
2.3 核心方法
lock()
unlock()
2.4 图解

3.CountDownLatch(倒数闭锁)
3.1 作用
若线程A依赖线程B和线程C的执行,控制A在B、C 都执行完后再执行A。
3.2 核心思想
1. AQS的state存储需要被countdown次数,。
2. 主线程await()时,若state>0,则主线程挂起。
3. 其他线程downtdown()时,state减1,若state=0,则主线程取消挂起。
3.3 核心方法
await()
countDown()
3.4 图解

4.Semaphore(信号量)
4.1 作用
流量控制。
4.2 核心思想
1. AQS的state 存储许可数,state>0 时可获得许可证。
2. 共享模式,不需要判断独占线程。
3. 默认非公平,获取时:
3.1 非公平,
state>0时,cas获取许可,若获取失败线程会进入同步队列,并挂起、唤醒后继续自旋。
3.2 公平,
state>0时,cas成功且没有前驱节点才获取许可,若获取失败线程会进入同步队列,并挂起、继续自旋。
4. 释放,自旋直到释放成功,唤醒队头线程,重新竞争。
4.3 核心方法
aquire()
release()
4.4 图解

5.ReentrantReadWriteLock(读写锁)
5.1 作用
控制资源竞争。
5.2 核心思想
1. AQS的state 存储读锁和写锁的state。
如 state=.0000 0000 0000 0011 0000 0000 0000 0001
高16位代表读锁state,低16位代表写锁state。
2. 写锁是独占模式,读锁是共享模式。读锁和写锁不能同时拥有。只能拥有一个写锁,或多个读锁。
3. 读锁:
aquire 时,
1. 若有其他线程有写锁,则获取失败。
2.
a.公平:队列里没有前驱节点,且读锁state没有超过最大值,且读锁state cas成功。
b.非公平:队列里没有独占节点,且读锁state没有超过最大值,且读锁state cas成功。
若不满足a/b,则会完整的重试一次aquire。
3. 若是第一次获取读锁,则特殊记录一下线程和该线程的读锁state;否则threadLoacl记录当前线程的读锁state。
4. 获取成功。
release 时,
1. 若是第一次获取读锁,则清空第一次的特殊记录。否则,threadLocal记录的state减一,减完则清空。
2. 自旋直到cas 读锁state-1。
3. 当读锁state=0时,唤醒头结点的线程,重新竞争。
写锁:
aquire时,和reentrant类似,有略微差异,
1. 若有写锁,或者当前线程不是独占线程,则获取失败。
release时,
1. 若是独占线程,且写锁的state=0,则唤醒队列的头结点线程,重新竞争。
5.3 核心方法
rwl.readLock().lock()
rwl.readLock.unlock()
rwl.writeLock.lock()
rwl.writeLock.unlock()
5.4 图解

5.5 总结思考
在读多写少的场景下,读写锁可能会造成写饥饿。JDK1.8 引入了StampedLock,加了个乐观锁,
当读和写冲突时,不是阻塞写,而是重读不阻塞写。
网友评论