zoukankan      html  css  js  c++  java
  • ReetrantLock源码解析-Java8

    一.ReentrantLock介绍

    1.1介绍

      ReentrantLock,可重入锁,首先是一个锁,独占锁,可重入(也就是当前线程获取锁后,还可以再次获取该锁);

      

    1.2API介绍

      ReentrantLock有多个接口,这也是相对于synchronized的一个优势吧,可以灵活地对锁进行控制,而不用像synchronized(没有获得monitor则只能阻塞死等)。

    // 创建reetrantLock对象,默认使用非公平锁
    ReentrantLock reentrantLock = new ReentrantLock();
    
    // ReentantLock构造器可以接收一个参数(是否为公平锁)
    ReentrantLock reentrantLock = new ReentrantLock(true);
    
    
    // 尝试获取锁,如果没有获取到锁,则一直等待(不响应中断)
    reentrantLock.lock();
    
    // 尝试获取锁,如果没有获取到锁,则一直等待,但是可以响应中断
    // reentrantLock.lockInterruptibly();
    
    // 释放锁,如果没有获取到锁就释放锁,会抛出IllegalMonitorStateException异常
    reentrantLock.unlock();
    
    // 查看线程获取该锁的次数
    reentrantLock.getHoldCount();
    
    // 判断锁是否为公平锁
    reentrantLock.isFair();
    
    // 判断锁是否被某个线程持有
    reentrantLock.isLocked();
    
    // 判断锁是否被当前线程所持有
    reentrantLock.isHeldByCurrentThread();
    
    // 尝试获取锁,如果获取锁失败,则立即返回false,获取锁成功返回true,不会阻塞
    boolean b = reentrantLock.tryLock();
    
    // 尝试获取锁,有超时时间,可以响应中断
    // reentrantLock.tryLock(1, TimeUnit.SECONDS);
    

      

    二.源码分析

    2.1 类关系图

      在看源码之前,需要先了解一下ReentrantLock相关的类关系:

      

       如果看图麻烦,可以看下面的示例:

    package java.util.concurrent.locks;
    
    /**
     * 极简ReetrantLock的定义
     */
    public class ReentrantLock implements Lock, java.io.Serializable {
    
        /**
         * 保存Sync的实例化类(公平锁或者非公平锁)
         */
        private final Sync sync;
    
        /**
         * AbstractQueuedSynchronizer就是传说中的AQS(抽象类)
         */
        abstract static class Sync extends AbstractQueuedSynchronizer {
            // ...
        }
    
        /**
         * 非公平锁
         */
        static final class NonfairSync extends Sync {
    
        }
    
        /**
         * 公平锁
         */
        static final class FairSync extends Sync {
    
        }
    }
    

      需要注意的是,AbstractQueuedSynchronizer(AQS)中有一个state属性,在ReentrantLock中该属性用来保存持有锁的线程获取该锁的次数,比如锁处于自由状态(没有被任何一个线程获取)时,state为0,如果一个线程获取到该锁,则state为1,如果该线程再获取一次该锁,那么state就变为2。

      

    2.2 创建ReentrantLock

      ReentrantLock类有两个构造方法:

      1.无参构造方法(默认使用非公平锁)

      2.接收boolean值,表示是否使用公平锁

    /**
     * 创建一个ReentrantLock对象,默认使用非公平锁
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
    /**
     * 创建一个ReetrantLock对象,传入是否使用公平锁
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

      因为公平锁和非公平锁的差别不是特别大,大部分的流程是相同的,所以先介绍公平锁,然后在介绍非公平锁。  

    2.3 获取锁lock

      调用reentrantLock.lock()方法,会尝试获取锁,如果获取到锁,则执行获取到锁后的操作;如果没有获取到锁,则会一直阻塞等待,并且不会响应中断。

    /**
     * 获得锁
     * 如果锁没有被其他线程持有,那么就当前线程就获取该锁,并且将state置为1,然后返回;
     * 如果当前线程已经持有该锁,那么就将state加1,然后方法返回;
     * 如果锁被其他线程持有,那么当前线程就将阻塞,知道当前线程获取到该锁。
     */
    public void lock() {
        sync.lock(); // 调用FairSync.lock方法
    }
    
    /**
     * 尝试加锁
     */
    final void lock() {
        // 传入的1,是想要设置state的值(如果加锁成功,就将state设为1,表示锁被该线程获取1次)
        acquire(1);
    }
    
    /**
     * 尝试获取锁,不接收中断
     */
    public final void acquire(int state) {
        // 尝试获取锁
        // 如果获取锁失败,则尝试将该线程加入等待队列
        if (!tryAcquire(arg) && acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg)) {
            selfInterrupt();
        }
    }
    
    
    /**
     * 尝试加锁,立即返回加锁的结果,不会阻塞
     *
     * @return true加锁成功;false:加锁失败
     */
    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
    
        // state为0,表示锁没有被其他线程持有(处于自由状态)
        int c = getState();
        
        if (c == 0) {
            // 如果队列中没有其他线程排在当前线程前面,那么就尝试加锁,设置state为1
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                // 如果加锁成功,则将当前线程设置为拥有锁的独占线程
                setExclusiveOwnerThread(current);
    
                // 返回加锁成功
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) { 
            // 判断是否为当前线程是否为拥有锁的独占线程(如果是,则重入)
    
            // 修改state的值,设置为已加锁次数再加1
            int nextc = c + acquires;
            if (nextc < 0) {
                throw new Error("Maximum lock count exceeded");
            }
            setState(nextc);
            return true;
        }
    
        // 否则返回false,表示尝试加锁失败
        return false;
    }
    

      

    2.4 加入阻塞队列-addWaiter

      上面在tryAcquire尝试获取锁失败后,就会将当前线程加入阻塞队列,也就是下面这个方法:

    /**
     * 根据当前线程和传入的mode,创建队列节点并且入队,返回入队的节点
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return 入队的新节点
     */
    private Node addWaiter(Node mode) {
        // 创建入队节点
        Node node = new Node(Thread.currentThread(), mode);
    
        // 尝试快速入队(利用CAS将节点设置到tail的后面)
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
    
            // 将节点插到tail的后面,如果入队成功,则返回入队节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
    
        // 快速入队失败,
        enq(node);
        return node;
    }
    
    /**
     * 节点入队
     * 如果是第一次入队,此时队列为空,此时不会直接入队,
     * 而是会创建一个新节点入队,然后将传入的节点进行入队,并返回要入队节点的前节点
     *
     * @return 返回该节点的前节点
     */
    private Node enq(final Node node) {
        for (; ; ) {
            Node t = tail;
            // 队列为空,则建一个队列的新节点,然后让head指向该节点
            // 注意,此时是创建一个新节点,而不是让要入队的节点加入队列
            if (t == null) {
                if (compareAndSetHead(new Node())) {
                    // 新加入一个元素,此时队列中只有一个元素,所以头尾指向同一个节点
                    tail = head;
                }
            } else {
                // 队列不为空,则将要入队的节点插入到队尾,然后将入队元素的前节点返回
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    

      

    2.5 阻塞入队的线程

      线程入队后,还要将线程进行阻塞,当然在阻塞前,还会再试一下能不能获取锁,如果还不能获取锁,则会再进行一次判断(判断waitStatus的值),再进行阻塞。

    /**
     * 将当前线程加入等待队列,或尝试获取锁,获取失败,则会判断是否需要阻塞,需要阻塞则进行阻塞
     *
     * @param node the node
     * @param arg  the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            // 标记等待过程中是否发生中断
            boolean interrupted = false;
    
            for (; ; ) {
                // 获取前一个节点
                final Node p = node.predecessor();
    
                // 如果前一个节点是头结点,那么就尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    // 如果获取锁成功,则将节点设置为头结点,同时清空该节点的thread和pre属性
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
    
                // 判断是否在获取锁失败后进行park,如果需要park,那么就进行park同时监听中断
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
                    interrupted = true;
                }
            }
        } finally {
            if (failed) {
                cancelAcquire(node);
            }
        }
    }
    

      

    2.6唤醒线程

      在调用lock()尝试获取锁的时候,有一步:

    /**
     * 尝试获取锁,不接收中断
     */
    public final void acquire(int arg) {
        // 尝试获取锁
        // 如果获取锁失败,则尝试将该线程加入等待队列
        if (!tryAcquire(arg) && acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg)) {
            selfInterrupt();
        }
    }
    

      acquireQueued上面已经看到了,就是将加入队列的元素进行阻塞(当然也可能获取到锁),获取到锁则返回true,那么就会执行selfInterrupt(),就是当前线程产生中断

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
    

      然后线程获取到锁后,就会继续执行自己的代码。

    2.7 获取非公平锁

      获取非公平锁,一个线程尝试获取非公平锁时,会直接上来就利用CAS尝试获取锁,如果获取到锁,则将该线程设置为锁的独占线程,否则进行和公平锁一样的排队

    /**
     * 线程尝试进行加锁,如果加锁失败(未获取到锁),则当前线程将阻塞
     */
    final void lock() {
        // 如果加锁成功,则将当前线程排除在外(等待队列)
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
        } else {
            // 获取锁,与公平锁相同
            acquire(1);
        }
    }
    

      

    2.8 非公平锁尝试获取锁

      非公平锁在尝试获取锁的时候,不会判断等待队列中是否有其他线程正在等待,而是尝试使用CAS来抢占锁。如果抢占锁失败后,才会加入等待队列。  

    // 尝试获取非公平锁
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
    
    /**
     * 尝试获取锁(非公平锁)
     */
    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
    
        // 获取state,如果state为0,表示锁除以自由状态
        int c = getState();
        if (c == 0) {
            // 注意锁为空自由状态时,非公平锁并没有像公平锁一样,先判断是否有其他的排队线程(hasQueuedPredecessors)
            // 而是直接尝试利用CAS获取锁
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            // 锁的独占线程和当前线程相同,则进行重入,增加state(此处是加1)
            int nextc = c + acquires;
            if (nextc < 0) {
                throw new Error("Maximum lock count exceeded");
            }
    
            setState(nextc);
            return true;
        }
        return false;
    }
    

      

    2.9 释放锁

      释放锁,分为3种情况:

      1.当前线程不是持有锁的线程,此时释放锁会抛出异常;

      2.当前线程释放锁后,锁处于自由状态,那么需要唤醒队列中的接任者;

      3.当前线程释放锁后,锁仍旧被当前线程持有(发生过重入),测试不会唤醒接任者。

    /**
     * 释放锁,如果锁不是由当前线程持有,则会抛出IllegalMonitorStateException异常
     */
    public void unlock() {
        sync.release(1);
    }
    
    public final boolean sync.release(int arg) {
        // 尝试释放锁
        if (tryRelease(arg)) {
            Node h = head;
            // 如果锁处于自由状态,那么就唤醒阻塞的继任者线程
            if (h != null && h.waitStatus != 0) {
                unparkSuccessor(h);
            }
            return true;
        }
        return false;
    }
    
    /**
     * 尝试释放锁,返回值表示锁是否处于自由状态
     */
    protected final boolean tryRelease(int releases) {
        // 计算释放锁后,state的值为多少(如果为0)表示自由状态
        int c = getState() - releases;
    
        // 如果当前线程不是持有锁的线程,则会抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread()) {
            throw new IllegalMonitorStateException();
        }
    
        boolean free = false;
        // 如果state为0,表示所处于自由状态,将该锁的独占线程标识给清除
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
    
        // 设置state
        setState(c);
        return free;
    }
    

      

      

  • 相关阅读:
    Luogu P1131 时态同步
    Codeforces Round #507 B. Shashlik Cooking
    Codeforces Round #507 A. Palindrome Dance
    Luogu P3818 小A和uim之dark♂逃离 Ⅱ
    Luogu P1373 小a和uim之dark♂逃离 Ⅰ
    Luogu P4822 [BJWC2012]冻结
    Luogu P2575 高手过招(博弈论)
    Luogu P1074靶形数独
    Luogu P2323「皇后游戏」
    GodFly的寻宝之旅·状压DP
  • 原文地址:https://www.cnblogs.com/-beyond/p/13154144.html
Copyright © 2011-2022 走看看