ReentrantLock 锁机制(非公平锁)源码

作者: Skymiles | 来源:发表于2016-10-08 18:06 被阅读443次

    ReentrantLock和synchronized一样加锁方式,独占的获取同步的对象锁。

        1. 简介

        ReentrantLock内部由Sync类实例实现其中ReentrantLock.FairSync,ReentrantLock.NonfairSync两个实现,也就是常说的公平锁和不公平锁。

    而Sync继承于AbstractQueuedSynchronizer。AbstractQueuedSynchronizer这个类真的很难也很复杂,是构建锁以及实现其他相关同步类的基础框架。本篇文章只能说是对自己看这个类的一点点理解和记录,若有错,请批评指正。

        2. ReentrantLock类的lock()方法

        由于锁Lock的实现都是委托给AbstractQueuedSynchronizer来实现的。因此,就将分析ReenterantLock类中如何获取锁和如何释放锁来理解。

    public voidJava.util.concurrent.locks.ReentrantLock.lock()

    如果该锁没有被另一个线程保持,则获取该锁并立即返回,并将锁的保持计数器设置为1.

    如果当前线程已经保持该锁,则将保持计数加1,并且该方法立即返回。

    如果该锁被另一个线程保持,则出于线程调度的目的,禁用该线程,并且在获得锁之前,该线程一直处于休眠状态,此时锁保持计数被设置为1.

        保持计数就是AQS类的state变量。

    默认ReentrantLock构造器

    public ReentrantLock() {

        sync =new NonfairSync();

    }

    默认是非公平锁,看看NonfairSync类下的lock方法:

    final void lock() {

        if (compareAndSetState(0,1))

            setExclusiveOwnerThread(Thread.currentThread());

        else

            acquire(1);

    }

        compareAndSetState(0, 1) 这个是尝试获取锁,把state的状态从0改为1表示取得锁。这个方法原理是CAS,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做!

        setExclusiveOwnerThread()设置获取锁的线程就是当前线程.

        具体调用的是:

    protected final boolean compareAndSetState (int expect, int update) {

        return unsafe.compareAndSwapInt (this, stateOffset, expect, update);

    }

        unsafe的compareAndSwapInt方法是native的.

    但是我们更关注的是,当前线程申请锁不成功的时候是怎么做的.可以看到是AQS中的acquire(1);

    public final void acquire(int arg) {

    //先尝试获取锁,如果获取锁失败了,acquireQueued则执行

        if( !tryAcquire(arg) && acquireQueued( addWaiter(Node.EXCLUSIVE), arg))

            selfInterrupt();

    }

    后面的方法。注意&&,前面为true后面才执行。获取锁失败后,会将该线程加入等待队列

        这个看起来比较复杂,我们分解以下4个步骤。

    1、如果tryAcquire(arg)成功,那就没有问题,已经拿到锁,整个lock()过程就结束了。如果失败进行操作2。

    2、调用addWaiter方法:将当前线程创建一个独占节点(Node)并且此节点加入CHL队列末尾。进行操作3。

    3、自旋尝试获取锁,失败根据前一个节点来决定是否挂起(park()),直到成功获取到锁。进行操作4。

    4、如果当前线程已经中断过,那么就中断当前线程(清除中断位)。

    下面我们对acquire方法中调用的其它方法一一进行分析。

        tryAcquire(acquires)

    protected final boolean tryAcquire(int acquires) {

        return nonfairTryAcquire(acquires);

    }

    final boolean nonfairTryAcquire (int acquires) {

        final Thread current = Thread.currentThread(); 

        int c = getState();  //对于AQS存在一个state来描述当前有多少线程持有锁

        /*

            如果c等于零,则没有线程持有锁,则将锁给当前线程即可

            如果c不等于,说明当前线程已经获取了锁,这里是当前线程再次要获得锁,所以state           要继续+1

        */

        if(c ==0) {

            if(compareAndSetState(0, acquires)) {

                setExclusiveOwnerThread(current);

                return true;

            }

        }

        else if (current == getExclusiveOwnerThread()) { //判断当前线程是否为AQS的独占线程

            int nextc = c + acquires;

            if( nextc < 0 ) // overflow 

                throw new Error("Maximum lock count exceeded");

            setState(nextc);

            return true;

        }

        //获取锁失败,返回false。

        return false;

        tryAcquire的逻辑是这样的, c = getState() 就是当前没有锁竞争的时候,会再尝试去获得锁.

    current == getExclusiveOwnerThread()):当前线程已经获取锁了,那么锁的记数加1.

    addWaiter(mode)

        如果tryAcquire没有成功,  就执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

        addWaiter是把线程和线程的状态信息封装到一个node对象,加入CHL阻塞链表,Node封装了各种线程状态:

    static final int CANCELLED = 1; //这个状态说明该节点已经被取消。

    static final int SIGNAL = -1; //这个状态说明该节点后续有阻塞的节点 。

    static final int CONDITION = -2; //表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞

    static final int PROPAGATE(-3); //传播共享锁

    0:0代表无状态

        其实就是把当前线程放到一个链表的末尾去.具体怎么放有点讲究,而且用到了无限循环,也就是说,一定要把线程放进链表的!

        static final Node EXCLUSIVE = null; //独占节点模式

        static final Node SHARED = new Node(); //共享节点模式

        addWaiter(mode)中的mode就是节点模式,也就是共享锁还是独占锁模式。

    privateNode addWaiter(Node mode) {

        //当前线程节点,线程的状态信息封装到一个node对象。

        Node node =new Node(Thread.currentThread(), mode);

        Node pred = tail;

        //判断有没有尾节点(也就是前面是否有等待线程)。如果有尾节点,则将当前线程的节点插入到队列的尾部,也就是将当前线程变成尾节点。

        if(pred != null) {

            node.prev = pred;

            //CAS的操作。

            if(compareAndSetTail(pred, node)) {

                pred.next = node;

                return node;

            }

        }

        //如果没有尾节点,说明前面还未有等待线程。调用下面的方法,创建等待队列

        enq(node);

        return node;

    }

    总而言之,addWaiter的目的就是通过CAS把当前现在追加到队尾,并返回包装后的Node实例。

    acquireQueued(node,arg)

    //这个方法是不断地获取锁,直到成功的获取锁,或者阻塞当前线程

    acquireQueued也是个无限循环。就是说要么获取到锁,要么中断当前线程。

    1. acquireQueued方法在无限循环内获取前继节点,判断前继节点是否为head,是就再尝试     获取锁,之后前继节点dequeue出队,node成为head。

    2. 前继节点p != head 或者 前继节点p == head但是tryAcquire失败了,那么应该阻塞当前线     程等待前继唤醒。阻塞之前会再重试一次,还需要设置前继的waitStaus为SIGNAL。

    3. 调用shouldParkAfterFailedAcquire方法,后面的方法是对当前线程进行阻塞并且判断是       否中断。这里注意的是,如果一个线程在等待锁期间这个线程被中断了,这里会将               interrupted赋为true,但是并不return。这个还一直进行for循环,知道这个线程获得了锁,     所以lock()方法不能立即响应中断,必须等线程获得了锁才可以响应中断。对应的可以立       即响应中断的方法为lockInterruptibly()方法。

    //阻塞当前线程

    private final boolean parkAndCheckInterrupt() {

        //阻塞当前线程

        LockSupport.park(this);

        return Thread.interrupted();

    unLock()方法

    public void unlock() {

        sync.release(1);

    非公平锁release方法 非公平锁tryRelease方法

    相关文章

      网友评论

        本文标题:ReentrantLock 锁机制(非公平锁)源码

        本文链接:https://www.haomeiwen.com/subject/nnwuyttx.html