首页 > 技术文章 > Java并发--ReentrantLock原理详解

guojuncheng 2020-06-15 22:33 原文

ReentrantLock是什么?

  1. ReentrantLock重入锁,递归无阻塞的同步机制,实现了Lock接口;
  1. 能够对共享资源重复加锁,即当前线程获取该锁,再次获取不会被阻塞;
  1. 支持公平锁和非公平锁。

UML图

  1. 公平锁 

问题:

    1. 重入性的实现原理;
    1. 公平和非公平锁。

ReentrantLock的使用

//非公平锁
ReentrantLock lock = new ReentrantLock();
lock.lock();
//lock方法:
public void lock() {
    sync.lock();
}
注释:
  1. Sync为ReentrantLock里面的一个内部类,它继承AQS(AbstractQueuedSynchronizer),它有两个子类:公平锁FairSync和非公平锁NonfairSync。
  1. ReentrantLock里面大部分的功能都是委托给Sync来实现的,同时Sync内部定义了lock()抽象方法由其子类去实现,默认实现了nonfairTryAcquire(int acquires)方法,可以看出它是非公平锁的默认实现方式。下面我们看非公平锁的lock()方法:
final void lock() {
        //尝试获取锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //获取失败,调用AQS的acquire(int arg)方法
            acquire(1);
    }
  1. 首先会第一次尝试快速获取锁,如果获取失败,则调用acquire(int arg)方法,该方法定义在AQS中,如下:
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
 
  1. 这个方法首先调用tryAcquire(int arg)方法,在AQS中讲述过,tryAcquire(int arg)需要自定义同步组件提供实现,非公平锁实现如下:
protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
 
    final boolean nonfairTryAcquire(int acquires) {
        //当前线程
        final Thread current = Thread.currentThread();
        //获取同步状态
        int c = getState();
        //state == 0,表示没有该锁处于空闲状态
        if (c == 0) {
            //获取锁成功,设置为当前线程所有
            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;
    }

释放锁

  1. 获取同步锁后,使用完毕则需要释放锁,ReentrantLock提供了unlock释放锁:
 public void unlock() {
        sync.release(1);
 }
  1. unlock内部使用Sync的release(int arg)释放锁,release(int arg)是在AQS中定义的:
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
  1. 与获取同步状态的acquire(int arg)方法相似,释放同步状态的tryRelease(int arg)同样是需要自定义同步组件自己实现:
protected final boolean tryRelease(int releases) {
        //减掉releases
        int c = getState() - releases;
        //如果释放的不是持有锁的线程,抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //state == 0 表示已经释放完全了,其他线程可以获取同步状态了
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

只有当同步状态彻底释放后该方法才会返回true。当state == 0 时,则将锁持有线程设置为null,free= true,表示释放成功。

重入性的实现原理
  1. 在线程获取锁时,如果已经获取锁的线程是当前线程,则直接再次获取成功;
  1. 锁会被获取n次,只有锁在被释放同样n次之后,该锁才会被完全释放。
重入锁--非公平锁--加锁的实现原理
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair tryfor trylock method.
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 如果该锁未被任何线程占有,该锁能被当前线程获取
    if (c == 0) {
        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;
}

注释:如果该锁未被任何线程占有,该锁能被当前线程获取,如果该锁被占有,检查占有线程是否是当前线程,如果是的话,同步状态加一并返回true。

 

重入锁--非公平锁--解锁的实现原理

源代码java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
protected final boolean tryRelease(int releases) {
    //当前线程的同步状态减一
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        //只用同步状态为0的时候,锁才能释放成功,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
    //当锁未完全释放,返回false
    setState(c);
    return free;
}

注释:

当前线程的同步状态减一, 只用同步状态为0的时候,锁才能释放成功,返回true, 当锁未完全释放,返回false。
公平性锁
锁的获取顺序符合请求上的绝对时间顺序,满足FIFO,
  1. ReentrantLock的构造方法,无参时是构造非公平锁,源码为:
/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}
  1. ReentrantLock的构造方法,带boolean参数,可传入一个boolean值,true时为公平锁,false时为非公平锁,源码为:
/**
 * Creates an instance of {@codeReentrantLock} with the
 * given fairness policy.
 *
 * @paramfair {@codetrue} ifthis lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

重入锁--公平锁--加锁的实现原理

源代码:java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
/**
 * Fair version of tryAcquire.  Don't grant access unless
 * recursive call or no waiters or is first.
 */
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        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;
}

注释:

public final boolean hasQueuedPredecessors() {
        Node t = tail;  //尾节点
        Node h = head;  //头节点
        Node s;
 
        //头节点 != 尾节点
        //同步队列第一个节点不为null
        //当前线程是同步队列第一个节点
        return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
    }
//该方法主要做一件事情:主要是判断当前线程是否位于CLH同步队列中的第一个。如果是则返回true,否则返回false。

 

  1. 代码和非公平锁基本一直,唯一的不同在于增加了hasQueuedPredecessors的逻辑判断。该方法用来判断当前节点在同步队列中是否有前驱节点。如果有前驱节点说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点,猜能够做后面的逻辑判断,
  1. 公平锁每次都是从同步队列中的对一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能够再次获取到锁。
公平锁和非公平锁的比较
  1. 公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
  1. 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。
 
 
参考链接:
  1. https://www.javazhiyin.com/15047.html
  1. https://www.javazhiyin.com/6265.html

推荐阅读