Java 5.0增加ReentrantLock,作为内置锁的补充选择。
Lock与ReentrantLock
Lock接口中定义了一种无条件、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。
public interfece Lock
{
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long timeout, TimeUnit unit
throw InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。
ReentrantLock同样提供了可重入的加锁语义
Lock lock = new ReentrantLock();
...
lock.lock();
try {
// 更新对象状态
// 捕获异常,并在必要时恢复不变性条件
} finally {
lock.unlock();//一定要记得在finally块里释放
}
轮询锁与定时锁
轮询锁和定时锁可由tryLock来实现
轮询锁,定时锁可以避免死锁的发生
轮询锁通过释放已获得的锁,并退回重新尝试获取所有锁(lock.tryLock()),定时锁通过释放已获得的锁,放弃本次操作(lock.tryLock(timeout, unit))来避免死锁
轮询锁:
while (true) {
if (fromAcct.lock.tryLock()) {
try {
if (toAcct.lock.tryLock()) {
try {
if (fromAcct.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
return true;
}
} finally {
toAcct.lock.unlock();
}
}
} finally {
fromAcct.lock.unlock();
}
}
if (System.nanoTime() < stopTime)
return false;
NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
}
}
定时锁
public boolean trySendOnSharedLine(String message,
long timeout, TimeUnit unit)
throws InterruptedException {
long nanosToLock = unit.toNanos(timeout)
- estimatedNanosToSend(message);
if (!lock.tryLock(nanosToLock, NANOSECONDS))
return false;
try {
return sendOnSharedLine(message);
} finally {
lock.unlock();
}
}
可中断的锁获取操作
Lock.lockInterruptibly():该锁与lock相似,但可以被中断
如果线程未被中断,也不能获取到锁,就会一直阻塞下去,直到获取到锁或发生中断请求
定时的lock.tryLock(timeout, unit)同样能响应中断
非块结构加锁
内置锁是基于块结构的加锁
Lock可以使块与块交叉实现非块结构的加锁(连锁式加锁或者锁耦合)
性能考虑因素
竞争性能 是可伸缩性的关键因素:如果有越多的资源被耗费在锁的管理和调度上,那么应用程序得到的资源就越少
在Java5.0中,ReentrantLock能提供更高的吞吐量,但在Java6中,二者的吞吐量非常接近
公平性
在公平的锁上,线程将按照它们发出请求的顺序来获得锁
在非公平的锁上,则允许”插队“:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁
公平性将由于在挂起线程和恢复线程时存在的开销而极大地降低性能(非公平性的锁允许线程在其他线程的恢复阶段进入加锁代码块)
当持有锁的时间相对较长,或者请求锁的平局时间间隔较长,那么应该使用公平锁
内置锁默认为非公平锁
在synchronized和ReentrantLock之间作出选择
在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。否则,还是应该优先使用synchronized
Synchronized是JVM的内置属性,能执行一些优化,并且基于块结构与特定栈管理,便于检测识别发生生死锁。
Java6提供了一个管理和调试接口,锁可以通过该接口进行注册,从而与ReentrantLocks相关的加锁信息就能出现在转储中,并通过其他的管理接口和调试接口来访问
读-写 锁
对于在多处理器系统上被频繁读取的数据结构,读 - 写锁能够提高性能。而在其他情况下,读 - 写锁的性能比独占锁的性能要略差一些,这是因为它们的复杂性更高
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
释放优先:
读线程插队: 读线程持有,另一个线程请求写入锁,其他读线程都不能获取读取锁,直到写线程使用完并释放写入锁。
重入性: 写入锁只能有唯一所有者,读取锁记录那些线程获取读取锁。
降级: 写线程降级为读线程可以
升级: 读线程升级为写线程不可以