总文档 :文章目录
Github : github.com/black-ant
锁是整个多线程中出现最频繁的概念之一 , 我们在之前细说 synchronized 中曾经简单说了一下 , 这里我们试着完善整个体系 , 来说说其他的几个类 .
一 . Lock 接口
Lock 接口是一切的基础 , 它抽象类一种用于控制多个线程对共享资源的访问的工具 .
> 提供了以下方法用于抽象整个业务 :
- void lock()
- void lockInterruptibly() throws InterruptedException : 打断锁
- boolean tryLock() : 非阻塞尝试获取一个锁
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException : 带时间尝试
- void unlock()
- Condition newCondition()
> Lock 接口提供了区别于隐式监视锁更多的功能 :
- 保证排序
- 不可重入使用
- 死锁检测
- 本身可以作为同步语句中的目标
- 获取锁实例的监视器锁与调用该实例的任何lock()方法没有指定的关系
> 内存同步 :
- 成功的锁操作与成功的锁操作具有相同的内存同步效果。
- 成功的解锁操作与成功的解锁操作具有相同的内存同步效果。
- 不成功的锁定和解锁操作,以及可重入的锁定/解锁操作,不需要任何内存同步效果。
二 . ReentranLock
2.1 ReentranLock 入门
ReentranLock 即重入锁 , 表示在单个线程内,这个锁可以反复进入,也就是说,一个线程可以连续两次获得同一把锁 .
ReentranLock 比 synchronized 提供更具拓展行的锁操作。它允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:
- 可以使锁更公平。
- 递归无阻塞的同步机制。
- 可以使线程在等待锁的时候响应中断。
- 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间。
- 可以在不同的范围,以不同的顺序获取和释放锁。
它的特点有:
- 可重入互斥锁
- 同时提供公平方式和非公平方式
- 公平锁 : 公平锁的锁获取是有顺序的
ReentranLock 基本使用
1 | java复制代码private Lock lock = new ReentrantLock(); |
2.2 内部重要类
2.2.1 Sync
Sync 是 ReentranLock 的内部抽象类 , 其后续会用来实现两种不同的锁 , 这里先看看Sync 内部做了什么
Node 1 : 继承于AbstractQueuedSynchronizer
又名 AQS , 这些大家就知道它了 , Sync 使用aqs state 来表示锁上的持有数
1 | java复制代码abstract static class Sync extends AbstractQueuedSynchronizer |
Node 2 : 有一个抽象方法 lock , 后续的公平和非公平会分别实现对应的方法
1 | java复制代码abstract void lock(); |
Node 3 : nonfairTryAcquire 方法干了什么
1 | java复制代码 |
Node 3 : tryRelease 释放
1 | java复制代码 |
2.3 synchronized 和 ReentrantLock 异同
相同点
- 都实现了多线程同步和内存可见性语义 (隐式监视器锁定)。
- 都是可重入锁
不同点
- 同步实现机制不同
- synchronized 通过 Java 对象头锁标记和 Monitor 对象实现同步。
- ReentrantLock 通过CAS、AQS(AbstractQueuedSynchronizer)和 LockSupport(用于阻塞和解除阻塞)实现同步。
- 可见性实现机制不同
- synchronized 依赖 JVM 内存模型保证包含共享变量的多线程内存可见性。
- ReentrantLock 通过 AQS 的 volatile state 保证包含共享变量的多线程内存可见性。
- 使用方式不同
- synchronized 可以修饰实例方法(锁住实例对象)、静态方法(锁住类对象)、代码块(显示指定锁对象)。
- ReentrantLock 显示调用 tryLock 和 lock 方法,需要在 finally 块中释放锁。
- 功能丰富程度不同
- synchronized 不可设置等待时间、不可被中断(interrupted)。
- ReentrantLock 提供有限时间等候锁(设置过期时间)、可中断锁(lockInterruptibly)、condition(提供 await、condition(提供 await、signal 等方法)等丰富功能
- 锁类型不同
- synchronized 只支持非公平锁。
- ReentrantLock 提供公平锁和非公平锁实现。当然,在大部分情况下,非公平锁是高效的选择。
总结 :
在 synchronized 优化以前,它的性能是比 ReenTrantLock 差很多的,但是自从 synchronized 引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了 .
在两种方法都可用的情况下,官方甚至建议使用 synchronized 。
并且,实际代码实战中,可能的优化场景是,通过读写分离,进一步性能的提升,所以使用 ReentrantReadWriteLock
2.3 ReentrantLock 深入
1 | java复制代码// 常用方法 : |
三 . ReadWriteLock
读写锁是用来提升并发程序性能的锁分离技术的 Lock 实现类。可以用于 “多读少写” 的场景,读写锁支持多个读操作并发执行,写操作只能由一个线程来操作。
ReadWriteLock 对向数据结构相对不频繁地写入,但是有多个任务要经常读取这个数据结构的这类情况进行了优化。
ReadWriteLock 使得你可以同时有多个读取者,只要它们都不试图写入即可。如果写锁已经被其他任务持有,那么任何读取者都不能访问,直至这个写锁被释放为止。
ReadWriteLock 对程序性能的提高主要受制于如下几个因素:
- 数据被读取的频率与被修改的频率相比较的结果。
- 读取和写入的时间
- 有多少线程竞争
- 是否在多处理机器上运行
特征 :
- 公平性:支持公平性和非公平性。
- 重入性:支持重入。读写锁最多支持 65535 个递归写入锁和 65535 个递归读取锁。
- 锁降级:遵循获取写锁,再获取读锁,最后释放写锁的次序,如此写锁能够降级成为读锁。
深入 ReadWriteLock :
1 | java复制代码ReadWriteLock 是一个接口 , 它仅仅提供了2个方法 : |
四 . ReentrantReadWriteLock
重入锁 ReentrantLock 是排他锁,排他锁在同一时刻仅有一个线程可以进行访问 , ReentrantReadWriteLock 则是可重入的读写锁实现类 , 只要没有线程 writer , 读取锁可以由多个 Reader 线程同时保持
I- ReadWriteLock
M- Lock readLock();
M- Lock writeLock();
C- ReentrantReadWriteLock : 可重入的读写锁实现类
I- ReadWriteLock
?- 内部维护了一对相关的锁,一个用于只读操作,另一个用于写入操作 , 写锁是独占的,读锁是共享的
4.1 ReentrantReadWriteLock 深入
使用案例 :
1 | java复制代码 Object data; |
Node 1 : 内部提供了2个内部属性 , 这也就是为什么能做到独写锁分离
1 | java复制代码// 读锁 |
Node 2 : 再次出现的 Sync , 老规矩 , Sync 还是通过 fair 去判断创建
1 | java复制代码final Sync sync; |
Node 3 : Sync 内部状态控制
1 | java复制代码// 读取和写入计数提取常量和函数。Lock state在逻辑上分为两个 : |
Node 4 : HoldCounter 类的作用 : 每个读线程需要单独计数用于重入
1 | java复制代码// 每个线程读取保持计数的计数器。作为ThreadLocal维护 , 缓存在cachedHoldCounter |
Node 5 : ThreadLocalHoldCounter , 为了反序列化机制
1 | java复制代码static final class ThreadLocalHoldCounter |
Node 6 : Sync 内部类
1 | java复制代码NonfairSync : 不公平锁 |
五 . Condition
5.1 Condition 简介
1 | java复制代码在 Java SE 5 后,Java 提供了 Lock 接口,相对于 synchronized 而言,Lock 提供了条件 Condition ,对线程的等待、唤醒操作更加详细和灵活 |
5.2 Condition 流程
5.3 Condition 源码
Condition 其实是一个接口 , 其在 AQS 中存在一个是实现类 , ConditionObject , 我们就主要说说它 :
Node 1 : 属性对象
1 | java复制代码// condition queue 第一个节点 |
Node 2 : 核心方法 doSignal + doSignalAll
1 | java复制代码 |
Node 3 : 主要方法 await
1 | java复制代码public final void await() throws InterruptedException { |
Node 4 : Release 方法
1 | java复制代码 // 使用当前状态值调用release;返回保存的状态。 |
Node 5 : 其他方法 :
- 方法 addConditionWaiter : 增加了一个新的服务员等待队列。
- awaitUninterruptibly :实现不可中断条件等待
1 | java复制代码public final void awaitUninterruptibly() { |
总结
写到这里 , 多线程系列准备告一段落了 , 其实整个系列写了一半不到 , 还远远没完 . 但是最近拜读了Java并发编程的艺术后 ,感觉自己对多线程的理解还远远不够 , 所以决定花一段时间再深读一下 , 从而充实整个文档 .
参考及感谢
本文转载自: 掘金