目录
思维导图
1 Lock和ReentrantLock
Lock定义了抽象的锁操作,如下图
Lock提供了无条件的、可中断的、可轮询的和定时的获取锁操作。
Lock的实现与内部锁具有相同的内存可见性语义。
ReentrantLock实现了Lock接口,提供了与synchronized相同的互斥和内存可见性保证。
内部锁存在的条件下,创建这种Lock机制原因,主要是内部锁具有的一些局限:
- 不能中断正在等待获取锁的线程,并且请求失败必须无限等待。
- 不具备高级特性,比如定时、可轮询等。
下列demo-1是Lock接口的规范形式:
private final Lock lock = new ReentrantLock();
//lock接口规范形式
public void addPlace(String place) {
lock.lock();
try {
//应用操作
//捕获异常
} catch (Exception e) {
//处理异常
} finally {
//finally释放锁
lock.unlock();
}
}
}
Lock锁必须在finally块中显示释放。
1.1 可轮询的和可定时的锁
Lock定义的tryLock
的两种形式可以实现可轮询和可定时的锁操作。这些操作可以避免死锁的发生。
- tryLock():立即返回,true表示获取成功,false表示锁不可用。
- tryLock(long time, TimeUnit unit):如当前线程没有被中断则获取锁,成功则返回true,否则当前线程将被休眠,直到发生下列事件:
- 1. 锁被当前线程获取。
- 2. 该线程被其它线程中断。
- 3. 指定的等待时间已过。
如demo-2演示了如何通过可轮询的方式,在指定时间进行一个操作:
public boolean transferMoney(Account from, Account to, BigDecimal amount, long timeout, TimeUnit timeUnit) throws InterruptedException {
long stopTime = System.nanoTime() + timeUnit.toNanos(timeout);
while (true) {
if (from.lock.tryLock()) {
try {
if (to.lock.tryLock()) {
try {
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
return true;
} finally {
to.lock.unlock();
}
}
} finally {
from.lock.unlock();
}
}
if (System.nanoTime() > stopTime) {
return false;
}
Thread.sleep(100);
}
}
上述代码通过传入一个等待时间,用于限定轮询获取锁的超时时间,如果超过这个时间还未获取两个锁,则代码直接失败返回false。
1.2 可中断的锁获取操作
可中断的锁可以在取消活动中使用。
一个可中断的规范形式如demo-3:
public void sendMessage(String message) throws InterruptedException {
lock.lockInterruptibly();
try {
doSendMessage(message);
} finally {
lock.unlock();
}
}
当等待获取锁过程,被中断,方法将抛出中断异常,交给调用方处理。
2 对性能的考量
ReentrantLock在Java5.0提供的竞争性要优于内部锁,但是Java6.0对内部锁进行了改善,使得两者性能差距非常小了。
性能是一个不断变化的目标。
3 公平性
ReentrantLock提供了两种公平性选择。默认是非公平性锁:
两者区别在于非公平锁允许在首次获取时进行抢占,失败后仍然进行排队:
4 在内部锁synchronized和ReentrantLock直接进行选择
如果内部锁满足需求,不使用ReentrantLock,只有当你需要如下高级性能时使用:
- 可定时、可轮询和可中断的操作。
- 公平队列(内部锁非公平),或者非块结构的锁。
未来的性能改进倾向于synchronized,因为它是基于JVM的,能够进行优化。
5 读写锁
读写锁:一个资源可以被多个读者访问,但是只能被一个写者访问。
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();
}
只暴露了读锁和写锁。
如下等待demo-4是使用读写锁封装Map的示例:
/**
* 读写锁包装Map
* @param <K> 键
* @param <V> 值
*/
public class ReadWriteMap<K, V> {
private final Map<K, V> map;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock w = readWriteLock.writeLock();
private final Lock r = readWriteLock.readLock();
public ReadWriteMap(Map<K, V> map) {
this.map = map;
}
public V put(K k, V v) {
//写锁控制写入-排它
w.lock();
try {
return map.put(k, v);
} finally {
w.unlock();
}
}
public V get(K k) {
//读锁控制读取-共享
r.lock();
try {
return map.get(k);
} finally {
r.unlock();
}
}
}
读写操作前获取对应读写锁即可。
总结
显式锁提供了内部锁的不具备的一些高级特性,但是不能完全替代内部锁。只有当你需要内部锁没有提供的特性时才使用显式锁。
参考文献
[1]. 《JAVA并发编程实战》.