显式锁
Lock与ReentrantLock
1 | public interface Lock { |
与内置加锁机制不同,Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。
Lock的标准使用形式:
1 | Lock lock = new ReentrantLock(); |
必须在finally块中释放锁,否则相当于启动了一个定时炸弹。
轮询锁与定时锁
与无条件的锁获取模式相比,可定时的、可轮询的锁获取模式具有更完善的错误恢复机制,来避免死锁。
如果不能获得所有需要的锁,那么可以使用定时的或可轮询的锁获取方式,从而使你重新获得控制权,它会释放已经获得的锁,然后重新尝试获取所有锁。
1 | public boolean transferMoney(Account from, Account to |
在实现具有时间限制的操作时,定时锁非常有用。当时用内置锁时,在开始请求锁后,这个操作将无法取消。可使用tryLock(timeout, timeunit)
方法来实现。
可中断的锁获取操作
请求内置锁时,无法响应中断。这些不可中断的阻塞机制,将使得实现可取消任务变得复杂。lockInterruptibly()
方法能够在获得锁的同时保持对中断的响应。
非块结构的加锁
连锁式加锁Hand-Over-Hand Locking
锁耦合Lock Coupling
性能考虑因素
性能是个不断变化的指标,如果昨天的测试基准中发现X比Y快,那么在今天就可能已经过时了。
公平性
非公平的锁允许插队:当一个线程请求非公平锁时,如何在发出请求的同时该锁的状态可用,那么这个线程将跳过队列中所有的等待线程,并获得这个锁。
非公平锁的性能高于公平锁。公平性将由于挂起线程和恢复线程时存在的开销而极大降低性能,实际情况下,统计上的公平性保证————确保被阻塞的线程能最终获得锁,已经够用了,并且开销小得多。
在持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁。这种情况下,插队带来的吞吐量提升则可能不会出现。
与默认ReentrantLock一样,内置锁不会提供确定的公平性保证,大多数情况下,实现统计上的公平性保证就已经足够了。
在synchronized和ReentrantLock之间进行选择
在内置锁无法满足需求的情况下,ReentrantLock可作为一种高级工具,如可定时的、可轮询的、可中断的锁获取操作,公平队列,以及非块结构的锁,才是用ReentrantLock。否则还是优先使用synchronized。
读写锁
如果能够放宽互斥的加锁策略,允许多个执行读操作的线程同时访问数据,那么将提升程序的性能。
ReentrantReadWriteLock在构造时,可选择非公平(默认)还是公平锁。写线程降级为读线程是可以的,但从读线程升级为写线程则不可以(会导致死锁)。
当锁的持有时间较长,且大部分操作都不会修改被守护的资源时,那么读写锁能提供并发性。