首页 > 其他分享 >ReentrantLock

ReentrantLock

时间:2022-08-20 16:47:00浏览次数:57  
标签:return lock ReentrantLock 节点 获取 线程 final

简介

  • ReentrantLock重入锁,是实现Lock接口的一个类。
  • 支持重入性(表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞,synchronized隐式支持重入性)
  • ReentrantLock还支持公平锁和非公平锁两种方式。

 

ueapoq  (ιμηαι.ιηι :Ιμη ιηομΚΚΙ.  ΊΑς  ΔΙΟΙ

 

 

ReentrantLock是J.U.C包下提供的独占锁锁,根据其名称可知,该锁是可重入的。

如果线程重入了一个锁,那么ReentrantLock的锁计数器会加1,每个解锁请求,锁计数器减1。当锁计数器为0的时候,表示资源被解锁了。

 

synchronized通过操作Mark Word实现同步,锁标识存储于Mark Word中,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现;ReentrantLock通过AQS(抽象同步队列)实现同步,锁标识存储于AQS的state属性中。

 

ReentrantLock在上锁时,会根据实例化时指定的策略去获取锁,默认为非公平锁。如果上锁成功,锁状态值+1(重入,最大次数为 Integer.MAX_VALUE),并将锁持有者设置为当前线程实例。在 Sync 内部维护了一个队列,存放了所有上锁失败的线程。公平锁在上锁前,会检查在自己前面是否还有其他线程等待,如果有就放弃竞争,继续等待。而非公平锁会抓住每个机会,不管是否前面是否还有其它线程等待,只顾上锁

 

ReetrantLock在释放锁时,将状态计数器减一(重入),当状态计数器为0时,锁可用。此时再从等待队列中寻找合适的线程唤醒,默认从队首开始,如果队列正在更新中,且未找到合适的线程,那么从队尾开始寻找。

 

ReentrantLock实现的锁又可以分为两类,分别是公平锁和非公平锁,分别由ReentrantLock类中的两个内部类FairSync和NonfairSync来实现。FiarSync和NonfairSync均继承了Sync类,而Sync类又继承了AbstractQueuedSynchronizer(AQS)类,所以ReentrantLock最终是依靠AQS来实现锁的。

 

重要方法

 

public class ReentrantLock implements Lock, java.io.Serializable {}

 

// 获得锁方法,获取不到锁的线程会到同步队列中阻塞排队

void lock();

// 获取可中断的锁

void lockInterruptibly() throws InterruptedException;

// 尝试获得锁,如果锁空闲,立马返回 true,否则返回 false

boolean tryLock();

// 带有超时等待时间的锁,如果超时时间到了,仍然没有获得锁,返回 false

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

// 释放锁

void unlock();

// 得到新的 Condition

Condition newCondition();

 

// 同步器 Sync 的两个子类锁

static final class FairSync extends Sync {}

static final class NonfairSync extends Sync {}

abstract static class Sync extends AbstractQueuedSynchronizer {}

 

 

构造器

 

ReentrantLock 构造器有两种

 

//无参数构造器,相当于 ReentrantLock(false),默认是非公平的

public ReentrantLock() {

    sync = new NonfairSync();

}

 

public ReentrantLock(boolean fair) {

    sync = fair ? new FairSync() : new NonfairSync();

}

 

公平锁

FairSync 公平锁只实现了 lock 和 tryAcquire 两个方法,lock 方法非常简单,如下:

 

// acquire 是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待

 

final void lock() {

    acquire(1);

}

 

在 FairSync 并没有重写 acquire 方法代码。调用的为 AbstractQueuedSynchronizer 的代码,如下:

 

public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

        selfInterrupt();

}

 

lock  lock  FairSync.lock  NonfairSync.l  ock  AQS.acquire  AQS.acquire  FairSync.  tryAcquire  Sync.  NonfairSync.  nonfairTryAc  tryAcquire  quire

 

 

使用

 

Lock lock = new ReentrantLock();

lock.lock();

try {

  doSomething();

}finally {

  lock.unlock();

}

 

锁实现

 

AbstractQueuedSynchronizer的实现类,锁的获取和释放都是基于Sync,对AbstractQueuedSynchronizer不清楚的,可以看下另一篇

abstract static class Sync extends AbstractQueuedSynchronizer {

 

        //抽象方法,由NonfairSync和FairSync进行实现,公平的获取锁,还是非公平的获取锁

        abstract void lock();

 

        //在NonfairSync中使用到,非公平的获取锁

        //@param acquires 要获取的锁数

        final boolean nonfairTryAcquire(int acquires) {

            //获取当前要加锁的线程

            final Thread current = Thread.currentThread();

            //获取锁的状态,即AQS的属性state值

            int c = getState();

            //如果锁的状态等于0,表示处于无锁状态

            if (c == 0) {

                //使用CAS更新锁状态,将锁状态更新成要获取的锁数

                if (compareAndSetState(0, acquires)) {

                    //如果CAS更新锁状态成功,表示获取锁成功,将当前线程设置为占有锁的线程,即设置属性exclusiveOwnerThread为当前线程

setExclusiveOwnerThread(current);

                    //返回加锁成功

                    return true;

                }

            }

            //当前锁已被占有,判断占有锁的线程是否是当前线程,如果不是直接返回获取锁失败

            else if (current == getExclusiveOwnerThread()) {

                //锁的原有状态加上传入进来要获取的锁数得到新的锁状态值

                int nextc = c + acquires;

                //如果计算出的状态值是负数,直接抛出Error错误,但是感觉这里会有些问题,比如原来的锁状态值为1,传入-1也会把锁给释放掉,这样加锁操作就变成了释放锁操作

                if (nextc < 0) // overflow

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

                //设置锁的状态值为新的状态值nextc

                setState(nextc);

                //返回获取锁成功

                return true;

            }

            //返回获取锁失败

            return false;

        }

 

        //此方法在ReentrantLock的unLock方法中使用到,释放锁,修改锁的状态         

        //此方法只能在占有锁的线程调用,即unLock方法只能在持有锁的线程进行锁的释放

        //@param releases 要释放的锁数

        protected final boolean tryRelease(int releases) {

            //得到锁的新状态值

            int c = getState() - releases;

            //如果当前线程不是持有锁的线程,直接抛出IllegalMonitorStateException异常

            if (Thread.currentThread() != getExclusiveOwnerThread())

                throw new IllegalMonitorStateException();

            //释放锁是否成功的标志位

            boolean free = false;

            //如果新的锁状态值为0

            if (c == 0) {

                //将释放锁是否成功的标志位设置为成功

                free = true;

                //将占有独占锁的线程,即属性exclusiveOwnerThread置为空

setExclusiveOwnerThread(null);

            }

            //设置锁的状态

            setState(c);

            //返回释放锁成功

            return free;

        }

 

        //判断当前线程是否是持有锁的线程,如果是返回true,否则返回false         

        protected final boolean isHeldExclusively() {

            //返回当前线程是否是持有锁的线程

            return getExclusiveOwnerThread() == Thread.currentThread();

        }

 

        //创建条件变量实例ConditionObject

        final ConditionObject newCondition() {

            //返回新建的ConditionObject实例

            return new ConditionObject();

        }

 

        //获取占有锁的线程

        final Thread getOwner() {

            //如果当前处于无锁状态,返回null,否则返回占有锁的线程

            return getState() == 0 ? null : getExclusiveOwnerThread();

        }

 

        //得到锁的被获取数,也是锁的状态,只能在持有锁的线程操作才能获取到锁的状态,即锁的被获取数,否则直接返回0

        final int getHoldCount() {

            //只能在持有锁的线程操作才能获取到锁的状态,即锁的被获取数,否则直接返回0

            return isHeldExclusively() ? getState() : 0;

        }

 

        //判断锁是否有被线程占有,即锁的状态是否是处于加锁的状态

        final boolean isLocked() {

            //锁的状态不等于0,表明锁被线程占有,锁状态处于加锁状态

            return getState() != 0;

        }

 

        //从工作流中得到锁的对象,此方法目前没有使用到

        private void readObject(java.io.ObjectInputStream s)

            throws java.io.IOException, ClassNotFoundException {

            s.defaultReadObject();

            //重新设置锁的状态

            setState(0); // reset to unlocked state

        }

}

 

NonfairSync

 

static final class NonfairSync extends Sync {

        private static final long serialVersionUID = 7316153563782823691L;

 

        //Sync的抽象lock方法的重写,非公平的获取锁,在Reentrantlock的lock方法使用到

        final void lock() {

            //使用CAS将锁的状态从0更新成1,即加锁操作

            if (compareAndSetState(0, 1))

                //如果加锁成功,将当前线程设置为占有锁的线程,即设置属性exclusiveOwnerThread为当前线程

setExclusiveOwnerThread(Thread.currentThread());

            else

                //NonfairSync从AQS中继承下来的方法,下面在讲锁的获取时会进行详细的介绍

                acquire(1);

        }

 

//NonfairSync重写了AbstractQueuedSynchronizer的tryAcquire模板方法,否则AQS中的tryAcquire方法会直接抛出  UnsupportedOperationException异常

        //tryAcquire方法在acquire中使用到,非公平的获取锁都是基于此方法

        //@param acquires 要获取的锁数

        protected final boolean tryAcquire(int acquires) {

            //nonfairTryAcquire方法,在上面Sync内部中有进行介绍,非公平的获取锁,无需判断同步队列中前面是否有节点也在获取锁

            return nonfairTryAcquire(acquires);

        }

}

 

FairSync

 

static final class FairSync extends Sync {

        //Sync的抽象lock方法的重写,公平的获取锁,在Reentrantlock的lock方法使用到

//FairSync的lock方法和NonfairSync的lock方法的区别是,NonfairSync的lock方法会尝试先获取锁,如果锁获取不到才会调用acquire方法,acquire内部也会尝试再获取锁,如果获取不到加入到同步队列中循环获取锁

        final void lock() {

            //FairSync 从AQS中继承下来的方法,下面在讲锁的获取时会进行详细的介绍

            acquire(1);

        }

 

//NonfairSync重写了AbstractQueuedSynchronizer的tryAcquire模板方法,否则AQS中的tryAcquire方法会直接抛出UnsupportedOperationException异常

        //tryAcquire方法在acquire中使用到,公平的获取都是基于此方法

//tryAcquire方法和NonfairSync的tryAcquire方法不同的是需要调用hasQueuedPredecessors方法,判断头节点的下一个节点的线程是否是当前线程,如果不是表明前面有等待获取锁的线程

        //@param acquires 要获取的锁数

        protected final boolean tryAcquire(int acquires) {

            //获取当前要加锁的线程

            final Thread current = Thread.currentThread();

            //获取锁的状态,即AQS的属性state值

            int c = getState();

            //如果锁的状态等于0,表示处于无锁状态

            if (c == 0) {

                //调用从AQS继承下来的hasQueuedPredecessors方法判断同步队列是否有获取锁的节点的线程,如果是就不执行直接获取锁

                if (!hasQueuedPredecessors() &&

                    //如果AQS同步队列中没有等待要获取锁的节点的线程,使用CAS更新锁的状态

                    compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

                    //返回公平的获取锁成功

                    return true;

                }

            }

            //如果当前线程是占有锁的线程

            else if (current == getExclusiveOwnerThread()) {

                //锁的原有状态加上传入进来要获取的锁数得到新的锁状态值

                int nextc = c + acquires;

                //如果计算出的状态值是负数,直接抛出Error错误

                if (nextc < 0)

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

                //设置锁的状态值为新的状态值nextc

                setState(nextc);

                //返回公平的获取锁成功

                return true;

            }

            //返回公平的获取锁失败

            return false;

        }

}

 

独占模式加锁机制

 

lock

 

加锁时首先使用CAS算法尝试将state状态变量设置为1,设置成功后,表示当前线程获取到了锁,然后将独占锁的拥有者设置为当前线程;

如果CAS设置不成功,则进入Acquire方法进行后续处理。

 

final void lock() {

    // 使用CAS算法尝试将state状态变量设置为1

    if (compareAndSetState(0, 1))

        // 设置成功后,表示当前线程获取到了锁,然后将独占锁的拥有者设置为当前线程

setExclusiveOwnerThread(Thread.currentThread());

    else

        // 进行后续处理,会涉及到重入性、创建Node节点加入到队列尾等

        acquire(1);

}

 

Acquire

 

acquire(1) 方法是AQS提供的方法

 

public final void acquire(int arg) {

    /**

     * 使用tryAcquire()方法,让当前线程尝试获取同步锁,获取成的话,就不会执行后面的acquireQueued()

     * 方法了,这是由于 && 逻辑运算符的特性决定的。

     *

     * 如果使用tryAcquire()方法获取同步锁失败的话,就会继续执行acquireQueued()方法,它的作用是

     * 一直死循环遍历同步队列,直到使addWaiter()方法创建的节点中线程获取到锁。

     *

     * 如果acquireQueued()返回的true,这个true不是代表成功的获取到锁,而是代表当前线程是否存在

     * 中断标志,如果存在的话,在获取到同步锁后,需要使用selfInterrupt()对当前线程进行中断。

     */

    if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

        selfInterrupt();

}

 

tryAcquire

//NonfairSync 非公平锁中重写了AQS的tryAcquire()方法

final boolean nonfairTryAcquire(int acquires) {

    // 当前线程

    final Thread current = Thread.currentThread();

    // 获取当前state同步状态变量值,由于使用volatile修饰,单独的读写操作具有原子性

    int c = getState();

    // 如果状态值为0

    if (c == 0) {

        // 使用compareAndSetState方法这个CAS算法尝试将state同步状态变量设置为1 获取同步锁

        if (compareAndSetState(0, acquires)) {

            // 然后将独占锁的拥有者设置为当前线程

            setExclusiveOwnerThread(current);

            return true;

        }

    }

    // 如果拥有独占锁的的线程是当前线程的话,表示当前线程需要重复获取锁(重入锁)

    else if (current == getExclusiveOwnerThread()) {

        // 当前同步状态state变量值加1

        int nextc = c + acquires;

        if (nextc < 0) // overflow

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

        // 写入state同步状态变量值,由于使用volatile修饰,单独的读写操作具有原子性

        setState(nextc);

        return true;

    }

    return false;

}

 

protected final boolean tryAcquire(int acquires) {

    final Thread current = Thread.currentThread();

    int c = getState();

    if (c == 0) {

        /**

         * 通过对比源码发现,公平锁比非公平锁多了这块代码: !hasQueuedPredecessors()

         * hasQueuedPredecessors() 是做什么呢?就是判断当前同步队列中是否存在节点,如果存在节点呢,

         * 就返回true,由于前面有个 !,那么就是false,再根据 && 逻辑运算符的特性,不会继续执行了;

         *

         * tryAcquire()方法直接返回false,后面的逻辑就和非公平锁的一致了,就是创建Node节点,并将

         * 节点加入到同步队列尾; 公平锁:发现当前同步队列中存在节点,有线程在自己前面已经申请可锁,那自己就得乖乖的向后面排队去。

         *

         */

        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;

}

 

addWaiter

private Node addWaiter(Node mode) {

    // model参数是独占模式,默认为null;

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

    // 将当前同步队列的tail尾节点的地址引用赋值给pre变量

    Node pred = tail;

    // 如果pre不为null,说明同步队列中存在节点

    if (pred != null) {

        // 当前节点的前驱结点指向pre尾节点

        node.prev = pred;

        // 使用CAS算法将当前节点设置为尾节点,使用CAS保证其原子性

        if (compareAndSetTail(pred, node)) {

            // 尾节点设置成功,将pre旧尾节点的后继结点指向新尾节点node

            pred.next = node;

            return node;

        }

    }

    // 如果尾节点为null,表示同步队列中还没有节点,enq()方法将当前node节点插入到队列中

    enq(node);

    return node;

}

 

acquireQueued

final boolean acquireQueued(final Node node, int arg) {

    // 标志cancelAcquire()方法是否执行

    boolean failed = true;

    try {

        // 标志是否中断,默认为false不中断

        boolean interrupted = false;

        for (;;) {

            // 获取当前节点的前驱结点

            final Node p = node.predecessor();

            /**

             * 如果当前节点的前驱结点已经是同步队列的头结点了,说明了两点内容:

             * 1、其前驱结点已经获取到了同步锁了,并且锁还没释放

             * 2、其前驱结点已经获取到了同步锁了,但是锁已经释放了

             *

             * 然后使用tryAcquire()方法去尝试获取同步锁,如果前驱结点已经释放了锁,那么就会获取成功,

             * 否则同步锁获取失败,继续循环

             */

            if (p == head && tryAcquire(arg)) {

                // 将当前节点设置为同步队列的head头结点

                setHead(node);

                // 然后将当前节点的前驱结点的后继结点置为null,帮助进行垃圾回收

                p.next = null; // help GC

                failed = false;

                // 返回中断的标志

                return interrupted;

            }

            /**

             * shouldParkAfterFailedAcquire()是对当前节点的前驱结点的状态进行判断,以及去针对各种

             * 状态做出相应处理,由于文章篇幅问题,具体源码本文不做讲解;只需知道如果前驱结点p的状态为

             * SIGNAL的话,就返回true。

             *

             * parkAndCheckInterrupt()方法会使当前线程进去waiting状态,并且查看当前线程是否被中断,

             * interrupted() 同时会将中断标志清除。

             */

            if (shouldParkAfterFailedAcquire(p, node) &&

                parkAndCheckInterrupt())

                // 中断标志置为true

                interrupted = true;

        }

    } finally {

        if (failed)

            /**

             * 如果for(;;)循环中出现异常,并且failed=false没有执行的话,cancelAcquire方法

             * 就会将当前线程的状态置为 node.CANCELLED 已取消状态,并且将当前节点node移出

             * 同步队列。

             */

            cancelAcquire(node);

    }

}

 

独占模式释放锁

 

Unlock

public void unlock() {

    // 释放锁时,需要将state同步状态变量值进行减 1,传入参数 1

    sync.release(1);

}

release

public final boolean release(int arg) {

    // tryRelease方法:尝试释放锁,成功true,失败false

    if (tryRelease(arg)) {

        Node h = head;

        if (h != null && h.waitStatus != 0)

            // 头结点不为空并且头结点的waitStatus不是初始化节点情况,然后唤醒此阻塞的线程

            unparkSuccessor(h);

        return true;

    }

    return false;

}

 

tryRelease

protected final boolean tryRelease(int releases) {

    // 当前state状态值进行减一

    int c = getState() - releases;

    // 如果当前独占锁的拥有者不是当前线程,则抛出 非法监视器状态 异常

    if (Thread.currentThread() != getExclusiveOwnerThread())

        throw new IllegalMonitorStateException();

    boolean free = false;

    if (c == 0) {

        free = true;

        setExclusiveOwnerThread(null);

    }

    // 更新state同步状态值

    setState(c);

    return free;

}

 

unparkSuccessor

private void unparkSuccessor(Node node) {

    // 获取头结点waitStatus

    int ws = node.waitStatus;

    if (ws < 0)

        compareAndSetWaitStatus(node, ws, 0);

    // 获取当前节点的下一个节点

    Node s = node.next;

    // 如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled状态的节点

    if (s == null || s.waitStatus > 0) {

        s = null;

        // 就从尾部节点开始找,到队首,找到队列第一个waitStatus<0的节点。

        for (Node t = tail; t != null && t != node; t = t.prev)

            if (t.waitStatus <= 0)

                s = t;

    }

    // 如果当前节点的后继结点不为null,则将其节点中处于阻塞状态的线程unpark唤醒

    if (s != null)

        LockSupport.unpark(s.thread);

}

 

标签:return,lock,ReentrantLock,节点,获取,线程,final
From: https://www.cnblogs.com/gustavo/p/16608053.html

相关文章