zoukankan      html  css  js  c++  java
  • 【JUC】ReenTrantLock公平锁,非公平锁源码分析

    其中有一些值的概念不太清楚,参考了:
    https://blog.csdn.net/lsgqjh/article/details/63685058(这一位大佬,讲的很细!!)
    https://blog.csdn.net/mulinsen77/article/details/84583716
    在此感谢!

    Lock接口功能:

    public interface Lock {
        // 获得锁
    	void lock();
        // 获得锁
        void unlock();
        // lock非阻塞版本,成功返回true
        boolean tryLock();
        // 添加尝试时间,时间到返回false
        boolean tryLock(long time, TimeUnit unit)
        // 返回一个监视器对象
        Condition newCondition();
    }
    

    AQS

    在看ReenTrantLock之前,先大致看看AQS都做了什么:

    这是一个简化的线程队列模型:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t4Mu45st-1586024262498)(image/aqs.png)]

    重要属性:

    • state:代表了资源是否处于锁定状态;

      1:锁定(已经有线程拿锁,如果重入了,此值一直累加)2:未锁定

      线程拿锁,就是通过CAS修改state,修改成功,则拿到锁;

    • Node内部类:

      每一个Node装载一个线程;对线程通过双向链表的方式排队;

    • Node内部类:还定义资源是 独占 / 还是共享
      也就是每个线程都有一个mode,标识是独占,还是共享;

      Node EXCLUSIVE:代表独占;

      Node SHARED:代表共享;

    先看几个重要方法,后面会用到;

    acquire

    acquire:顾名思义获取,获取锁的方法

    tryAcquire:就先当作是获得锁,返回true,没拿到锁,返回false;这个方法是ReenTrantLock的方法,后面会讲;

    addWaiter:如果没拿到锁,将当前要拿锁的线程加入线程队列

    acquireQueued:已经入队完成,会进一步判断,后面讲;主要是判断线程是否被挂起;

    • true:挂起
    • false:拿锁成功了

    所以:

    当:拿锁失败,并且线程被挂起,就会执行selfInterrupt();,执行线程的中断;

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    

    addWaiter

    这个方法就是:(CAS操作入队)

    让当前线程包装成Node,

    队列存在,直接入队

    如果队列不存在,调用enq(node);,初始化队列,再入队

    (入队:将node设置为队列的tail尾部节点,并且与tail双向关联起来)

    private Node addWaiter(Node mode) {
        // 包装线程为Node,并且是独占的
        Node node = new Node(Thread.currentThread(), mode);
        // 拿到线程队列的尾节点
        Node pred = tail;
        // 如果pred存在,即队列非空
        if (pred != null) {
            node.prev = pred;
            // CAS操作成功入队,将Node设置为tail
            if (compareAndSetTail(pred, node)) {
                // 因为是双向链表,要再链一次
                pred.next = node;
                return node;
            }
        }
        // 队列为空,调用enq,初始化队列,并入队
        enq(node);
        return node;
    }
    

    acquireQueued

    此方法:是线程没拿到锁,入队之后执行;

    主要是:如果发现当前线程的前一个结点是队列的head,那么会再次尝试拿锁;

    也就是说,此时线程队列,就俩线程,一个是head,持锁线程,一个是当前线程;

    那么当前线程会不断尝试拿锁(for (;;)

    最终返回的boolean型interrupted

    • true:挂起
    • false:拿锁成功了
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 拿到当前线程的前一任节点
                final Node p = node.predecessor();
                // 发现前任是head,再次尝试拿锁
                if (p == head && tryAcquire(arg)) {
                    // 拿锁成功,node设置为head
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 判断是否将当前线程挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

    ReenTrantLock

    实现Lock接口

    ReenTrantLock只有一个内部属性:就是Sync内部类的锁抽象对象

    // 这是一个父类,两个子类分别实现公平锁,非公平锁
    private final Sync sync;
    

    三个内部类:

    • Sync(继承AQS):锁抽象;
    • NonfairSync(继承Sync):非公平锁抽象;
    • FairSync(继承Sync):公平锁抽象;

    构造器

    我们在创建ReenTrantLock对象,调用构造器时,就会创建不同的Sync(锁抽象的实现)

    // 非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    // 公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

    公平锁源码(拿锁,排队,重入锁)

    当我们调用了lock.lock();

    公平锁下,sync已经是FairSync的实例了;

    调用sync.lock()

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

    然后调用FairSync内部类下的lock方法:(建议点进源码,看下)

    acquire(1):此方法是AQS下的方法;(去上面看!)在内部是调用了下面的tryAcquire方法;

    这个参数1是干嘛的:就代表尝试获取锁;之前AQS的属性state,如果为0表示未锁定;

    这个1就是要通过compareAndSetState(0, acquires)CAS操作进行加锁的;

    (这里也是通过内存地址stateOffset,拿到state的状态,CAS操作不再赘述)

    尝试将state设置为1,即拿到锁;

    重点:tryAcquire方法(实现了拿锁,排队,重入锁)

    static final class FairSync extends Sync {
        final void lock() {
            acquire(1); // 调用AQS acquire方法,前面讲了
        }
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            // 从AQS中拿到当前资源的state状态
            int c = getState();
            // 如果为0,则表示未锁定,可以尝试获取锁
            if (c == 0) {
                // hasQueuedPredecessors是看当前线程队列中是否有其他线程(非公平锁没有此判断)
                // 如果有其他线程,当前线程不允许拿锁,而是去排队
                // 如果没有线程,并且CAS操作将state置1,那么当前线程就拿到了锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    // 设置独占的资源持有者为当前线程,即拿锁,并返回true
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // state非0,即资源已被锁定
            // 判断当前的线程,是不是占用锁的线程
            // 是,则累加state,也就是重入锁的实现
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                // 叠加state状态
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    

    非公平锁源码

    同样是lock()方法,不再赘述,只不过这里的Sync实例,是NonfairSync的实例;

    与公平锁的主要区别:线程是否排队

    直接看NonfairSync

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
    	// 加锁方法
        final void lock() {
            // CAS尝试加锁
            if (compareAndSetState(0, 1))
                // 成功,设置资源独占者为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 底层依然调用下面的tryAcquire
                acquire(1);
        }
    
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    

    nonfairTryAcquire方法是其父类Sync下的方法

    类似于公平锁的tryAcquire方法

    区别是:不再进行hasQueuedPredecessors()方法的判断,直接尝试获取锁

    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) 
                throw new Error("Maximum lock count exceeded");
            // 叠加state状态
            setState(nextc);
            return true;
        }
        return false;
    }
    

    释放锁

    释放锁的过程:

    并不是说直接将state置为0即可,因为可能发生了多次重入;

    每调用一次tryRelease,state减一;

    protected final boolean tryRelease(int releases) {
        // 当前state-1
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // state如果为0,可以释放锁
        if (c == 0) {
            free = true;
            // 独占线程设为null
            setExclusiveOwnerThread(null);
        }
        // 不能释放锁,state设置为c,即:减一操作;
        setState(c);
        return free;
    }
    
  • 相关阅读:
    C++ Primer 读书笔记 第六章
    C++ Primer 读书笔记 第十章
    面试题笔记
    C++ Primer 读书笔记 第八章
    ZedGraph源码学习(三)
    一个简单的代码生成器XML与XLST的应用测试。
    信息导到Execl上.
    SQL相关功能实现.
    ZedGraph源码学习(二)
    EXECL导入(检查服务器版本.包括NPOI方式导入.可以通过配置文件信息导入EXECL)代码记录下.
  • 原文地址:https://www.cnblogs.com/mussessein/p/12638692.html
Copyright © 2011-2022 走看看