[超级链接:Java并发学习系列-绪论]
Lock接口在之前的章节中多次提及:
本章主要通过解读Lock接口的源码注释,来学习Lock接口与synchronized关键字的区别与联系。
通过前面章节的学习,我们都知道Lock接口与synchronized关键字都是Java提供的用于对对象进行加锁和解锁的技术,那这两种方式有什么区别和联系呢?先看JDK源码中的注释:
/**
* {@code Lock} implementations provide more extensive locking
* operations than can be obtained using {@code synchronized} methods
* and statements. They allow more flexible structuring, may have
* quite different properties, and may support multiple associated
* {@link Condition} objects.
*
* A lock is a tool for controlling access to a shared resource by
* multiple threads. Commonly, a lock provides exclusive access to a
* shared resource: only one thread at a time can acquire the lock and
* all access to the shared resource requires that the lock be
* acquired first. However, some locks may allow concurrent access to
* a shared resource, such as the read lock of a {@link ReadWriteLock}.
*
*
The use of {@code synchronized} methods or statements provides
* access to the implicit monitor lock associated with every object, but
* forces all lock acquisition and release to occur in a block-structured way:
* when multiple locks are acquired they must be released in the opposite
* order, and all locks must be released in the same lexical scope in which
* they were acquired.
*
*
While the scoping mechanism for {@code synchronized} methods
* and statements makes it much easier to program with monitor locks,
* and helps avoid many common programming errors involving locks,
* there are occasions where you need to work with locks in a more
* flexible way. For example, some algorithms for traversing
* concurrently accessed data structures require the use of
* "hand-over-hand" or "chain locking": you
* acquire the lock of node A, then node B, then release A and acquire
* C, then release B and acquire D and so on. Implementations of the
* {@code Lock} interface enable the use of such techniques by
* allowing a lock to be acquired and released in different scopes,
* and allowing multiple locks to be acquired and released in any
* order.
*
*
With this increased flexibility comes additional
* responsibility. The absence of block-structured locking removes the
* automatic release of locks that occurs with {@code synchronized}
* methods and statements. In most cases, the following idiom
* should be used:
*
*
{@code
* Lock l = ...;
* l.lock();
* try {
* // access the resource protected by this lock
* } finally {
* l.unlock();
* }}
*
* When locking and unlocking occur in different scopes, care must be
* taken to ensure that all code that is executed while the lock is
* held is protected by try-finally or try-catch to ensure that the
* lock is released when necessary.
*
* {@code Lock} implementations provide additional functionality
* over the use of {@code synchronized} methods and statements by
* providing a non-blocking attempt to acquire a lock ({@link
* #tryLock()}), an attempt to acquire the lock that can be
* interrupted ({@link #lockInterruptibly}, and an attempt to acquire
* the lock that can timeout ({@link #tryLock(long, TimeUnit)}).
*
*
A {@code Lock} class can also provide behavior and semantics
* that is quite different from that of the implicit monitor lock,
* such as guaranteed ordering, non-reentrant usage, or deadlock
* detection. If an implementation provides such specialized semantics
* then the implementation must document those semantics.
*
*
Note that {@code Lock} instances are just normal objects and can
* themselves be used as the target in a {@code synchronized} statement.
* Acquiring the
* monitor lock of a {@code Lock} instance has no specified relationship
* with invoking any of the {@link #lock} methods of that instance.
* It is recommended that to avoid confusion you never use {@code Lock}
* instances in this way, except within their own implementation.
*
*
Except where noted, passing a {@code null} value for any
* parameter will result in a {@link NullPointerException} being
* thrown.
*
*
Memory Synchronization
*
* All {@code Lock} implementations must enforce the same
* memory synchronization semantics as provided by the built-in monitor
* lock, as described in
*
* The Java Language Specification (17.4 Memory Model):
*
* - A successful {@code lock} operation has the same memory
* synchronization effects as a successful Lock action.
*
- A successful {@code unlock} operation has the same
* memory synchronization effects as a successful Unlock action.
*
*
* Unsuccessful locking and unlocking operations, and reentrant
* locking/unlocking operations, do not require any memory
* synchronization effects.
*
* @see ReentrantLock
* @see Condition
* @see ReadWriteLock
*
* @since 1.5
* @author Doug Lea
*/
public interface Lock {//...}
上面的注释翻译如下:
{Lock接口的实现}提供了比{synchronized关键字}更加广泛的锁定操作。{Lock接口的实现}允许更加灵活的结构、更多的属性,而且支持与{Condition接口}对象的关联使用。
{Lock接口的实现}是一种用来控制多线程对共享资源的访问权限的工具。通常情况下,{Lock接口}提供对共享资源的独占访问:一次只能有一个线程可以获取锁;所有对共享资源的访问都需要首先获取锁。然而,有些{Lock接口的实现}提供了对共享资源的并发访问,例如:{ReadWriteLock接口}提供的读锁。
通过使用{synchronized关键字}定义的同步方法/代码块提供对每个对象的隐式监视器锁的访问权限,但是强制要求所有对锁的获取和释放以{块结构}的方式进行:当某个线程获取了多个对象锁时,它必须以相反的顺序释放这些锁;并且所有的锁必须在获取它们的语法范围内释放。
虽然同步方法/代码块的作用域机制使得对监视器锁的编程更简易,而且帮助我们避免很多常见的涉及锁操作的编程错误,但是我们有时候需要更加灵活的方式使用锁。
例如:有些并发的穿插访问数据的算法需要使用{手牵手}或者{链锁}方式:你获取了节点A的锁,接着获取了节点B的锁,然后释放了节点A的锁并获取了节点C的锁,然后又释放了节点B的锁并且获得节点D的锁等等。{Lock接口的实现}提供了上述的技术使用:通过允许在不同作用域内获取和释放锁,并且允许以任意的次序获取和释放多个锁。
{Lock接口的实现}提供了更多的灵活性,随之带来的是更多的编程限制。由于没有块结构锁定机制,所以{Lock接口的实现}不需要同步方法/代码块的自动释放锁机制。
在大多数情况下,使用如下的方式进行{Lock接口}的加锁和解锁:
Lock l = ...;
l.lock();//加锁
try {
// access the resource protected by this lock
} finally {
l.unlock();//解锁
}}
当加锁和解锁发生在不同的作用域时,我们必须注意确保所有持有锁的代码被{try-finally}或{try-catch}保护起来,以确保必要时能够释放锁。
相较于{synchronized关键字},{Lock接口的实现}提供了更多的功能:通过{tryLock()方法}提供了一种非阻塞的获取锁的操作、通过{lockInterruptibly()方法}提供了一种可以被中断的锁、通过{tryLock(long, TimeUnit)方法}提供了一种可以超时的锁。
{Lock接口的实现}还可以提供与{隐式监视器锁}完全不同的行为和语义,如:次序保证、不可重入和死锁检测等。
需注意,{Lock接口的实现}的实例只是普通的对象,并且可以将它们作为目标使用在{synchronized关键字}定义的语句中。通过{synchronized关键字}获取{Lock接口的实现}的实例对象的{监视器锁}和调用{Lock接口的实现}的实例对象的加锁方法没有任何关系。当然了,虽然{Lock接口的实现}和{synchronized关键字}互不相干,但是不建议使用这种混搭的加锁方式。
所有的{Lock接口的实现}都必须提供与内置的监视器锁(Java内存模型中定义的)相同的内存同步语义:
上面的注释说了一大堆,有些朋友可能看了也没多大体会,下面我用更加容易理解的方式进行叙述:
1.JDK版本不同
2.读写锁
3.块锁与链锁
4.解锁方式
5.阻塞锁与非阻塞锁
6.可中断锁
7.可超时锁
8.公平锁(线程次序保证)
我们都知道,如果高并发环境下多个线程尝试去访问同一共享资源,同一时刻只有一个线程拥有访问这个共享资源的锁,其他的线程都在等待。
9.互不干扰、可以共用
上面的9条总结就是我对JDK源码注释的理解,其实还有很多可以学习的,例如:可重入锁、死锁检测等等。由于时间和认知有限,就不再多说了,如果有需要,请自行学习。