思维导图:
引言:
本文的主要内容是介绍两种显式锁的使用。一种是Lock,一种是ReadWriteLock。所以,本文可以归类为使用部分:
- 使用部分:介绍Lock,其实现类是ReentrantLock,功能则是补充synchronized的不足。也会介绍ReadWriteLock,用于读多写少的并发程序。
一.Lock
显式锁Lock可以作为synchronized在功能不足时的补充,比如定时获取锁这些操作。Lock并不能替代synchronized,这个小节会介绍显式锁Lock的一些常见用法。
1.1 加锁方式
如何使用Lock进行各种各样的加锁呢?
1.1.1 直接加锁
public void addLock(int n) {
Lock lock = new ReentrantLock();
lock.lock();
try{
//别加锁的操作
}finally {
//释放锁
lock.unlock();
}
}
1.1.2 轮询锁
我们可以使用Lock进行轮询操作以获取锁,这样做可以避免发生顺序死锁。如下代码所示:
public class DeadlockAvoidance {
private static Random rnd = new Random();
public boolean transferMoney(Account fromAcct, Account toAcct, DollarAmount amount, long timeout, TimeUnit unit) throws InsufficientFundsException, InterruptedException {
long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
long randMod = getRandomDelayModulusNanos(timeout, unit);
long stopTime = System.nanoTime() + unit.toNanos(timeout);
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);
}
}
private static final int DELAY_FIXED = 1;
private static final int DELAY_RANDOM = 2;
static long getFixedDelayComponentNanos(long timeout, TimeUnit unit) {
return DELAY_FIXED;
}
static long getRandomDelayModulusNanos(long timeout, TimeUnit unit) {
return DELAY_RANDOM;
}
static class DollarAmount implements Comparable<DollarAmount> {
public int compareTo(DollarAmount other) {
return 0;
}
DollarAmount(int dollars) {
}
}
class Account {
public Lock lock;
void debit(DollarAmount d) {
}
void credit(DollarAmount d) {
}
DollarAmount getBalance() {
return null;
}
}
class InsufficientFundsException extends Exception {
}
}
1.1.3 定时锁
可以利用Lock实现尝试在规定时间内获取锁,超时则放弃的功能。如下所示:
public class TimedLocking {
private Lock lock = new ReentrantLock();
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();
}
}
private boolean sendOnSharedLine(String message) {
/* send something */
return true;
}
long estimatedNanosToSend(String message) {
return message.length();
}
}
1.1.4 可中断锁
Lock也有对中断操作保持响应的可中断锁的功能,如下所示:
public class InterruptibleLocking {
private Lock lock = new ReentrantLock();
public boolean sendOnSharedLine(String message) throws InterruptedException {
lock.lockInterruptibly();
try {
return cancellableSendOnSharedLine(message);
} finally {
lock.unlock();
}
}
private boolean cancellableSendOnSharedLine(String message) throws InterruptedException {
/* send something */
return true;
}
}
1.2 使用要点
使用显示锁时有以下几个需要注意的地方。
- 在并发程序发生激烈的竞争时,使用显示锁的速度并不会比使用synchronized更快
- ReetrantLock可以使用公平锁或者非公平锁。公平锁时依照请求顺序获得锁,而非公平锁的可以插队,如果该锁的状态在发出请求后恰好可用的话
- 使用ReetrantLock在内存的语义上和使用synchronized相同,他们提供的性能也相差不远,所以,一般情况下还是使用synchronized比较方面,出错的可能性也较小。但是,如果需要一些特殊的功能,比如公平性,可中断,可等待时,就需要使用ReetrantLock了,总的来说,ReetrantLock是synchronized在功能上的补充。
二.ReadWriteLock
对于某些并发程序来说,其读操作的数量远远大于写操作的数量。在读取操作时,我们其实只需要保证内存的可见性就好,并不需要获取整个锁。只有当需要进行写操作是,才需要获取独占锁。此时,我们就可以使用ReadWriteLock了。如下所示:
public class ReadWriteMap <K,V> {
private final Map<K, V> map;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock r = lock.readLock();
private final Lock w = lock.writeLock();
public ReadWriteMap(Map<K, V> map) {
this.map = map;
}
public V put(K key, V value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
public V get(Object key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
}