ReentrantLock、synchronized 与 ReadWriteLock

ReentrantLock、synchronized 与 ReadWriteLock

一. ReentrantLock

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

1. Lock 的源码

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 的标准用法
//  Lock 的标准用法
private void luckExp(){
    Lock lock = new ReentrantLock();

    //....

    lock.lock();

    try {
        // 更新对象转态
        // 捕获异常,并在必要时恢复不变性条件
    } finally {
        lock.unlock();  // 一定要在 finally 中解锁
    }
}
2.2 ReentrantLock 带时间的加锁
 // 带有时间限制的加锁
// 如果操作不能再指定的时间内给出结果,程序会提前结束
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 方法在同一时间只能被一个线程使用,其他线程必须等到这方法释放。
线程在进入了同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁。

同步代码块包含两部分

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

两种使用方法

  • 以锁的对象使用
private Object lock = new Object();

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

}
  • Class对象锁
 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 接口

public interface ReadWriteLock {
    
    Lock readLock();  // 返回一个读的锁

    Lock writeLock(); // 返回一个写的锁
}
  • 提高一些并发容器的性能
/**
 * 当锁的持有较长并且大部分操作都不会修改被守护的资源时, 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();
        }
    }
}
发布了58 篇原创文章 · 获赞 20 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/yxhuang2008/article/details/86032335