13.1 Lock与ReentrantLock
Reentrant 英[riːˈɛntrənt] 可重入; 可重入的; 重入; 可再入的; 重进入;
程序清单13-1 Lock接口定义
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
public class ReentrantLock implements Lock, java.io.Serializable {...}
ReentrantLock提供了与synchronized相同的互斥性和可见性,可重入。
并且与synchronized相比,还为处理锁的不可用性问题提供了更高的灵活性。
为什么要创建一种与内置锁如此类似的新加锁机制?
大多数情况下内置锁都能很好地工作,但在功能上存在一些局限性。eg:
- 无法中断一个正在等待获取锁的线程
- 无法再请求获取一个锁时无限地等待下去
(开始请求锁后,这个操作将无法取消,因此内置锁很难实现带有时间限制的操作)- 内置锁无法实现非阻塞结构的加锁机制
程序清单13-2 使用ReentrantLock来保护对象状态
Lock lock = new ReentrantLock();
lock.lock();
try {
//更新对象状态
//捕获异常,并在必要时恢复不变性条件
} finally {
lock.unlock();
}
13.1.1 轮询与定时
程序清单13-3 通过tryLock来避免锁顺序死锁(10.1.2)
public boolean transferMoney(Account fromAccount, Account toAccount, DollarAmount amount,long timeout, TimeUnit unit) {
long fixedDelay = getFixedDelayComponentNanos(timeout,unit);
long randMod = getRandomDelayComponentNanos(timeout,unit);
long stopTime = System.nanoTime() + unit.toNanos(timeout);
while(true) {
if(fromAccount.lock.tryLock()) {
try {
if(toAccount.lock.tryLock()) {
try {
if(fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientResourcesException();
} else {
fromAccount.debit(amount);
toAccount.credit(amount);
return true;
}
} finally {
toAccount.lock.unlock();
}
}
} finally {
fromAccount.lock.unlock();
}
}
if(System.nanoTime() < stopTime)
return false;
NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
}
return false;
}
程序清单13-4 带有时间限制的加锁
//试图在Lock保护的共享通信线路上发送一条消息。
public boolean trySendOnSharedLine(String message,long timeout,TimeUnit unit) {
long nanoToLock = unit.toNanos(timeout) - estimatedNanosToSend(message);
if(!lock.tryLock(timeout, unit)) //如果不能在指定时间内完成,代码就会失败。独占加锁行为
return false;
try {
return sendOnSharedLine(message);
} finally {
lock.unlock();
}
}
13.1.2 可中断的锁获取操作
程序清单13-5 可中断的锁获取操作
public boolean trySendOnSharedLine(String message) throws InterruptedException {
lock.lockInterruptibly();
try {
return cancellableSendOnSharedLine(message);
} finally {
lock.unlock();
}
}
public boolean cancellableSendOnSharedLine(String message) throws InterruptedException{
//TODO ....
return true;
}
定时的tryLock()同样能响应中断,因此当需要实现一个定时的和可中断的锁操作时,可以使用tryLock方法
13.1.3 非块结构的加锁
13.2 性能考虑因数
Java 5.0中 ReentrantLock比内置锁有更好的竞争性能(是可伸缩性的关键要素。体现在锁的管理和调度)
从Java 6.0 开始 则内置锁synchronized 追平了ReentrantLock
13.3 公平性
ReentrantLock 构造函数提供2种选择:
1.非公平的锁(默认):允许【插队】:
*1)当一个线程请求非公平锁时,如果在发出请求的同时改锁的状态变为可用,
2)只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中
那么这个线程将跳过队列中所有的等待线程并获得这个锁。
3)不提倡插队,但无法防止某个线程在合适的时候进行“插队”
2.公平的锁:线程按照发出请求的顺序来获得锁
新发出请求的线程将被安排在等待队列中,不会插队
为什么不希望所有的锁都是公平的?
公平性将由于挂起和恢复线程时存在的开销而极大地降低性能。非公平锁的性能要高于公平锁的性能。
A线程持有锁 ——> B在排队等待 ——>A释放锁——>唤醒B——>B请求锁
A线程持有锁 ——> B在排队等待 ——>A释放锁,于此同时C请求锁,C可能在B被唤醒之前获得、使用、释放锁
——>唤醒B——>B请求锁(可能此时C已经执行完了)
什么情况下使用公平锁?
当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁
13.4 在synchronized和ReentrantLock之间选择
在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock:
- 可定时的、可轮询的、可中断的锁获取操作
- 公平队列
- 非块结构的的锁
否则应该优先使用synchronized
13.5 读 - 写锁
一个资源可以被多个读操作访问, 或者被一个写操作访问,但两者不能同时进行。
程序清单13-6 ReadWriteLock接口
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();
}
ReentrantReadWriteLock实现
ReentrantReadWriteLock的读写锁都可重入。
公平锁:等待时间最长的将获得锁。
读线程持有锁,下一个是写入锁,此时所有的其它读线程都无法获取到锁,必须等待写线程执行完时间片后才行。
非公平锁:线程获取访问许可的顺序不定。
写线程可以降级为读线程,读线程不可以升级为写线程(会产生死锁)
Listing 13.7. Wrapping a Map with a Readwrite Lock
public class ReadWriteMap {
private final Map map;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock r = lock.readLock();
private final Lock w = lock.writeLock();
public ReadWriteMap(Map map) {
this.map = map;
}
public V put(K key, V value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
} }
// Do the same for remove(), putAll(), clear()
public V get(Object key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
} }
// Do the same for other read-only Map methods