《Java并发编程实战》-12 第13章 显式锁

第13章 显式锁

13.1 Lock 与 ReentrantLock

使用ReentrantLock来保护对象状态

Lock lock = new ReentrantLock();
...
lcok.lock()
try{
    // 更新对象状态
    // 捕捉异常,并在必要时恢复不变性条件
} finally {
    lock.unlock();
}

13.1.1 轮询锁与定时锁

if (!lock.tryLock(nanosToLock, NANOSECONDS)) 
    return false;
try {
    return sendOnShareLine(message)
} finally {
    lock.unlock()
}

13.1.2 可中断的锁获取操作

正如定时的锁获取操作能在带有事件限制的操作中使用独占锁,可中断的锁获取操作同样能在可取消的操作中使用加锁。

13.1.3 非块结构的加锁

锁分段技术在基于散列的容器中实现了不同的散列链,以便使用不同的锁。

13.2 性能考虑因素

性能时一个不断变化的指标,内置锁和ReentrantLock在各个JDK版本都不一样。

13.3 公平锁

在公平锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平锁上,则允许“插队”:当一个线程请求非公平锁的时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。(在Semaphore中同样可以选择采用公平或非公平的获取顺序。)

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

13.4 在synchronied和ReentrantLock之间进行选择

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

读-写锁

ReadWriteLock中的一些可选实现包括:

  • 释放优先。当一个写入操作释放写入锁时,并且队伍中同时存在读线程和写线程,那么应该优先选择读线程,写线程,还是最先发出请求的线程?
  • 读线程插队。如果锁是由读线程持有,但有写线程正在等待,那么新到达的读线程能否立即获得访问权,还是应该在写线程后面等待?如果允许线程插队到写线程之前,那么将提高并发性,但却可能造成写线程饥饿问题。
  • 重入性。读取锁和写入锁是否是可重入的。
  • 降级。如果一个线程持有写入锁,那么它能否在不释放该锁的情况下获得读取锁?这可能会使得写入锁被“降级”为读取锁,同时不允许其他写线程修改被保护的资源。
  • 升级。读取锁能否优于其他正在等待的读线程和写线程而升级为一个写入锁?在大多数的读-写锁实现中并不支持升级,因为如果没有显式的升级操作,那么很容易造成死锁。(如果两个读线程试图同时升级为写入锁,那么二者都不会释放读取锁。)

总结

与内置锁相比,显式的Lock提供了一些宽展功能,在处理锁的不可用性方面有着更高的灵活性,并且对队列行有着更好的控制。但ReentrantLock不能完全替代synchronized,只有在synchronized无法满足需求时,才应该使用它。
读-写允许多个读线程并发地访问被保护的对象,当访问以读取操作为主的数据结构时,他能提高程序的可伸缩性。