深入理解JVM之synchronized和Lock(六)

在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile。Java5.0增加了一种新的机制:ReentrantLock。与之前提到过的机制相反,ReentrantLock并不是一种替代内置加锁的方法,而是当内置加锁机制不适应时,作为一种可选的高级功能。

Lock与ReentrantLock

与内置加锁机制不同的是,Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。在Lock的实现中必须提供与内部锁相同的内存可见性语义,但在加锁语义、调度算法、顺序保证以及性能特性等方面可以有所不同。

1
2
3
4
5
6
7
8
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}

ReentrantLock实现了Lock接口,并提供了与synchronized相同的排斥性和内存可见性。在获取ReentrantLock时,有着与进入同步代码块相同的内存语义,在释放ReentrantLock时,同样有着与退出同步代码块相同的内存语义。

为什么要创建一种与内置锁如此相似的新加锁机制?在大多数情况下,内置锁都能很好地工作,但在功能上存在一些局限性,例如,无法中断一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限地等待下去。内置锁必须在获取该锁的代码块中释放,这就简化了编码工作,并且与异常处理操作实现了很好的交互,但却无法实现非阻塞结构的加锁规则。这些都是使用synchronized的原因,但在某些情况下,一种更灵活的加锁机制通常能提供更好的活跃性或性能。

Lock锁的使用比使用内置锁复杂一些:必须在finally块中释放锁。否则,如果在被保护的代码中抛出了异常,那么这个锁永远都无法释放。

公平性

ReentrantLock的构造函数中提供了两种公平性选择:创建一个非公平的锁(默认)或者一个公平的锁。在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许“插队”:当一个线程请求非公平锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。
当执行加锁操作时,公平性将由于在挂起线程和恢复线程时存在的开销大大的降低性能。
在激烈竞争的情况下,非公平性锁的性能高于公平锁的性能的原因就是:在恢复一个被挂起的线程与该线程真正开始运行之间存在严重的延迟。而这个空档延迟时间刚好可以被其他线程充分利用,从而提高了吞吐量。

在synchronized和ReentrantLock之间选择

ReentrantLock在加锁和内存上提供的语义与内置锁相同,此外它还停供了一些其他的功能,包括定时的锁等待、可中断的锁等待、公平性,以及实现非块结构的加锁。ReentrantLock在性能上似乎优于内置锁。但内置锁简单,不易出错。

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

读-写锁

ReentrantLock实现了一种标准的互斥锁:每次最多只有一个线程能够持有ReentrantLock。互斥是一种保守的加锁策略,虽然说可以避免“写/写”冲突和“写/读”冲突,但同样避免了“读/读”冲突。
ReadWriteLock中暴露了两个Lock对象,其中一个用于读操作,而另一个用于写操作。要读取由ReadWriteLock保护的数据,必须首先获得读取锁,当需要修改ReadWriteLock保护的数据时,必须首先获得写入锁。

1
2
3
4
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}

在读——写锁实现的加锁策略中,允许多个读操作同时进行,但每次只允许一个写操作。与Lock一样,ReadWriteLock可以采用多种不同的实现方式,这些方式在性能、调度保证、获取优先性、公平性以及加锁语义等方面可能有所不同。

参考

本文内容摘自 Java并发编程实战,与周志明老师的深入理解Java虚拟机一起看最好,会梳理清楚不少关于Java并发中锁的内容,正如文章开头说的,Java虚拟内置同步机制就是synchronized和volatile,前者几乎实现了Java内存模型的所有特性,原子性、可见性、有序性,而后者仅仅实现了内存可见性和有序性,不过这些都是Java内置同步机制,在虚拟机中已经实现了的,而代码级别的就是lock了。

-EOF-