ReentrantLock的lock()源码过程的分析

前言

ReentTrantLock其实在jdk1.6以前相比与synchronized效率上和api丰富性上是具有很大区别,
但是之后的话,其实性能上差距不大,但是在api的丰富性上ReentrantLock还是有有时

那为什么在1.6前synchronzed与ReentrantLock相比,性能上差很多呢?

  • 首先当是的synchronzed在当时的需要同过操作系统的互斥量来实现锁,这就需要其在用户态和内核态上进行切换,当是直接上来就是重量级锁;但是在jdk1.6之后呢hotspot对synchronized进行的大量优化,通过锁的升级减少锁造成的开销,最后才到重量级锁
  • ReentrantLock在其实线上使用了大量的cas,自旋,park…很大一部分减少了锁的开销,甚至在并发线程交替执行时,甚至可以忽略不计;

Lock()

情景: t1为第一个进入且很久不释放锁,t2随后进入然后被阻塞

t1进入后的过程:

进入ReentrantLoc的lock首先我们通过构造方法就能知道ReentrantLock是区分公平/非公平锁,对应的方法实现肯定也是不同的,ReentrantLock模式是非公平锁;

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    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();
    }

但是我们先使用公平锁来讲解,可以看到
在这里插入图片描述
进入FairSync的lock方法:

static final class FairSync extends Sync 
        final void lock() {
            acquire(1);
        }

仅仅又一个acquire方法:

/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
    	//如果没有获取到锁 !tryAcquire(arg)=true,才会执行下面的代码
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

首先得看if里面的表达式:如果 !tryAcquire(arg) 为true他才会执行下面的 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
tryAcquire这个方法会尝试去获取锁,现在让我们来看看这个尝试获取锁的代码(有多个实现,我们需要看FairSync):
在这里插入图片描述
FairSync:

 /**
         * 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();
            //这个State就是ReentrantLock中aqs的一个state属性,默认是0,没被人持有
            int c = getState();
            
            if (c == 0) {
            	//如果没有被人持有
            	//!hasQueuedPredecessors():是否需要排队
                if (!hasQueuedPredecessors() &&
	                //cas改变state的值
                    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;
        }
    }

从尝试获取锁的代码我们能看到它锁可重入的关键代码,同时还有一个关键重要的方法就是hasQueuedPredecessors

只有其返回false,!hasQueuedPredecessors才是true,才会加下来的真正获取锁的一系列操作;
接下来我们看看hasQueuedPredecessors方法(为什么需要判断是否需要排队):

/**
     * Queries whether any threads have been waiting to acquire longer
     * than the current thread.
     *
     * <p>An invocation of this method is equivalent to (but may be
     * more efficient than):
     *  <pre> {@code
     * getFirstQueuedThread() != Thread.currentThread() &&
     * hasQueuedThreads()}</pre>
     *
     * <p>Note that because cancellations due to interrupts and
     * timeouts may occur at any time, a {@code true} return does not
     * guarantee that some other thread will acquire before the current
     * thread.  Likewise, it is possible for another thread to win a
     * race to enqueue after this method has returned {@code false},
     * due to the queue being empty.
     *
     * <p>This method is designed to be used by a fair synchronizer to
     * avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>.
     * Such a synchronizer's {@link #tryAcquire} method should return
     * {@code false}, and its {@link #tryAcquireShared} method should
     * return a negative value, if this method returns {@code true}
     * (unless this is a reentrant acquire).  For example, the {@code
     * tryAcquire} method for a fair, reentrant, exclusive mode
     * synchronizer might look like this:
     *
     *  <pre> {@code
     * protected boolean tryAcquire(int arg) {
     *   if (isHeldExclusively()) {
     *     // A reentrant acquire; increment hold count
     *     return true;
     *   } else if (hasQueuedPredecessors()) {
     *     return false;
     *   } else {
     *     // try to acquire normally
     *   }
     * }}</pre>
     *
     * @return {@code true} if there is a queued thread preceding the
     *         current thread, and {@code false} if the current thread
     *         is at the head of the queue or the queue is empty
     * @since 1.7
     */
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

可以看到方法是AQS里面的方法,这就需要涉及到AQS里面的队列这个数据结构,这个数据结构主要就是:AQS的head和tail分别指向这个node链表的头和尾,node节点里面有pre和next指针,分别指向前后节点,还有一个thread属性,指向当是等待的线程;
return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
h!=t这个条件很重要,这里为true才会执行下面的条件;
就会因为很多种情况返回不同;我们此时是t1进入,此时aqs队列是为空,所以直接返回false(后续情况会在绕回这里,请大家注意这个方法,这里我先将t1)
从这里t1返回false ,那么接下来就直接cas设置state打断一系列操作,然后一层层返回,这样就上锁成功;

可以看到,如果没有锁的竞争,t1的上锁十分顺利,直接在jdk代码级别就就上锁了,连队列都没有入

如果两个线程交替执行的 reentrantLock是很快的,直接在代码层面上就解决了,不用到内核态,所以与当是的synchronized相比定然是快

那么此时t1如果一直持有锁,t2来了:

lock->tryacquire():

/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

现在开始他与t2有竞争的关系了,那么我们一步步分析下

1:首先是尝试是否能获取锁

    /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
		
		//此时t1还在一直持有锁
        protected final boolean tryAcquire(int acquires) {
        	//t2
            final Thread current = Thread.currentThread();
            //1
            int c = getState();
            //false
            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;
            }
            //返回false出去
            return false;
        }
    }
  • 我们可以看到返回false出去,那么!tryAcquire就是true了,其之后的条件方法就不会被短路

此时t2就得开始执行这里:acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
首先执行addWaiter(Node.EXCLUSIVE), arg):这个也是aqs里面的方法

/**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
    	//创建node,thread=currentThread
        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) {
        	//队列已经初始化执行下面
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //队列没有被初始化,初始化方法:enq
        //t2执行
        enq(node);
        return node;
    }

可以通过分析,t2得执行enq(node)方法
enq:

 /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
     //t2
    private Node enq(final Node node) {
    	//死循环(自旋)
        for (;;) {
        	//获得尾节点
            Node t = tail;
            //尾节点为空
            if (t == null) { // t2的第一次循环进入这里
            	//新建立过空的node,同时cas将head指向这个空node
                if (compareAndSetHead(new Node()))
                	//将尾指针指向这个节点
                    tail = head;
            } else {//t2的第二次循环进入这里,将自己维护进这个链表,同时返回方法出去
            	//如果不为空,直接一套设置:等于是将自己这个节点加入这个链表
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    //返回方法,跳出循环
                    return t;
                }
            }
        }
    }

此时 通过addWaiter 和 enq方法,t2初始化了aqs队列,同时自己也维护进去了;
那么此时就来到了acquireQueued(addWaiter(Node.EXCLUSIVE), arg)的acquireQueued方法
这个也是个aqs很重要的方法:
acquireQueued:

/**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @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 (;;) {
            	//p=获取到队列中的自己前一个节点
            	//此时因为就只有t2和一个空节点,所以t2的前一个节点:空节点-》head
            	///判断自己的上一个节点是不是头部,如果是,那就代表他是第二个,他就可以去尝试一下获取锁,因为可能在他入队的时候拿锁的线程释放了锁
                final Node p = node.predecessor();
                //前一个节点为head,返回true=》tryAcquire在尝试获取锁
                if (p == head && tryAcquire(arg)) {
                	//如果此时t1释放,获取成功的话
                	//将自己设置为头节点,同时删除全部前面节点的引用让其被gc掉
                	//现在aqs队列中就只有自己这个node 同时node的thread也为null
                	//自己拿到了锁,自己应该出去这个队列,这样持有锁的那个线程永远不再队列
					//将head指向自己,同时自己的thread=null,这样设计就能让持有锁的线程永远不参与排队
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //阻塞
                //t1没释放锁,所以t2会执行下面,如果shouldParkAfterFailedAcquire为true就会被park中断掉,shouldParkAfterFailedAcquire很重要
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

setHead(将自己设置为头节点同时thread=null):

/**
     * Sets head of queue to be node, thus dequeuing. Called only by
     * acquire methods.  Also nulls out unused fields for sake of GC
     * and to suppress unnecessary signals and traversals.
     *
     * @param node the node
     */
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

shouldParkAfterFailedAcquire

 /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    	//t2进入,pre前一个节点为node
    	//ws是 waitterStatus,是当前线程的状态,从头到尾默认都是没有给这个传过值,ws=0
        int ws = pred.waitStatus;
        //第一次:ws=0(默认)第二次进入:ws=-1
        //第一次:false
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        	//第一次:进入这里执行
            /*
             * 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.
             */
             //改变waitStatus=Node.SIGNAL=-1
             //为什么要帮上一个节点改为-1?
             //可以在自旋一次同时帮上一个节点将状态改为休眠-1,同时-1代表当前节点的后一个节点在排队
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //第一次由于waitstatus=0,直接返回false
        return false;
    }

可以看到第一次进入因为ws变量默认为0,所以执行完cas改变ws为signal(-1)后就返回false
这样返回到shouldParkAfterFailedAcquire中再看看:

inal 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;
                }
                //第一次shouldParkAfterFailedAcquire返回false,短路
                //接下来直接执行下一次for循环(for在自旋一次)
                //接下来如果再次获取锁没有成功在进入这个shouldParkAfterFailedAcquire方法中,由于上次改成了SIGNAL所以返回true,直接进入parkAndCheckInterrupt进行park线程阻塞了
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

这样总结:t2由于t1一直没释放锁:首先会进行队列初始化,然后在判断上一个节点是否为head,如果是还可以在获取一次锁,如果这次没获取成功,在进入shouldParkAfterFailedAcquire中ws变量的值默认为0的问题上会再次让for循环自旋,从而在进行tryAcquire一次;所以如果当前线程是第一个被阻塞的就会自旋尝试获取锁两次
同时又一个问题,为什么要初始化一个thread为null的空头节点?同时shouldParkAfterFailedAcquire在这里又帮自己的上一个节点的waitstate节点改为SIGNAL(-1)?

我的理解:其实一直都会有一个thread为null的在第一个,同时如果自己出队后也会将前一个给踢开,将head指向自己,同时自己的thread=null,这样设计就能让持有锁的线程永远不参与排队,同时这个null的节点设计,可以理解为当切排队线程知道前面又线程拿着锁,但是不知道是谁
帮上一个节点将状态改为休眠-1,同时-1代表当前节点的后一个节点在排队

在这里插入图片描述
明天早补完非公平的情况?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值