ReentrantLock

和Synchronized一样,ReentrantLock也是可重入的,可作为内置锁的替代物。

与Synchronized相比较而言,ReentrantLock有以下优势:支持公平/非公平锁、支持可中断的锁、支持非阻塞的tryLock(可超时)、支持锁条件、可跨代码块使用(一个地方加锁,另一个地方解锁),总之比Synchronized更加灵活。但也有缺点,比如锁需要显示解锁、无法充分享用JVM内部性能提升带来的好处等等。

public interface Lock {
    /**
     * 获取锁,若无法获取到,则当前线程被阻塞。直到获取成功为止。
     */
    void lock();
    /**
     * 获取锁,若无法获取到,则当前线程被阻塞。直到发生以下两种情况:
     * 获取成功;或者
     * 其他线程中断当前线程 
     */
    void lockInterruptibly() throws InterruptedException;
    /**
     * 如果当前锁可用,获取成功返回true;否则返回false
     */
    boolean tryLock();
    /**
     * 与lockInterruptibly相似,还有第三种情况,即达到超时时间。
     * 如果当前锁可用,获取成功返回true;否则返回false
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    /**
     * 释放锁
     */
    void unlock();
    /**
     * 返回当前锁相关的条件。在条件等待之前,锁必须被当前线程占有,await方法会在等待前释放锁,在结束等待前重新获取锁
     */
    Condition newCondition();
}

基于AQS机制构建的同步类,内部都会使用相关类继承AQS。ReentrantLock也不例外。

// 内部协调机制对象
private final Sync sync;
// 抽象类继承AQS,具有公平和非公平版本的子类。利用AQS的state来表示锁持有(重入)的次数。. 
abstract static class Sync extends AbstractQueuedSynchronizer 

非公平锁子类,新来的类之间尝试获取锁,无论等待队列中是否有线程阻塞等待。

final static class NonfairSync extends Sync

公平锁子类,只有在递归(重入)或者同步队列中没有其他线程或者当前线程是等待队列中的第一个线程时才准许访问

final static class FairSync extends Sync 

非公平版的锁-加锁操作

  1. 当前线程首先会无条件的执行一个CAS操作来获取锁,如果CAS操作成功,获取锁成功。
  2. 如果第1步没成功,当前会检查锁是否被其他线程持有,也就是锁是否可用。
  3. 如果没有其他线程持有锁,会以CAS的方式尝试获取锁,如果CAS操作成功,获取锁成功。
  4. 如果有其他线程持有锁,会判断一下持有锁的线程是否为当前线程,如果是当前线程,重入次数+1,获取锁成功。
  5. 根据AQS的分析,上述2、3、4步会执行多次,如果最终获取锁失败,当前线程会被阻塞,等待其他线程执行解锁操作将其唤醒。

公平版的锁-加锁操作

  1. 当前线程首先会检查锁是否被其他线程持有,并且当前同步等待队列里有没有其他线程在等待。
  2. 如果没有其他线程持有锁,且同步等待队列里没有其他线程,会以CAS的方式尝试获取锁,如果CAS操作成功,获取锁成功。
  3. 如果有其他线程持有锁,会判断一下持有锁的线程是否为当前线程,如果是当前线程,重入次数+1,获取锁成功。nonfairTryAcquire方法
  4. 根据AQS的分析,上述1、2、3步会执行多次,如果最终获取锁失败,当前线程会被阻塞,等待其他线程执行解锁操作将其唤醒。

非公平版和公平版锁的解锁操作一样

  1. 当前线程首先将锁重入次数减1(AQS的state),如果减1后结果为0,将当前同步器的线程信息置空,并唤醒同步等待队列中队头的等待线程。
  2. 如果第1步中,重入次数减1后结果不为0(说明当前线程还持有当前锁),方法结束。

ReentrantLock实现,整体就是sync的一个代理。

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = (fair)? new FairSync() : new NonfairSync();
}

public void lock() {
    sync.lock();
}

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public void unlock() {
    sync.release(1);
}
public Condition newCondition() {
    return sync.newCondition();
}

所有try方法,相当于非阻塞的方式获取锁。

synchronized获得的内部锁存在一定的局限

  1. 不能中断一个正在试图获得锁的线程

  2. 试图获得锁时不能像trylock那样设定超时时间

  3. 每个锁只有单一的条件,不像condition那样可以设置多个