ReentrantLock源码解读

18 篇文章 2 订阅

ReentrantLock 概述

在这里插入图片描述

  • ReentrantLock 来自于 jdk 1.5,位于 JUC 包的 locks 子包,独占(互斥)式可重入锁。Synchronized 的功能他都有,并且具有更加强大的功能
  • 实现了 Lock 接口,具有通用的操作锁的方法。内部是使用 AQS 队列同步器来辅助实现的,重写了 AQS 的获取独占式锁的方法,并实现了可重入性
  • ReentrantLock 还具有公平与非公平两个获取锁模式,这个并非 AQS 提供的,而是ReentrantLock 基于 AQS 自己实现的

ReentrantLock 源码阅读可需参考 AQS 框架的详解:https://blog.csdn.net/weixin_38192427/article/details/117028828

可重入性

ReentrantLock 是一个可重入的互斥锁。顾名思义,互斥锁表示锁在某一时间点只能被同一线程所拥有。可重入表示锁可被某一线程多次获取。当然 synchronized 也是可重入的互斥锁

  • synchronized 关键字隐式的支持重进入,比如一个 synchronized 修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁
  • ReentrantLock 虽然没能像 synchronized 关键字一样支持隐式的重进入,但是在调用 lock() 方法时,已经获取到锁的线程,能够再次调用 lock() 方法获取锁而不被阻塞。主要的实现是在实现 tryAcquire(int acquires) 方法时考虑占有锁的线程再次获取锁的场景

ReentrantLock 中, AQSstate 同步状态值表示线程获取该锁的可重入次数

  • 在默认情况下,state 的值为 0 表示当前锁没有被任何线程持有
  • 当一个线程第一次获取该锁时会尝试使用 CAS 设置 state 的值为 1 ,如果 CAS 成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程
  • 在该线程没有释放锁的情况下,第二次获取该锁后,状态值被设置为 2 , 这就是可重入次数
  • 在该线程释放该锁时,会尝试使用 CAS 让状态值减 1,如果减 1 后状态值为 0,则当前线程成功释放该锁

公平与非公平模式

如果在绝对时间上,等待时间最长的线程最优先获取锁。那么这个锁是公平的,反之,是不公平的

  • 公平模式是严格的以 FIFO 先进先出的方式进行锁的竞争,但是非公平锁是无序的锁竞争,刚释放锁的线程很大程度上能比较快的获取到锁,队列中的线程只能等待,所以非公平锁可能会有“饥饿”的问题。但是重复的锁获取能减小线程之间的切换,而公平锁则是严格的线程切换,这样对操作系统资源的影响是比较大的,所以非公平锁的吞吐量是大于公平锁的,这也是为什么 JDK 将非公平锁作为默认的实现
  • 与默认情况非公平锁相比,使用公平锁的程序在多线程环境下效率比较低。而且即使是公平锁也不一定能保证线程调度的公平性,后来的线程调用 tryLock() 方法同样可以不经过排队而获得该锁

ReentrantLock 源码

源码概览

public class ReentrantLock implements Lock, java.io.Serializable {

    private static final long serialVersionUID = 7373984872572414699L;
    
    private final Sync sync;
	
	// 静态内部类 Sync 继承了 AQS 
	abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();

        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;
        }

        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;
        }

        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0);
        }
    }

	// 静态内部类 NonfairSync 继承了 Sync,表示非公平模式
	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);
        }
    }

	// 静态内部类 FairSync 继承了 Sync,表示公平模式
	static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        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;
        }
    }
}

ReentrantLock 构造器

非公平模式(默认)

private final Sync sync;

// 创建一个不公平的 ReentrantLock 的实例,这是默认的模式
public ReentrantLock() {
    sync = new NonfairSync();
}

公平模式

// 创建一个具有给定公平策略的 ReentrantLock,false表示不公平,true表示公平
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平模式加锁

lock() 不可中断获取锁

JUC 中只要是实现 Lock 的锁,那么加锁的方法,一般都统一调用开放给外部的 lock() 方法

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

lock() 方法的具体实现是在 sync 的子类 NonfairSync

static final class NonfairSync extends Sync {
	private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
    	// 首先假设当前锁没有被重入过,那么尝试获取锁
    	// CAS 尝试将 state 的值从 0 更新为 1
    	if (compareAndSetState(0, 1))
    		// 如果 CAS 成功,说明获取到了锁,那么记录获取到的锁的线程
       		setExclusiveOwnerThread(Thread.currentThread());
        else
        	// 如果 CAS 失败,说明当前锁被重入过,或者锁被其他线程获取了,
        	// 那么执行 AQS 的 acquire() 方法继续尝试获取
        	acquire(1);
    }
}
  • 首先尝试获取锁,使用 CASstate 的值从 0 更新为 1,如果 CAS 成功,那么说明该锁没有被任何线程获取,此时获取锁成功,将当前线程标记为持有锁的线程,加锁结束
  • 若获取锁失败,表示该锁此前已经被某条线程获取到了,或者被别的线程抢先 CAS 成功,那么执行 AQSacquire() 方法继续尝试获取锁

关于 AQS 中的获取独占式锁 acquire() 方法详情:https://blog.csdn.net/weixin_38192427/article/details/117028828

tryAcquire(arg) 方法的重写(重点)

AQS 中的获取独占式锁 acquire() 方法中,有一个 tryAcquire(arg) 方法,它的具体实现是由 AQS 的子类去完成的。在非公平模式下,它的实现在 NonfairSync 类中

protected final boolean tryAcquire(int acquires) {
	// 调用类 Sync 中的 nonfairTryAcquire() 
	return nonfairTryAcquire(acquires);
}

/**
 * NonfairSync 中的方法,非公平的获取锁
 *
 * @param acquires 参数,ReentrantLock 中传递 1
 * @return true 成功  false 失败
 */
final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取当前state同步状态值
    int c = getState();
    // 如果为 0,表示还没有线程获取该锁
    if (c == 0) {
        // 使用 CAS 尝试获取锁
        if (compareAndSetState(0, acquires)) {
            // 获取成功就设置线程变量
            setExclusiveOwnerThread(current);
            // 返回true,方法结束
            return true;
        }
    }
    // 如果不为 0,那么表示有线程获取到了锁,此时判断是否就是当前线程获取到了锁
	// 如果是,那么表示可重入,执行重入的逻辑
    else if (current == getExclusiveOwnerThread()) {
        // 新的 state 应该是此前获取的 state + 1
        int nextc = c + acquires;
        /**
         * 如果小于 0,那么表示重入次数过多(超过了 Integer.MAX_VALUE ),直接抛出异常
         * 新的 state 小于 0 的情况,只有 c 为 Integer.MAX_VALUE 时才会发生
         * 由于计算机二进制的计算原理,此时加上 1 反而会变成 int 类型的最小值,从而小于 0
         */
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 到这一步说明重入次数正常,那么设置新状态值,此时重入次数 +1
        // 这里不需要 CAS,因为在 else if 条件中,当前线程就是已经获取到锁的线程了
        setState(nextc);
        //返回true,方法结束
        return true;
    }
    // 到这一步,说明尝试获取锁没有成功,或者重入锁的线程不是当前线程,那么返回 false
    // 之后执行 AQS 的 acquire() 后续方法,会被加入到同步队列,执行后面的逻辑
    return false;
}
  • 这该实现中,使用 state 来统计锁被某个线程重入的次数,每重入一次 state + 1,同时注意到内部还会判断加 1 之后的 state 值是否小于 0,这说明 ReentrantLock 并不是无限重入的,最大重入次数就是 Integer.MAX_VALUE,这个值再加上 1 就会变成负数,进而抛出异常
  • nonfairTryAcquire(arg) 方法如果返回 true,则说明当前线程成功获取了锁(第一次获取或者重入获取)
重写的 tryAcquire(arg) 方法实现非公平模式理解

非公平的意思是说:先尝试获取锁的线程并不一定比后尝试获取锁的线程优先获取锁

  • 这里假设线程 A 调用 lock() 方法时执行到 nonfairTryAcquire(),然后发现当前状态值不为 0,又发现当前线程不是线程持有者,则返回 false ,然后当前线程被放入 AQS 同步队列
  • 这时候某个线程 C 释放了锁,state 变成 0,此时线程 B 也调用了 lock() 方法执行到 nonfairTryAcquire(),发现当前状态值为 0 了,所以通过 CAS 设置获取到了该锁,甚至在外面的 lock() 方法中就有可能抢先获取到了锁
  • 明明是线程 A 先请求获取该锁,但是却由后请求的 B 线程获取了锁,这就是非公平的体现。这里线程 B 在获取锁前并没有查看当前 AQS 队列里面是否有比自己更早请求该锁的线程,而是使用了抢夺策略,从这里也能想像出公平模式的实现

公平模式加锁

公平性与否是针对获取锁顺序而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是 FIFO 先进先出

// 创建一个具有给定公平策略的 ReentrantLock,false表示不公平,true表示公平
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

// FairSync 类中的 lock()
final void lock() {
	// 调用 AQS 中的 acquire()
    acquire(1);
}

关于 AQS 中的获取独占式锁 acquire() 方法详情:https://blog.csdn.net/weixin_38192427/article/details/117028828

tryAcquire(arg) 方法的重写(重点)

公平模式 FairSync 对于获取锁的 tryAcquire() 方法的实现

/**
 * FairSync 中的方法,公平的获取锁
 *
 * @param acquires 参数,ReentrantLock 中传递 1
 * @return true 成功  false 失败
 */
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();

    if (c == 0) {
        // hasQueuedPredecessors 方法就是公平性的保证
        if (!hasQueuedPredecessors() &&
        		// CAS 尝试将 state 的值从 0 更新为 acquires
                compareAndSetState(0, acquires)) {
            // 如果 CAS 成功,说明获取到了锁,那么记录获取到的锁的线程
            setExclusiveOwnerThread(current);
            return true;
        }
    // 如果不为 0,那么表示有线程获取到了锁,此时判断是否就是当前线程获取到了锁
	// 如果是,那么表示可重入,执行重入的逻辑
    } else if (current == getExclusiveOwnerThread()) {
    	// 新的 state 应该是此前获取的 state + 1
        int nextc = c + acquires;
        /**
         * 如果小于 0,那么表示重入次数过多(超过了 Integer.MAX_VALUE ),直接抛出异常
         * 新的 state 小于 0 的情况,只有 c 为 Integer.MAX_VALUE 时才会发生
         * 由于计算机二进制的计算原理,此时加上 1 反而会变成 int 类型的最小值,从而小于 0
         */
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 到这一步说明重入次数正常,那么设置新状态值,此时重入次数 +1
        // 这里不需要 CAS,因为在 else if 条件中,当前线程就是已经获取到锁的线程了
        setState(nextc);
        return true;
    }
    return false;
}

/**
 * 是 AQS 中的方法,查询是否有任何线程等待获取锁的时间超过当前线程
 *  * @return 如果有前驱 返回true 否则 返回false
 */
public final boolean hasQueuedPredecessors() {
    // 同步队列尾节点
    Node t = tail;
    // 同步队列头节点
    Node h = head;
    Node s;
    // 如果头结点等于尾节点,则返回false,表示没有线程等待
    // 否则,如果头结点的后继s不为null并且s的线程和当前线程相等,则返回false,表示表示当前线程就是等待时间最长的线程
    return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
}

该方法与 NonfairSync 类非公平模式的 nonfairTryAcquire(int acquires) 方法比较:唯一不同的位置为判断条件多了 hasQueuedPredecessors() 方法,是否有任何线程等待获取锁的时间超过当前线程的判断

hasQueuedPredecessors() 方法

  • 由于同步队列中头节点是当前获取锁的线程,而新加入的节点是加入到尾部,那么队列中的第二个节点代表的线程就是请求优先级最高的,即等待时间最长的线程
  • 如果头节点等于尾节点,表示此时同步队列中没有线程等待;否则,如果头节点的后继 s 不为 null 并且 s 的线程和当前线程相等,表示当前线程就是等待时间最长的线程。这两种情况都返回 false,表示没有线程比当前线程更早地请求获取锁,那么当前线程可以去获得锁
  • 如果该方法返回 true,则表示有线程比当前线程更早地请求获取锁。那么当前线程将不会执行 CAS 操作去获取锁,而是返回 false,保证了线程获取锁的顺序与加入同步队列的顺序一致,很好的保证了公平性,但同时也明显增加了获取锁的成本。为了性能,ReentrantLock 的实现就是默认非公平的

lockInterruptibly() 可中断获取非公平或公平锁

就是当前线程在调用该方法,因为没有获取到锁而被挂起时,如果其他线程调用了当前线程的 interrupt() 方法,则当前线程会被唤醒并抛出 InterruptedException 异常,然后返回

这里的中断与不可中断模式,是 AQS 已经为我们实现好了的,我们只需要调用相应的方法就行了,不需要自己实现

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


public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    // 如果当前线程被中断,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试获取锁
    if (!tryAcquire(arg))
        // 如果没获取到,那么调用 AQS 可被中断的方法
        doAcquireInterruptibly(arg);
}

tryLock() 尝试获取非公平或公平锁

public boolean tryLock() {
	// 调用类 Sync 中的 nonfairTryAcquire(),上面一分析过了
    return sync.nonfairTryAcquire(1);
}
  • 尝试获取锁,如果当前线程获取该锁(第一次获取或者重入获取)那么返回 true,否则返回 false 。该方法不会引起当前线程阻塞

unlock() 释放锁

JUC 中只要是实现 Lock 的锁,那么解锁的方法,一般都统一调用开放给外部的 unlock() 方法

public void unlock() {
    // 调用 AQS 的 release 方法
    sync.release(1);
}


// AQS 的 release 方法
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        // 获取头结点
        Node h = head;
        // 果头结点不为 null 并且状态不等于 0
        if (h != null && h.waitStatus != 0)
            // 那么唤醒头结点的一个出于等待锁状态的后继结点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

tryRelease(int arg) 方法的重写(重点)

tryRelease() 方法是 AQS 中定义的方法,在 AQS 中的实现是抛出异常,需要子类自己重写

/**
 * Sync 的 tryRelease 实现,尝试释放锁
 *  * @param releases 参数,ReentrantLock 中传递 1
 * @return true 成功 false 失败
 */
protected final boolean tryRelease(int releases) {
    // 获取释放锁之后的 state 值 c,ReentrantLock 中是减去 1
    int c = getState() - releases;
    // 如果当前线程不是获取锁的线程,那么抛出 IllegalMonitorStateException 异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // free 用于记录释放成功与否的标志位,默认为 false
    boolean free = false;
    // 如果 c 为 0,那表示完全释放了锁,此时清除记录线程 free 为 true
    // 否则,说明该锁被重入了,那么 lock 了几次就需要 unlock 几次,因为每一次只是 state 减去 1,并且此次并没有完全释放
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 设置新的 state
    setState(c);
    // 返回 free
    return free;
}
  • ReentrantLocktryRelease() 的实现,其实就是将线程持有锁的次数 state - 1,若减少后 state 值为 0,那么表示线程完全释放锁了,设置获取锁的线程为 null,更新 state 值,返回 true
  • 如果返回 false,说明此次释放锁并没有完全释放。由于执行该方法的线程必然持有锁,故该方法对 state 的更新不需要任何 CAS 操作

ReentrantLock 总结

ReentrantLock 是基于 AQS 实现的独占式可重入锁,同时还有公平模式和非公平模式

非公平模式

从源码中可以看出来,非公平锁的非公平之处在于:在头节点释放锁唤醒后继节点线程的时候,如果有新来的线程在尝试获取锁,那么新来的线程在和后继节点中的线程的竞争中可能获取锁成功,有两个地方都有支持这样的可能性

  • NonfairSync 类实现的 lock() 方法中,使用 CAS 方式获取到锁(只有在当前锁未被任何线程占有时才能成功,通过 state 是否等于 0 来判断)
  • NonfairSync 类实现的 tryAcquire(int acquires) 方法中,使用 CAS 方式获取到锁(只有在当前锁未被任何线程占有时才能成功,通过 state 是否等于 0 来判断)

如果被新来的线程获取到了锁,那么此时获取到锁的线程,不需要加入队列。而队列中被唤醒的后继节点,由于获取不到锁只能继续等待获得锁的节点线程释放了锁之后继续尝试

只要是进入同步队列排了队的节点线程,就只能按照队列顺序去获取锁,而没有排队的线程,则可能直接插队获取锁。如果在上面的两处获取锁都失败后,线程会被构造节点并加入同步队列等待,再也没有先一步获取锁的可能性

可以看出来,非公平模式下,同步队列中等待很久的线程相比还未进入队列等待的线程并没有优先权,甚至竞争也处于劣势:在队列中的线程要等待唤醒,并且还要检查前驱节点是否为头节点。在锁竞争激烈的情况下,在队列中等待的线程可能迟迟竞争不到锁,这也就是非公平模式,在高并发情况下会出现的饥饿问题

但是非公平模式会有良好的性能,直接获取锁线程不必加入等待队列就可以获得锁,免去了构造节点并加入同步队列的繁琐操作,因为加入到队列尾部时也是需要 CAS 竞争的,可能会造成 CPU 的浪费,并且还可能会因为后续的线程等待、唤醒,造成线程上下文切换,非常耗时

公平模式

即使是公平锁也不一定能保证线程调度的公平性,公平模式下,后来的线程调用 tryLock() 方法同样可以不经过排队而获得该锁。因为 tryLock() 方法内部是直接调用的 nonfairTryAcquire() 方法,这个方法我们说了它不具备公平性

参考:https://blog.csdn.net/weixin_43767015/article/details/107131636

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值