1. Lock 和 ReentrantLock
Lock
接口:
1 2 3 4 5 6 7 8
| public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
|
Lock 提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的。
Lock 的实现必须提供具有与内部加锁相同的内存可见性的语义。
ReentrantLock 实现了 Lock 接口,提供了与内置锁 synchronized 相同的互斥和内存可见性保证。获得 ReentrantLock 的锁与进入 synchronized 块有着相同的内存语义,释放 ReentrantLock 锁与退出 synchronized 块有相同的内存语义。
synchronized 内置锁局限性:
- 不能中断那些正在等待获取锁的线程。
- 在请求锁失败情况下,会无限等待。
Lock 锁必须在 finally 中释放。如果 Lock 锁守护的代码在 try 块之外跑出了异常,它将永远不会被释放。ReentrantLock 不能完全替代 synchronized,忘记释放 Lock 是非常危险的,因为当程序的控制权离开了守护的块时,不会自动清除锁。
1.1 可轮询的和可定时的锁请求
可定时的与可轮询的锁获取模式是由 tryLock()
方法实现的,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。
在内置锁中,死锁是一个严重的问题,恢复程序的唯一方法是重新启动程序,而防止死锁的唯一方法就是在构造过程时避免不一致的锁顺序。
可定时的与可轮询的锁提供了另一种选择:避免死锁的发生。
在第10章,有这样一个例子:转账
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| import java.util.concurrent.atomic.AtomicInteger;
public class DynamicOrderDeadlock { public static void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount) throws InsufficientFundsException { synchronized (fromAccount) { synchronized (toAccount) { if (fromAccount.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsException(); } else { fromAccount.debit(amount); toAccount.credit(amount); } } } }
static class DollarAmount implements Comparable<DollarAmount> {
private int amount = 0;
public DollarAmount(int amount) { this.amount = amount; }
public DollarAmount add(DollarAmount d) { this.amount = this.amount + d.amount; return this; }
public DollarAmount subtract(DollarAmount d) { this.amount = this.amount - d.amount; return this; }
public int compareTo(DollarAmount dollarAmount) { return Integer.compare(this.amount, dollarAmount.amount); }
public int getAmount() { return amount; } }
static class Account { private DollarAmount balance; private final int acctNo; private static final AtomicInteger sequence = new AtomicInteger();
public Account() { acctNo = sequence.incrementAndGet(); }
void debit(DollarAmount d) { balance = balance.subtract(d); }
void credit(DollarAmount d) { balance = balance.add(d); }
DollarAmount getBalance() { return balance; }
public void setBalance(DollarAmount balance) { this.balance = balance; }
int getAcctNo() { return acctNo; } }
static class InsufficientFundsException extends Exception { } }
|
对于使用内置锁 synchronized 的 transferMoney()
方法:
- 如果【线程A】获取了 Account1 的锁,向 Account2 转账。
- 而【线程B】又获取了 Account2 的锁,向 Account1 转账。
- 那么就会出现【线程A】等待 Account2 的锁,【线程B】等待 Account1 的锁 —- 线程死锁
轮询锁:通过 tryLock 来避免锁顺序死锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
public class DeadlockAvoidance { private static Random rnd = new Random();
public boolean transferMoney(Account fromAccount, Account toAccount, 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 (fromAccount.lock.tryLock()) { try { if (toAccount.lock.tryLock()) { try { if (fromAccount.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsException(); } 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); } }
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
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) { return true; }
long estimatedNanosToSend(String message) { return message.length(); } }
|
1.2 可中断的锁获取操作
- 正如定时锁的获得操作允许在限时活动内部使用独古锁,可中断的锁获取操作允许在可取消的活动中使用。
- lockInterruptible() 方法能够在获得锁的同时保持对中断的响应,并且由于它包含在Lock中,因此无需创建其他类型的不可中断阻塞机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
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 { return true; } }
|
1.3 非块结构的锁
- 在内置锁(synchronized)中,锁的获取和释放都是基于使用内置锁的代码块的,并不用考虑内置锁对代码块的控制权是如何退出的。
- 虽然自动释放锁简化了程序的分析,并且避免了潜在的代码错误造成的麻烦,但是有时候需要更灵活的加锁规则。Lock就是这样一个可定制化的锁。
2. 对性能的考量
显式锁 ReentrantLock 要比内置锁 synchronized 提供更好的竞争性能。
对于锁守护的程序而言,发生锁竞争时,程序的性能是可伸缩性的关键。
:label:可伸缩性指的是:当增加计算资源的时候(比如增加额外 CPU 数量、内存、存储器、TO 带宽),吞吐量和生产量能够相应地得以改进。
如果很多资源都花费在锁的管理和调度上,那么留给程序的就会越少。
锁的实现越好,那么上下文切换会更少,在共享内存总线上发起的内存同步通信也会更少。
3. 公平性
ReentrantLock 构造函数:
1 2 3 4 5 6 7 8 9 10 11 12
| public ReentrantLock() { sync = new NonfairSync(); }
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
|
- 在公平的锁上,线程将按照它们发出请求的顺序来获得锁(对于公平锁而言,可轮询的tryLock总会闯入)
- 在非公平的锁上,则允许”插队“:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。
- 公平的锁,在线程挂起和线程恢复时(上下文切换),存在的开销,会极大的降低程序性能。
- 非公平的锁,允许线程在其它线程的恢复阶段进入加锁代码块。
- 如果持有锁的时间相对较长,或者请求锁的平均时间将额较长,那么推荐使用公平锁。
- 内置锁 synchronized 没有提供确定的公平性保证。
4. 在 synchronized 和 ReentrantLock 之间进行选择
ReentrantLock
ReentrantLock 功能性方面更全面,具有更强的扩展性
ReentrantLock 提供了 Condition,对线程的等待和唤醒等操作更加灵活。一个 ReentrantLock 能够有多个 Condition 实例。
1 2 3 4
| public Condition newCondition() { return sync.newCondition(); }
|
ReentrantLock 可以控制线程得到锁的顺序(公平锁、非公平锁) – fair
ReentrantLock 可以查看锁的状态、等待锁的线程数。可以响应中断(lockInterruptibly)。
ReentrantLock 提供了可轮询的锁请求(tryLock)
在获取 ReentrantLock 时,可以设置超时时间。
ReentrantLock是显式锁,需要手动释放锁,忘记释放后果非常严重。
synchronized
- synchronized 是在JVM层面上实现的,能够通过一些监控工具监控synchronized的锁定。
- synchronized 能够在代码块执行完成或异常退出时自动释放锁。
- 被 synchronized 保护的代码块,一旦被线程获取锁,如果不释放,别的线程要获取该锁,会一直长时间的等待,不能被中断。
5. 读-写锁
一个资源可以被多个读操作访问,或者被一个写操作访问,读-写 两者不能同时进行。ReadWriteLock 实现的加锁策略允许多个同时存在的读者,但是只允许一个写者。
读写锁的可选实现:
- 释放优先。写入锁释放后,应该优先选择读线程,写线程,还是最先发出请求的线程
- 读线程插队。锁由读线程持有,写线程在等待,再来一个读线程,是继续让读线程访问,还是让写线程访问
- 重入性。读取锁和写入锁是否可重入
- 降级。将写入锁降级为读取锁
- 升级。将读取锁升级为写入锁
用读写锁包裝的 Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteMap <K,V> { private final Map<K, V> map; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock();
public ReadWriteMap(Map<K, V> map) { this.map = map; }
public V put(K key, V value) { writeLock.lock(); try { return map.put(key, value); } finally { writeLock.unlock(); } }
public V remove(Object key) { writeLock.lock(); try { return map.remove(key); } finally { writeLock.unlock(); } }
public void putAll(Map<? extends K, ? extends V> m) { writeLock.lock(); try { map.putAll(m); } finally { writeLock.unlock(); } }
public void clear() { writeLock.lock(); try { map.clear(); } finally { writeLock.unlock(); } }
public V get(Object key) { readLock.lock(); try { return map.get(key); } finally { readLock.unlock(); } }
public int size() { readLock.lock(); try { return map.size(); } finally { readLock.unlock(); } }
public boolean isEmpty() { readLock.lock(); try { return map.isEmpty(); } finally { readLock.unlock(); } }
public boolean containsKey(Object key) { readLock.lock(); try { return map.containsKey(key); } finally { readLock.unlock(); } }
public boolean containsValue(Object value) { readLock.lock(); try { return map.containsValue(value); } finally { readLock.unlock(); } } }
|