ReentrantLock、synchronized 与 ReadWriteLock

ReentrantLock、synchronized 与 ReadWriteLock

一. ReentrantLock

ReentrantLock 并不是内置锁 synchronized 的替代方式,而是当 synchronized 加锁机制不适用时,作为一种可选择的高级功能。
ReentrantLock 是 Lock 的实现类,Lock 提供无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法是显式的,因此 ReentrantLock 也被称为显示锁。

1. Lock 的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Lock {
void lock(); // 加锁

void lockInterruptibly() throws InterruptedException;

boolean tryLock(); // 尝试获取锁,如果加锁成功,返回 true

boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 在一定时间内获取锁,如果在时间内没有加锁成功,则提前结束

void unlock(); // 解锁

Condition newCondition(); // 返回锁的条件实例
}

2. ReentrantLock 的用法

2.1 ReentrantLock 的标准用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//  Lock 的标准用法
private void luckExp(){
Lock lock = new ReentrantLock();

//....

lock.lock();

try {
// 更新对象转态
// 捕获异常,并在必要时恢复不变性条件
} finally {
lock.unlock(); // 一定要在 finally 中解锁
}
}
2.2 ReentrantLock 带时间的加锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 // 带有时间限制的加锁
// 如果操作不能再指定的时间内给出结果,程序会提前结束
public boolean trySend(String message, long timeout, TimeUnit unit) throws InterruptedException{
Lock lock = new ReentrantLock();
long nanosToLock = unit.toNanos(timeout) - estimatedNanosToSend(message);
if (!lock.tryLock(nanosToLock, TimeUnit.NANOSECONDS)){ // 定时锁
return false;
}

try {
return sendOnSharedLine(message); // 操作
}finally {
lock.unlock();
}
}

二. Synchronized

使用了 synchronized 方法在同一时间只能被一个线程使用,其他线程必须等到这方法释放。
线程在进入了同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁。

同步代码块包含两部分

  • 一个作为锁的对象引用
  • 一个作为由这个锁保护的代码块

两种使用方法

  • 以锁的对象使用
1
2
3
4
5
6
7
8
private Object lock = new Object();

// 锁的对象引用
private void lockObject(){
synchronized (lock){
// 访问或修改由锁保护的共享状态
}
}
  • 锁保护的代码块
1
2
3
4
// 锁保护代码块
private synchronized void doSomething(){

}
  • Class对象锁
1
2
3
4
5
6
7
8
9
10
11
12
 public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}

三. Synchronized 与 ReentrantLock 之间选择

synchronized

  • 开发人员熟悉,简洁紧凑
  • 不用担心解锁的问题
  • 在线程转储中能给出在哪些调用帧获得了哪些锁,并能检测和识别发生了死锁的线程

ReentrantLock

  • 提供了可定时的、可轮询的与可中断的锁
  • 如果忘记 unlock, 会发生死锁的问题

选择

在 synchronized 无法满足的情况下才会使用 ReentrantLock

3. ReadWriteLock 读写锁

  • 当读线程多于写线程时,使用 ReadWrite 线程会更好一些
  • ReentrantReadWriteLocks 可以用作提高一些并发容器的性能
  • 当锁的持有时间比较长并且大部分操作都不会修改被守护资源是,ReadWriteLock 能提高并发性

  • ReadWriteLock 接口

1
2
3
4
5
6
public interface ReadWriteLock {

Lock readLock(); // 返回一个读的锁

Lock writeLock(); // 返回一个写的锁
}
  • 提高一些并发容器的性能
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
/**
* 当锁的持有较长并且大部分操作都不会修改被守护的资源时, ReadWriteLock 可以提高并发性
* 避免了 "读-写" 与 "写-写"的冲突,
* 可以多个同时 "读-读"
* @param <K>
* @param <V>
*/
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();
}
}

// 对 remove(), putAll(), clear() 等方法执行相同的操作

public V get(K key){
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
}
yxhuang wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客