The Art of Java Concurrency Programming II
第五章 Java中的锁
5.1 Lock接口
1、可以尝试非阻塞获取锁(lockInterruptibly())
2、能被中断地获取锁
3、超时获取锁
5.2 队列同步器(AQS)-面向自定义锁的实现者
1、同步队列
- 设置尾节点为原子方式
- 头节点线程释放同步状态后,唤醒后继结点,由于只有一个线程能够获取到同步状态(当前的头节点),所有设置头节点不需要CAS保证
2、独占式同步状态获取与释放
通过几个死循环的方式保证节点添加的并发请求串行化
节点进入同步队列后,自省观察并获取同步状态,过程中头节点不为空,enq方法保证,第一次enq方法后(enq中会有两次循环),队列状态如下:
入参节点node的前驱是一个新节点,在方法enq中new出,此时新的线程在公平锁的获取流程中,hasQueuedPredecessors()返回ture,后继节点node再继续获取锁的流程中(注意此时图中node的线程并没有返回,因而获取锁过程没有结束),多个线程竞争依靠compareAndSetState的CAS方式保证一致性。释放调用
release(int)
方法进行释放,其中唤醒后继节点3、一个示例,TwinsLock
|
|
测试代码:
5.3 重入锁
1、获取同步状态时,如果该线程与当前持有线程相同,则进行同步状态值增加返回true,表示获取成功
2、公平锁与非公平锁:前者FIFO(获取锁时会检查当前同步队列时候有等待的线程节点,见上文红色),后者CAS竞争,前者代价是进行大量的线程切换(切换锁),非公平锁可能造成饥饿,但是线程切换很少(耗时94倍,切换次数133倍)
5.4 读写锁
1、同一时刻允许多个读线程访问,写线程访问时,读写均阻塞
2、一般情况下,读写锁的性能都会比排它锁好,大多数情景读是多于写的
3、读写锁实现分析:整形按位切割,32位前16位用于标记读状态,后16位标记写状态,写状态=S & 0X0000FFFF,读状态=S>>>16,所以,S不等于0时候,写状态=0则读状态>0,即表示读锁已经获取
4、锁降级:把持住写锁,再获取到读锁,随后释放写锁
5.5-5.6 Condition接口
1、Condition对象:依赖Lock接口实现一套等待通知机制
2、一个示例,有界队列:
3、如果一个线程调用Condition.await(),则其释放锁、构造为节点加入等待队列并进入等待状态
4、等待await:同步队列中的首节点构造为新节点,加入等待队列,等待队列新增节点的线程不需要CAS,因为调用await()的线程必定获取了锁,由锁来保证线程安全
5、通知signal:一起唤醒唤醒等待时间最长的,即首个等待节点,加入同步队列中由locksupport唤醒节点中的线程