ReentrantLock并不是一种代替内置加锁的方法,而是当内置加锁机制不适用时的一种可选择的高级功能。
1. Lock与ReentrantLock
Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所以加锁操作和解锁的方法都是显式的。
public interface Lock{
void lock();
void lockInterruptibly() throws InterruptrdException;
boolean tryLock();
boolean tryLock(long timeout,TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。它还支持在Lock中定义的所有的获取锁的模式,并且与synchronized相比,它还为处理锁的不可用性问题提供了更高的灵活性。
内置锁在功能上的局限性:
无法中断一个正在等待获取锁的线程,或者无法在请求一个锁时无限的等下去...
- 优点:由于内置锁必须在获取该锁的代码块中释放,从而简化了编码工作,并且与异常交互的很好
- 无法实现非阻塞结构的加锁规则。
Lock接口的标准使用方式:
Lock lock=new ReentrantLock();
...
lock.lock();
try{
//更新对象状态
//补货异常,在必要时回复不变性条件
} finally{
lock.unlock;
}
该形式比内置锁复杂一些,必须在finally中释放锁。否则会导致一定的危险。这个也是ReentrantLock不能完全替代synchronized的原因,容易忘记在fianlly中释放锁。
1.1 轮询锁与定时锁
这两种锁的获取模式都是由tryLock方法实现的,具有更完善的错误恢复机制。并且可以避免死锁的发生:如果不能获得所有需要的锁,可以使用定时或者轮询的锁获取方式,从而使你可以重新获得控制权,它会释放已经获得的锁,然后重新尝试获取所有的锁。
当在带有时间限制的操作中调用一个阻塞方法时,她会根据剩余时间来提供一个时限,如果操作不能在相应的时间内给出结果,程序就会提前结束。然鹅内置锁很难实现带有时间限制的操作。
1.2 可中断的锁获取操作
lockInterrutibly方法能够在获得锁的同时保持对中断的响应,并且由于它处于Lock中,因此无需创建其他类型的不可中断阻塞机制。
2. 公平性
在ReentrantLock的构造函数中提供了两种公平性选择:
-
创建一个公平的锁
在公平的锁上,线程将按照它们发出请求的顺序来获得锁。
在公平的锁中,如果有一个线程持有这个锁或者有其他线程在队列中等待这个锁,那么新发出请求的线程将被放入队列中。
适用于持锁时间相对较长或者请求锁的平均时间间隔较长的情况。 -
创建一个非公平的锁
在非公平的锁上,允许插队:当一个线程请求非公平的锁时,如果再发出请求的同时,该锁的状态变为可用,那么该线程将跳过所有的等待线程并获得这个锁。
在非公平的锁中,只有当锁被某个线程持有时,新发出的线程请求才会被放入到队列中。
在大多数情况下,非公平的锁的性能都要高于公平锁的性能。
在竞争激烈的情况下,非公平锁的性能高于公平锁性能的原因之一有:
在恢复一个被挂起的线程与该线程真正被执行之间存在着严重的延迟!
E.G.线程A持有一个锁,并且线程B请求这个锁,因此B被挂起。A释放锁的时候,B被唤醒,并且尝试再次获取这个锁。与此同时,C也在请求这个锁,那么C很有可能在B被完全唤醒之前获得、使用以及释放了这个锁。从而提高了吞吐量呀!
3. 在synchronized和ReentrantLock之间进行选择
内置锁仍具备的优势:
- 为开发人员熟悉,并且简洁紧凑,还广为使用。
- ReentrantLock的危险性高,仅当内置锁不能满足需求时,才会使用ReentrantLock
- 在java5.0中,在线程转储中能给出在哪些调用帧中获得了哪些锁,并能够检测和识别发生死锁的线程。ReentrantLock的非块结构性仍意味着,获取锁的操作不能和特定的帧关联起来。
- 未来更可能提升的是synchronized的性能
4. 读-写锁
一个资源可以被多个读操作访问,或者被一个写操作访问,二者不能同时进行。
public interface ReadWriteLock{
Lock readLock();
Lock writeLock();
}
要读取被ReadWriteLock保护的数据,首先必须获得读取锁,当需要修改由ReadWriteLock保护的数据时,必须先获得写入锁。看上去两个锁是独立的,但是读取锁和写入锁只不过是读写锁对象的不同视图。
ReadWriteLock中一些可选实现:
- **释放优先 : ** 当一个写入操作释放写入锁时,并且队列中同时存在读线程和写线程,那么应该优先选择读线程,写线程,还是最先发出请求的线程?
- **读线程插队: ** 如果锁是由读线程持有,但有写线程正在等待,那么新到达的读线程能否立即获得访问权,还是应该在写线程后面等待?如果允许读线程插入到写线程前面,有助于提高并发性,但是有可能造成写线程发生饥饿问题。
- **重入性 : ** 读操作和写操作是否为可重入的?
- **降级 : ** 如果一个线程持有写入锁,那么它能否在不释放该锁的情况下获取读取锁?这样可能会使得写入锁被降级为读取锁,同时不允许其他写线程修改被保护的资源。
- **升级 : ** 读取锁能否优先于其他正在等待的读线程和写线程而升级为一个写入锁?
ReentrantReadWriteLock
ReentrantReadWriteLock为这两种锁都提供了可重入的加锁定义。并且在其构造函数内可选择一个公平锁或者非公平的锁(默认)。
- 公平的锁中:等待时间最长的线程优先获得锁。如果这个锁被读线程持有,而另一个线程请求写入锁,那么其他读线程都不能获取读取锁,直到写线程使用完并且释放了写入锁。
- 在非公平的锁中:线程获得访问许可的顺序是不确定的。写线程可以降级为读线程,但是读线程不能升级为写线程(导致死锁)