[[explicit-locks]]
== 显示锁

ReentrantLock实现了Lock接口，并提供了与synchronized相同的互斥性和内存可见性。
注: 怎么实现的？又没插入相关指令。

在获取ReentrantLock时，有着与进入同步代码块相同的内存语义，在释放ReentrantLock时，同样有着与退出同步代码块相同的内存语义。

必须在finally块中释放锁。否则，如果在被保护的代码中抛出了异常，那么这个锁永远都无法释放。

它更加“危险”，因为当程序的执行控制离开被保护的代码块时，不会自动清除锁。虽然在finally块中释放锁并不困难，但也可能忘记

可定时的与可轮询的锁获取模式是由tryLock方法实现的，与无条件的锁获取模式相比，它具有更完善的错误恢复机制。

在内置锁中，死锁是一个严重的问题，恢复程序的唯一方法是重新启动程序，而防止死锁的唯一方法就是在构造程序时避免出现不一致的锁顺序。可定时的与可轮询的锁提供了另一种选择：避免死锁的发生。

使用tryLock来获取两个锁，如果不能同时获得，那么就回退并重新尝试。在休眠时间中包括固定部分和随机部分，从而降低发生活锁的可能性。如果在指定时间内不能获得所有需要的锁，那么transferMoney将返回一个失败状态，从而使该操作平缓地失败。

在实现具有时间限制的操作时，定时锁同样非常有用（请参见6.3.7节）。

lockInterruptibly方法能够在获得锁的同时保持对中断的响应，并且由于它包含在Lock中，因此无须创建其他类型的不可中断阻塞机制。

在内置锁中，锁的获取和释放等操作都是基于代码块的—释放锁的操作总是与获取锁的操作处于同一个代码块，而不考虑控制权如何退出该代码块。自动的锁释放操作简化了对程序的分析，避免了可能的编码错误，但有时侯需要更灵活的加锁规则。

连锁式加锁（Hand-Over-Hand Locking）或者锁耦合（Lock Coupling）。

当把ReentrantLock添加到Java 5.0时，它能比内置锁提供更好的竞争性能。

锁的实现方式越好，将需要越少的系统调用和上下文切换，并且在共享内存总线上的内存同步通信量也越少，而一些耗时的操作将占用应用程序的计算资源。

性能是一个不断变化的指标，如果在昨天的测试基准中发现X比Y更快，那么在今天就可能已经过时了。

在ReentrantLock的构造函数中提供了两种公平性选择：创建一个非公平的锁（默认）或者一个公平的锁。在公平的锁上，线程将按照它们发出请求的顺序来获得锁，但在非公平的锁上，则允许“插队”：当一个线程请求非公平的锁时，如果在发出请求的同时该锁的状态变为可用，那么这个线程将跳过队列中所有的等待线程并获得这个锁。

在公平的锁中，如果有另一个线程持有这个锁或者有其他线程在队列中等待这个锁，那么新发出请求的线程将被放入队列中。在非公平的锁中，只有当锁被某个线程持有时，新发出请求的线程才会被放入队列中。

当执行加锁操作时，公平性将由于在挂起线程和恢复线程时存在的开销而极大地降低性能。

在大多数情况下，非公平锁的性能要高于公平锁的性能。

在激烈竞争的情况下，非公平锁的性能高于公平锁的性能的一个原因是：在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。

假设线程A持有一个锁，并且线程B请求这个锁。由于这个锁已被线程A持有，因此B将被挂起。当A释放锁时，B将被唤醒，因此会再次尝试获取锁。与此同时，如果C也请求这个锁，那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样的情况是一种“双赢”的局面：B获得锁的时刻并没有推迟，C更早地获得了锁，并且吞吐量也获得了提高。

当持有锁的时间相对较长，或者请求锁的平均时间间隔较长，那么应该使用公平锁。在这些情况下，“插队”带来的吞吐量提升（当锁处于可用状态时，线程却还处于被唤醒的过程中）则可能不会出现。

ReentrantLock在加锁和内存上提供的语义与与内置锁相同，此外它还提供了一些其他功能，包括定时的锁等待、可中断的锁等待、公平性，以及实现非块结构的加锁。

ReentrantLock的危险性比同步机制要高，如果忘记在finally块中调用unlock，那么虽然代码表面上能正常运行，但实际上已经埋下了一颗定时炸弹，并很有可能伤及其他代码。仅当内置锁不能满足需求时，才可以考虑使用ReentrantLock。

在一些内置锁无法满足需求的情况下，ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock，这些功能包括：可定时的、可轮询的与可中断的锁获取操作，公平队列，以及非块结构的锁。否则，还是应该优先使用synchronized。

在Java 5.0中，内置锁与ReentrantLock相比还有另一个优点：在线程转储中能给出在哪些调用帧中获得了哪些锁，并能够检测和识别发生死锁的线程。JVM并不知道哪些线程持有ReentrantLock，因此在调试使用ReentrantLock的线程的问题时，将起不到帮助作用。Java 6解决了这个问题，它提供了一个管理和调试接口，锁可以通过该接口进行注册，从而与ReentrantLocks相关的加锁信息就能出现在线程转储中，并通过其他的管理接口和调试接口来访问。

未来更可能会提升synchronized而不是ReentrantLock的性能。因为synchronized是JVM的内置属性，它能执行一些优化

ReentrantLock实现了一种标准的互斥锁：每次最多只有一个线程能持有ReentrantLock。

其中大多数访问操作都是读操作。此时，如果能够放宽加锁需求，允许多个执行读操作的线程同时访问数据结构，那么将提升程序的性能。

在读-写锁实现的加锁策略中，允许多个读操作同时进行，但每次只允许一个写操作。

与Lock一样，ReadWriteLock可以采用多种不同的实现方式，这些方式在性能、调度保证、获取优先性、公平性以及加锁语义等方面可能有所不同。

读-写锁是一种性能优化措施，在一些特定的情况下能实现更高的并发性。在实际情况中，对于在多处理器系统上被频繁读取的数据结构，读-写锁能够提高性能。

而在其他情况下，读-写锁的性能比独占锁的性能要略差一些，这是因为它们的复杂性更高。如果要判断在某种情况下使用读-写锁是否会带来性能提升，最好对程序进行分析。由于ReadWriteLock使用Lock来实现锁的读-写部分，因此如果分析结果表明读-写锁没有提高性能，那么可以很容易地将读-写锁换为独占锁。

释放优先。

当一个写入操作释放写入锁时，并且队列中同时存在读线程和写线程，那么应该优先选择读线程，写线程，还是最先发出请求的线程？

读线程插队。如果锁是由读线程持有，但有写线程正在等待，那么新到达的读线程能否立即获得访问权，还是应该在写线程后面等待？如果允许读线程插队到写线程之前，那么将提高并发性，但却可能造成写线程发生饥饿问题。

重入性。读取锁和写入锁是否是可重入的？

降级。如果一个线程持有写入锁，那么它能否在不释放该锁的情况下获得读取锁？这可能会使得写入锁被“降级”为读取锁，同时不允许其他写线程修改被保护的资源。

升级。读取锁能否优先于其他正在等待的读线程和写线程而升级为一个写入锁？在大多数的读-写锁实现中并不支持升级，因为如果没有显式的升级操作，那么很容易造成死锁。

（如果两个读线程试图同时升级为写入锁，那么二者都不会释放读取锁。）

在公平的锁中，等待时间最长的线程将优先获得锁。如果这个锁由读线程持有，而另一个线程请求写入锁，那么其他读线程都不能获得读取锁，直到写线程使用完并且释放了写入锁。在非公平的锁中，线程获得访问许可的顺序是不确定的。写线程降级为读线程是可以的，但从读线程升级为写线程则是不可以的（这样做会导致死锁）。

ReentrantReadWriteLock中的写入锁只能有唯一的所有者，并且只能由获得该锁的线程来释放。

在Java 6中修改了这个行为：记录哪些线程已经获得了读者锁。

当锁的持有时间较长并且大部分操作都不会修改被守护的资源时，那么读-写锁能提高并发性。
