ReentrantLock

Lock接口

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

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

大多情况下,内置锁都能很好地工作,但在功能上存在一些局限性,例如,无法中断一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限地等待下去.内置锁必须在获取该锁的代码块中释放,这就简化了编码工作,并且与异常处理操作实现了油耗的交互,但却无法试下非阻塞结构的加锁规则.

Lock接口库的标准使用形式:

1
2
3
4
5
6
7
8
9
10
Lock lock = new ReentrantLock();
...
lock.lock();
try {
// 更新对象状态
// 捕获异常
} finally {
// 必须在finally中释放锁
lock.unlock();
}

ReentrantLock锁特性

轮询锁与定时锁

可定时的与可轮询的锁模式都是由tryLock方法实现的,与无条件的锁获取模式相比,它具有更完善的错误恢复机制.在内置锁中,死锁是一个严重的问题,恢复程序的唯一方法是重启应用程序,而防止死锁的唯一方法是在构造程序时避免出现不一致的锁顺序.可定时的与可轮询的锁提供了避免死锁发生的方案.

如果不能获取所有需要的锁,那么可以使用可定时的或可轮询的锁获取方式,从而使程序重新获得控制权.它会释放已经获得的锁,然后重新尝试获取所有锁.

在实现就有时间限制的操作时,定时锁同样非常有用.当在带有时间限制的操作中调用了一个阻塞方法时,它能根据剩余时间来提供一个时限.如果操作不能在指定的时间内给出结果,那么就会使程序提前结束.当使用内置锁时,在开始请求锁后,这个操作将无法取消,因此内置锁很难实现带有时间限制的操作.

1
2
3
4
5
6
7
8
9
10
public boolean trySendOnSharedLine(String message, long timeout, TimeUnit unit) throws InterruptException {
if (!lock.tryLock(nanosToLock, NANOSECONDS)) {
return false;
}
try {
return sendOnSharedLine(message);
}finally {
lock.unlock();
}
}

可中断的锁获取操作

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

1
2
3
4
5
6
7
8
9
10
11
12
public boolean sendOnShareLine(String message) throws Interruption {
lock.lockInterruptibly();
try {
return cancellableSendOnSharedLine(message);
} finally {
lock.unlock();
}
}

private boolean cancellableSendSharedLine(String message) throws InterruptedException {
...
}

非块结构的加锁

在内置锁中,锁的获取和释放等操作都是基于代码块的,释放锁的操作总是与获取锁的操作处于同一个代码块,而不考虑控制权如何退出该代码块.内置锁的自动释放操作简化了对程序的分析,避免了可能的编码错误,但有时候需要更灵活的加锁方式.

公平性

在ReentrantLock的构造函数中提供了公平性选择.

公屏是一种好的行为,而不公平则是一种不好的行为,但是当执行加锁操作时,公平性将由于在挂起线程和恢复线程时存在的开销极大的降低性能.在实际情况中,统计学上的公平性保证:确保被阻塞的线程能最终获得锁,通常已经够用了,并且实际开销也很小.

性能考虑因素

ReentrantLock在Java5.0比内置锁提供更好的竞争性能.
在Java6中,内置锁与ReentrantLock之间的性能差异减小.

在synchronized和ReentrantLock之间进行选择

ReentrantLock在加锁he内存上提供的语义与内置锁相同,此外它还提供了一些其他功能.ReentrantLock在性能上似乎优于内置锁.

与ReentrantLock相比,内置锁仍然具有很大的优势:内置锁为很多开发人员所熟悉,并且简洁紧凑

ReentrantLock的危险性比synchronized要高,如果忘记在finally中调用relock,那…

仅当内置锁不能满足需求时,才考虑ReentrantLock.

未来更可能提升synchronized而不是ReentrantLock的性能.因为synchronized是JVM的内置属性,它能执行一些优化,例如对线程封闭锁对象的锁消除优化,通过加锁的粒度来消除内置锁的同步,而如果通过基于类库的锁来实现这些功能,则可能性不大.

就性能方面来说,应该选择synchronized而不是ReentrantLock.