• ReentrantLock源码


    ReentrantLock与Synchronized区别在于后者是JVM实现,前者是JDK实现,属于Java对象,使用的时候必须有明确的加锁(Lock)和解锁(Release)方法,否则可能会造成死锁。

    先来查看ReentrantLock的继承关系(下图),实现了Lock和Serializable接口,表明ReentrantLock对象是可序列化的。

    同时在ReentrantLock内部还定义了三个重要的内部类,Sync继承自抽象类AbstractQueuedSynchronizer(队列同步器)。其后又分别定义了它的两个子类公平锁FairSync和非公平锁NonfairSync。

        /**
         * Base of synchronization control for this lock. Subclassed
         * into fair and nonfair versions below. Uses AQS state to
         * represent the number of holds on the lock.
         */
        abstract static class Sync extends AbstractQueuedSynchronizer {
            private static final long serialVersionUID = -5179523762034025860L;
    
            /**
             * Performs {@link Lock#lock}. The main reason for subclassing
             * is to allow fast path for nonfair version.
             */
            abstract void lock();
    
            /**
             * Performs non-fair tryLock.  tryAcquire is implemented in
             * subclasses, but both need nonfair try for 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;
            }
    
            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() {
                // While we must in general read state before owner,
                // we don't need to do so to check if current thread is owner
                return getExclusiveOwnerThread() == Thread.currentThread();
            }
    
            final ConditionObject newCondition() {
                return new ConditionObject();
            }
    
            // Methods relayed from outer class
    
            final Thread getOwner() {
                return getState() == 0 ? null : getExclusiveOwnerThread();
            }
    
            final int getHoldCount() {
                return isHeldExclusively() ? getState() : 0;
            }
    
            final boolean isLocked() {
                return getState() != 0;
            }
    
            /**
             * Reconstitutes the instance from a stream (that is, deserializes it).
             */
            private void readObject(java.io.ObjectInputStream s)
                throws java.io.IOException, ClassNotFoundException {
                s.defaultReadObject();
                setState(0); // reset to unlocked state
            }
        }
    
        /**
         * Sync object for non-fair locks
         */
        static final class NonfairSync extends Sync {
            private static final long serialVersionUID = 7316153563782823691L;
    
            /**
             * Performs lock.  Try immediate barge, backing up to normal
             * acquire on failure.
             */
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
    
        /**
         * Sync object for fair locks
         */
        static final class FairSync extends Sync {
            private static final long serialVersionUID = -3000897897090466540L;
    
            final void lock() {
                acquire(1);
            }
    
            /**
             * 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;
            }
        }

    然后先看一下ReentrantLock的构造函数:

        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
        /**
         * Creates an instance of {@code ReentrantLock} with the
         * given fairness policy.
         *
         * @param fair {@code true} if this lock should use a fair ordering policy
         */
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    ReentrantLock():无参构造器,默认的是非公平锁。
    ReentrantLock(boolean):有参构造器,根据参数指定公平锁还是非公平锁。

    从这里可以看出,ReentrantLock其实既可以是公平锁也可以是非公平锁,通过参数来进行自定义。

    然后我们看一下加锁方法Lock:

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

    内部是调用了构造器中创建的Sync对象,由于默认的是非公平锁,因此我们先来看一下非公平锁的实现。

            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }

    从方法名compareAndSetState可以看出这是一个CAS操作,我们点进去查看源码,这是在AbstractQueuedSynchronized里面定义的一个方法

        protected final boolean compareAndSetState(int expect, int update) {
            // See below for intrinsics setup to support this
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }

    通过Unsafe对象来进行CAS操作。由于Unsafe里面定义的是Native方法,通过其他语言实现了对内存的直接操作,因此是保证了线程安全的。

    然后我们再来看操作成功后的代码:setExclusiveOwnerThread(Thread.currentThread());

        protected final void setExclusiveOwnerThread(Thread thread) {
            exclusiveOwnerThread = thread;
        }

    这个方法的实现是在AbstractQueuedSynchronized的父类AbstractOwnableSynchronized中进行的,只是记录了当前拥有锁的线程。由于我们在if判断中已经获取到了锁,因此这一步也是线程安全的。由此,非公平锁获取结束。

    然后我们再看看如果获取锁失败后的执行方法:acquire(1);获取锁失败,则说明现在已经有其他线程获取到了锁,并且正在执行代码块里面的内容。我们假设这个线程为B。

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

    这个方法是被定义在AbstractQueuedSynchronized中,里面只有三行代码,但是需要注意,在这里的时候就可能会发生同步执行。

    首先看最下面的如果条件成立执行的方法:selfInterrupt()

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

    这个方法很简单,令当前线程中断。但需要注意的是,这个中断只是把线程里面的中断标志位改为true,并没有实际的对线程进行阻塞。线程阻塞已经在上面的两个判断条件里面完成了。

    然后我们再来看下上面的判断条件:

    首先是tryAcquire(arg),调用非公平锁的tryAcquire(int),里面又调用了Sync的nonfairTryAcquire(int)方法,通过判断当前的锁状态是否等于0,等于则表示没有线程获取锁(实际有可能是线程B已经执行完成并已经释放锁),再次尝试用CAS操作获取锁,获取成功则返回true,并且记录当前线程。如果获取失败,或者锁状态不等于0,则表示已经有线程获取到锁,此时会比较记录的线程是否为当前线程,如果是,则表示是当前线程重入(这里可以看出ReentrantLock是可重入锁),再令锁状态state加1,返回true,否则没有获取到锁返回false。

    在这里我们可以看到tryAcquire()目的是再次判断当前锁是否是可获取状态(线程B已经执行完成并释放锁)以及是否是同一个线程的重入操作。获取锁成功或者是线程重入则返回true,lock方法就此结束。否则继续执行第二个条件判断。

        static final class NonfairSync extends Sync {
    
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
    
        abstract static class Sync extends AbstractQueuedSynchronizer {
            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;
            }
        }

    再次获取锁失败后,会通过addWaiter()将当前线程添加到FIFO队列中。

    在AQS(队列同步器)中通过Node内部类来制定一个双向链表,此链表采取的是先进先出(FIFO)策略。同时定义了一个头结点head和尾节点tail,都使用关键字volatile来保证多线程的可见性。

        private Node addWaiter(Node mode) {
            Node node = new Node(Thread.currentThread(), mode);  //创建新的节点
            // Try the fast path of enq; backup to full enq on failure
            Node pred = tail;
            if (pred != null) { //判断当前尾节点是否等于null
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {  //尾节点不等于null,通过CAS操作将当前节点替换为链表尾节点。替换成功令当前节点作为前一个节点的next节点。替换失败则说明有其他线程正在操作,进入enq进行操作。
                    pred.next = node; 
                    return node; //操作成功,返回当前节点。
                }
            }
            enq(node); //自旋获取锁
            return node;
        }
    
        private final boolean compareAndSetTail(Node expect, Node update) {
            return unsafe.compareAndSwapObject(this, tailOffset, expect, update);  //这里是通过偏移量上值比较来进行值替换。
        }
    
    //进入这个方法有两种可能,一是当前链表没有初始化,等于null,二是当前线程与其他线程竞争添加线程到尾节点失败。
    private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) //当前链表没有初始化,先进行初始化。添加一个新节点作为头节点(代表的是当前正在执行的线程),初始化成功,则令首尾节点都等于该节点。初始化失败,说明已经有其他线程进行了初始化。进入下一个循环。 tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { //当前链表中已经初始化过,将新节点添加在链表末尾,添加成功则返回新节点,添加失败说明有其他线程在竞争添加,进入下一个循环,直到操作成功,当前线程被添加进队列中。 t.next = node; return t; } } } }

    新的线程被添加到队列里面后,再调用方法acquireQueue(Node,int);

    这里可以简单的理解,线程自旋,如果当前线程的前一个节点是头节点(头结点代表获取到锁且正在执行的线程),说明下一个移出队列参与竞争锁的线程是当前线程,再次尝试获取锁,获取到了说明前一个节点已经执行完,令当前节点替换头节点,并返回中断标志位false。

    获取锁失败说明上一个线程仍未执行完,或者锁被其他线程竞争到(新建的线程尚未添加到队列中,可以参与锁竞争),同时如果当前线程的上一个节点不是头节点(说明下一个移出队列竞争锁的线程不是当前线程),都会将线程节点的前一个节点的标志位设置为SIGNAL(表示下一个节点需要被unparking),然后令当前线程中断,暂停循环,等待唤醒。

        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)) {  //如果前一个节点是头节点,说明当前线程是下一个执行的线程,再次尝试获取锁,获取成功则将当前节点作为头节点,去掉后面的所有节点。
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;  //获取锁成功返回false;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) //不会一直循环下去,因为会不断地消耗资源,适时会进入中断,等待被唤醒后才继续自旋。
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
            final Node predecessor() throws NullPointerException { //获取当前节点的前节点
                Node p = prev;
                if (p == null)
                    throw new NullPointerException();
                else
                    return p;
            }
    
        private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            int ws = pred.waitStatus; //在这里Node的线程状态一共有5种情况:SIGNAL=-1,CANCELLED=1,CONDITION=-2,PROPAGATE=-3,以及默认值0
            if (ws == Node.SIGNAL)  //SIGNAL表示唤醒状态
                /*
                 * This node has already set status asking a release
                 * to signal it, so it can safely park.
                 */
                return true;
            if (ws > 0) {  //大于0的只有CANCELLED情况,当前线程被取消执行,因此从队列中剔除
                /*
                 * Predecessor was cancelled. Skip over predecessors and
                 * indicate retry.
                 */
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {  //剩下的不论什么情况,都会利用CAS操作尝试将节点的waitStatus改为SINGAL,不论操作成功还是失败,都会返回false
                /*
                 * waitStatus must be 0 or PROPAGATE.  Indicate that we
                 * need a signal, but don't park yet.  Caller will need to
                 * retry to make sure it cannot acquire before parking.
                 */
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }
    
        private static final boolean compareAndSetWaitStatus(Node node,
                                                             int expect,
                                                             int update) {  //利用CAS操作修改节点的waitStatus值
            return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                            expect, update);
        }
    
        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }
    
        private void cancelAcquire(Node node) {
            // Ignore if node doesn't exist
            if (node == null)
                return;
    
            node.thread = null;
    
            // Skip cancelled predecessors
            Node pred = node.prev;
            while (pred.waitStatus > 0)
                node.prev = pred = pred.prev;
    
            // predNext is the apparent node to unsplice. CASes below will
            // fail if not, in which case, we lost race vs another cancel
            // or signal, so no further action is necessary.
            Node predNext = pred.next;
    
            // Can use unconditional write instead of CAS here.
            // After this atomic step, other Nodes can skip past us.
            // Before, we are free of interference from other threads.
            node.waitStatus = Node.CANCELLED;
    
            // If we are the tail, remove ourselves.
            if (node == tail && compareAndSetTail(node, pred)) {
                compareAndSetNext(pred, predNext, null);
            } else {
                // If successor needs signal, try to set pred's next-link
                // so it will get one. Otherwise wake it up to propagate.
                int ws;
                if (pred != head &&
                    ((ws = pred.waitStatus) == Node.SIGNAL ||
                     (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                    pred.thread != null) {
                    Node next = node.next;
                    if (next != null && next.waitStatus <= 0)
                        compareAndSetNext(pred, predNext, next);
                } else {
                    unparkSuccessor(node);
                }
    
                node.next = node; // help GC
            }
        }

    由此,整个非公平锁的加锁过程结束,总结一下:

    1.如果state位是0,则表示没有线程获取对象锁,通过CAS操作设置state位从0到1,尝试获取锁

    2.获取锁成功,记录当前获取锁的线程。流程结束

    3.获取失败,判断是否是已经获取了锁的线程再次获取(通过第二步里面记录的线程与当前线程判断是否相等),如果是,令state再加1,流程结束

    4.如果不是,将线程添加到FIFO链表队列中,然后进行自旋。

    5.自旋时会判断当前线程是否是head节点的next,如果是则再次尝试获取锁,获取到了后将头节点替换为当前节点,返回false。流程结束

    6.自旋一定次数后仍未获取到锁,或当前线程节点不是下一个参与竞争锁的线程,则进入中断。等待被唤醒后继续自旋。

    公平锁的Lock()方法:

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

    公平锁与非公平锁的加锁方法区别在于,tryAcquire(int)方法的不同。公平锁中要判断队列里第一个线程是否是当前线程,如果是,则允许它获取锁,如果不是,则不能获取。

    下面看一下解锁方法:unlock()

        public void unlock() {
            sync.release(1);
        }

    内部不分公平锁与非公平锁,一律调用AbstractQueuedSynchronized方法的release(int)。

        public final boolean release(int arg) {
            if (tryRelease(arg)) {
                Node h = head;
                if (h != null && h.waitStatus != 0)
                    unparkSuccessor(h);
                return true;
            }
            return false;
        }

    先看第一行的判断:if(tryRelease(arg))

    里面先判断了锁指向的线程与当前线程相等,不相等则抛出异常。

    再令status减1,判断结果是否等于0。等于0说明可以释放锁,将锁指向的线程改为null,status改为0,返回true。

    不等于0则说明仍未全部执行完重入的操作,令status自减一,返回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;
            }

    判断为false则释放锁失败,判断为true则继续执行if里面内容。

    if里面主要是判断了链表队列head里面有等待唤醒的其他线程节点,对他们进行一个唤醒。

        private void unparkSuccessor(Node node) {
            /*
             * If status is negative (i.e., possibly needing signal) try
             * to clear in anticipation of signalling.  It is OK if this
             * fails or if status is changed by waiting thread.
             */
            int ws = node.waitStatus;
            if (ws < 0)
                compareAndSetWaitStatus(node, ws, 0);  //将waitStatus赋值为初始状态0
    
            /*
             * Thread to unpark is held in successor, which is normally
             * just the next node.  But if cancelled or apparently null,
             * traverse backwards from tail to find the actual
             * non-cancelled successor.
             */
            Node s = node.next;
            if (s == null || s.waitStatus > 0) {  //下一个节点等于null或者被取消执行,从尾节点开始向前遍历,找到最头位置上的节点
                s = null;
                for (Node t = tail; t != null && t != node; t = t.prev)
                    if (t.waitStatus <= 0)
                        s = t;
            }
            if (s != null)  //唤醒队列中的下一个可执行的节点。
                LockSupport.unpark(s.thread);
        }

    解锁流程总结:

    1.锁指向的线程与当前线程必须是同一个线程。

    2.锁标志位status必须已经减到0。

    3.判断链表队列不等于null,并且头节点的waitStatus标志位不等于0,需要唤醒下一个节点。否则返回true,业务结束

    4.唤醒下一个节点首先利用CAS操作将waitStatus的标志位改为0,然后再按队列顺序获取下一个节点。

    5.如果获取的新节点等于null,或者waitStatus位等于1(表示已经被取消执行),则从尾节点向前遍历,直到遇见最前面的非null非当前线程节点的节点。

    6.唤醒获取的新节点。业务结束

  • 相关阅读:
    信息论
    学习抓包
    深入学习垃圾kafka
    share data
    【转载】计算图像相似度——《Python也可以》之一
    聊聊java list的使用特性
    log4j多线程以及分文件输出日志
    【转载】JDBC的连接参数的设置导致rowid自动添加到sql
    背包问题
    【转】【动态规划】01背包问题
  • 原文地址:https://www.cnblogs.com/yxth/p/10677528.html
Copyright © 2020-2023  润新知