Java多线程总结(三):[Lock]ReentrantLock

对于ReentrantLock来说,它本身是Lock接口的一个实现,AQS只是它使用的工具,所以,我们的思路是从Lock的角度切入,搞清楚它是如何利用AQS实现这套锁机制的

1. Lock接口

Lock接口定义比较简单,结合我们之前了解的AQS内容,基本看到这些方法名就大概了解它们是如何实现的

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

2. 方法实现

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

ReentrantLock在构造时可以接受参数来指定是否是公平锁,默认是非公平锁。根据入参的不同,sync字段会对应不同的同步器,而Lock接口定义方法的实现也都是直接交由sync去执行的,所以接下来我们从sync入手研究。

进一步观察后我们发现,FairSync与NonfairSync是Sync的两种不同扩展,而Sync是ReentrantLock中对AQS的一种扩展

2.1. Sync

Sync继承AQS之后实现了一个非公平的抢占逻辑nonfairTryAcquire,其中包含两部分

  1. 如果当前锁当好处于被释放状态,用CAS尝试插队
  2. 对于锁重入的处理
/**
 * 锁同步控制逻辑的基础,有公平锁和非公平锁两种实现。
 * 使用AQS中的state来表示持有的锁数量
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    //省略部分代码
    
    /**
     * 非公平锁的tryLock。
     * tryAcquire方法在子类实现,但是子类实现的trylock都需要一次非公平的尝试
     * 换句话说,tryLock总是会以非公平的方式尝试抢占锁
     * 
     * 这个方法与公平锁的tryAcquire非常相似,唯一的区别就是公平锁的tryAcquire方法会有一次队列的遍历来保证公平
     * 见java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire(int)
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //如果当前锁刚好被释放了,会尝试抢占
            //公平锁的tryAcquire方法在这里会先确认是否有等待的节点
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //重入
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    
    //省略部分代码
}

2.2. FairSync

关于公平锁值得一提的事情:

  • 公平锁的公平是如何定义与实现的
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    /**
     * 回顾acquire的执行过程
     * public final void acquire(int arg) {
     *     if (!tryAcquire(arg) &&
     *         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
     *         selfInterrupt();
     * }
     * lock直接调用acquire(1),相当于tryAcquire成功就执行,不成功就入队等待
     */
    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    /**
     * 公平版本的tryAcquire
     * 这里需要注意“公平”的定义:
     * 1. 保证不会插队:如果等待队列有节点在等待,那新节点一定排在后面
     * 2. 不保证先调用tryAcquire的线程就一定先获取到锁(也不能保证先调用tryAcquire的一定先入队),线程调度问题,易于理解
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //hasQueuedPredecessors会查看head的next是否有值
            //如果head.next不是当前节点,即便head.next已经cancelled了,tryAcquire也会返回false
            //即只要队列里还存在元素,公平锁的tryAcquire就一定会失败,除非是锁重入的场景
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //锁重入
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

2.3. NonfairSync

对于非公平锁值得一提的事情:

  • 它其实会插队两次,而且第一次甚至都没有判断state是否为0,换句话说,硬抢一次+软抢一次
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        //先抢一次
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //再抢一次
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

2.4. 执行逻辑对比

Java多线程总结(三):[Lock]ReentrantLock_第1张图片

你可能感兴趣的:(Java,java)