@@@ 在 Java 5.0 之前,在协调对共享对象的访问时可以使用的机制只有 synchronized 和
volatile 。
@@@ Java 5.0 增加了一种新的机制:ReentrantLock , ReentrantLock 并不是一种替代内置
加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。
》》Lock 与 ReentrantLock
@@@ Lock 接口中定义了一组抽象的加锁操作。与内置加锁机制不同的是,Lock 提供了一种
无条件的 、 可轮询的 、 定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。
在 Lock 的实现中必须提供与内置锁相同的内存可见性语义,但在加锁语义 、 调度算法 、 顺序保证
以及性能特性等方面可以有所不同。
@@@ ReentrantLock 实现了 Lock 接口 ,并提供了与 synchronized 相同的互斥性和内存可见性。
在获取 ReentrantLock 时,有着与进入同步代码块相同的内存语义,在释放 ReentrantLock 时,同样
有着与退出同步代码块相同的内存语义。
@@@ ReentrantLock 支持在 Lock 接口中定义的所有获取锁模式,并且与 synchronized 相比,
它还为处理锁的不可用性问题提供了更高的灵活性。
@@@ 在大多数情况下,内置锁都能很好地工作,但在功能上存在一些局限性。
内置锁必须在获取该锁的代码块中释放,这就简化了编码工作,并且与异常处理操作实现了很好的交互,
但却无法实现非阻塞结构的加锁规则。
@@@ Lock 接口的标准使用形式:必须在 finally 块中释放锁。
如果没有使用 finally 来释放 Lock ,那么相当于启动了一个定时炸弹。当“ 炸弹爆炸 ” 时,就很难追踪到
最初发生错误的位置,因为没有记录已经
### 轮询锁与定时锁
@@@ 可定时的与可轮询的锁获取模式是由 tryLock 方法实现的,与无条件的锁获取模式相比,它具有
更完善的错误恢复机制。
@@@ 在内置锁中,死锁是一个严重的问题,恢复程序的唯一方法是重新启动程序,而防止死锁的唯一
方法就是构造程序时避免出现不一致的锁顺序。
可定时与可轮询的锁提供了另一种选择:避免死锁的发生。
@@@ 如果不能获得所有需要的锁,那么可以使用可定时的或可轮询的锁获取方式,从而使你重新
获得控制权,它会释放已经获得的锁,然后重新尝试获取所有锁(或者至少会将这个失败记录到日志,
并采取其他措施)。
@@@ 在实现具有时间限制的操作时,定时锁非常有用。当在带有时间限制的操作中调用一个阻塞方法
时,它能根据剩余时间来提供一个时限。如果操作不能在指定的时间内给出结果,那么就会使程序提前结束。
当使用内置锁时,在开始请求锁后,这个操作将无法取消,因此内置锁很难实现带有时间限制的操作。
### 可中断的锁获取操作
@@@ 定时的锁获取操作能在带有时间限制的操作中使用独占锁,可中断的锁获取操作同样能在可取消
的操作中使用加锁。
@@@ 可中断的锁获取操作的标准结构比普通的锁获取操作略微复杂一些,因为需要两个 try 块。
(如果在可中断的锁获取操作中抛出了 InterruptedException , 那么可以使用标准的 try - finally 加锁模式)。
### 非块结构的加锁
@@@ 在内置锁中,锁的获取和释放等操作都是基于代码块的---------释放锁的操作总是与获取锁的操作
处于同一个代码块,而不考虑控制权如何退出该代码块。自动的锁释放操作简化了程序的分析,避免了可能
的编码错误,但有时候需要更灵活的加锁机制。
@@@ 降低链表中锁的粒度,即为每个链表节点使用一个独立的锁,使不同的线程能独立地对链表的不同
部分进行操作。
连锁式加锁(Hand-Over-Hand Locking)
锁耦合(Lock Coupling)
》》性能考虑因素
@@@ 当把 ReentrantLock 添加到 Java 5.0 时,它能比内置锁提供更好的竞争性能。
@@@ 锁的实现方式越好,将需要越少的系统调用和上下文切换,并且在共享内存总线上的内存同步
通信量也越少,而一些耗时的操作将占用应用程序的计算资源。
@@@ Java 6 使用了改进后的算法来管理内置锁,与在 ReentrantLock 中使用的算法类似,该算法
有效地提高了可伸缩性。
@@@ 性能与可伸缩性对于具体平台等因素都较为敏感,例如 CPU 、 处理器数量 、 缓存大小以及
JVM 特性等,所有这些因素都可能会随着时间而发生变化。
@@@ 性能是一个不断变化的指标,如果在昨天的测试基准中发现 X 比 Y 更快,那么在今天就可能
已经过时了。
》》公平性
@@@ 在 ReentrantLock 的构造函数中提供了两种公平性选择:创建一个非公平的锁(默认)或
者一个公平的锁。
--------- 在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许
“ 插队 ” : 当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么
这个线程将跳过队列中所有的等待线程并获得这个锁。(在 Semaphore 中同样可以选择采用
公平的或非公平的获取顺序)。
@@@ 当执行加锁操作时,公平性将由于在挂起线程和恢复线程时存在的开销而极大地降低性能。在
实际情况中,统计上的公平性保证---------确保被阻塞的线程能最终获得锁,通常已经够用了,并且实际
开销也小得多。
@@@ 有些算法依赖于公平的排队算法以确保它们的正确性,但这些算法并不常见。在大多数情况下,
非公平锁的性能要高于公平锁的性能。
@@@ 在激烈竞争的情况下,非公平锁的性能要高于公平锁的性能的一个原因是:在恢复一个被挂起的
线程与该线程真正开始运行之间存在严重的延迟。
@@@ 当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁。
@@@ Java 语言规范并没有要求 JVM 以公平的方式来实现内置锁,而在各种 JVM 中也没有这样做。
ReentrantLock 并没有进一步降低锁的公平性,而只是使一些已经存在的内容更明显。
》》在 synchronized 和 ReentrantLock 之间进行选择
@@@ ReentrantLock 在加锁和内存上提供的语义与内置锁相同,此外它还提供一些其他功能,包括
定时的锁等待 、 可中断的锁等待 、 公平性,以及实现非块结构的加锁。
@@@ 内置锁为许多开发人员所熟悉,并且简洁紧凑,而且在许多现有的程序中都已经使用了内置锁
----------- 如果将两种机制混合使用,那么不仅容易令人困惑,也容易发生错误。
@@@ ReentrantLock 的危险性比同步机制要高,如果忘记在 finally 块中调用 unlock ,那么虽然代码
表面上能正常运行,但实际上已经埋下了一颗定时炸弹,并很有可能伤及其他代码。
@@@ 仅当内置锁不能满足需要时,才可以考虑使用 ReentrantLock 。
@@@ 一些内置锁无法满足需求的情况下, ReentrantLock 可以作为一种高级工具。当需要一些高级
功能时才应该使用 ReentrantLock ,这些功能包括:可定时的 、 可轮询的与可中断的锁获取操作,
公平队列 , 以及非结构的锁。否则,还是应该优先使用 synchronized 。
@@@ 线程转储中的加锁能给很多程序员带来帮助。
@@@ ReentrantLock 的非块结构特性仍然意味着,获取锁的操作不能与特定的栈帧关联起来,而内置
锁却可以。
@@@ synchronized 是 JVM 的内置属性,它能执行一些优化。
》》读----写锁
@@@ ReentrantLock 实现了一种标准的互斥锁:每次最多只有一个线程能持有 ReentrantLock 。但对于
维护数据的完整性来说,互斥通常是一种过于强硬的加锁规则,因此也就不必要地限制了并发性。
-------- 互斥是一种保守的加锁策略,虽然可以避免 “ 写 / 写 ” 冲突和 “ 写 / 读 ” 冲突,但同样也避免了
“ 读 / 读 ” 冲突
@@@ 读 / 写锁:一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行。
@@@ 读--写锁是一种性能优化措施,在一些特定的情况下能实现更高的并发性。在实际情况中,对于
多处理器系统上被频繁读取的数据结构,读--写锁能够提高性能。而在其他情况下,读---写锁的性能比独占
锁的性能要略差一些。
@@@ 如果要判断在某种情况下使用读---写锁是否会带来性能提升,最好对程序进行分析。
@@@ 在读取锁和写入锁之间的交互可以采用多种实现方式:
----------- 释放优先
----------- 读线程插队
----------- 重入性
----------- 降级
----------- 升级
在大多数读----写锁实现中并不支持升级,因为如果没有显式的升级操作,那么容易造成死锁。
(如果两个读线程试图同时升级为写入锁,那么二者都不会释放读取锁)
@@@ ReentrantReadWriteLock 为读取锁和写入锁提供了可重入的加锁语义。
ReentrantReadWriteLock 在构造时也可以选择是一个非公平的锁(默认)还是一个公平
的锁。
@@@ ReentrantReadWriteLock 中的写入锁只能有唯一的所有者,并且只能由获取该锁的线程来释放。
@@@ 当锁的持有时间较长并且大部分操作都不会修改被守护的资源时,那么读----写锁能提高并发性。
》》小结
@@@ 与内置锁相比,显式的 Lock 提供了一些扩展功能,在处理锁的不可用性方面有着更高的灵活性,
并且对队列行有着更好的控制。但 ReentrantLock 不能完全替代 synchronized ,只有在 synchronized
无法满足需求时,才应该使用它。
@@@ 读----写锁允许多个读线程并发地访问被保护的对象,当访问以读取操作为主的数据结构时,
它能提高程序的可伸缩性。