简单实现ReentrantLock,详解ReentrantLock底层原理

目录

前言

一、ReentrantLock和Synchronized的相似点与区别

1.相似点:

2.区别:

二、ReentrantLock的三个特性的实现。

1.公平锁

2.可中断锁

3.超时锁

三、AbstractQueuedSynchronizer类

1.AbstractQueuedSynchronizer结构

2.AbstractQueuedSynchronized相关实现类

2.acquire方法

3.release方法

四 、ReentrantLock类

1.tryAcquire方法

2.hasQueuedPredecessors方法

五、测试该锁,并且附上源码

1.代码测试:

2.ReentrantLock类源码:

3.AbstractQueuedSynchronizer 源码

 4.AbstractOwnableSynchronizer 类源码

总结



前言

ReentrantLock与Synchronized的作用相似,保证了同一时间只有一个线程能访问加锁的区域。


一、ReentrantLock和Synchronized的相似点与区别

1.相似点:

  • 都是可重入锁:也就是说一个线程可以反复获取锁资源。
    • 当线程获取ReentrantLock时,会判断state变量是否不为0(state的大小表示重入了几次),如果不为0就说明有线程获取了锁。这个时候会判断获取锁的线程是否是当前线程,如果是当前线程,则再次获取锁资源并且++state。
  • 都是独占锁:同一时刻,只有一个线程可以获取锁。

2.区别:

  1. 锁的获取方式不同:Synchronized 是隐式锁,它的获取和释放都是由 JVM 自动控制的,不需要显式地进行操作。而 ReentrantLock 是显式锁,需要手动获取和释放锁。

  2. 功能上的区别:相比 Synchronized,ReentrantLock 提供了更多的功能,比如可中断锁、可超时锁、公平锁等,这些功能在某些场景下非常有用。

  3. 性能上的区别:在低并发的情况下,Synchronized 的性能表现通常优于 ReentrantLock。但在高并发情况下,ReentrantLock 的性能通常优于 Synchronized。

  4. 可见性的区别:使用 Synchronized 时,被锁定的代码块或方法中所做的修改会立即对其他线程可见。但是,使用 ReentrantLock 时,需要调用 unlock() 方法来释放锁,才能保证其他线程能够看到修改。

  5. 适用范围的区别:Synchronized 只适用于在单个 JVM 实例中的多线程同步,而 ReentrantLock 可以跨越多个 JVM 实例,甚至可以在分布式环境下进行线程同步。

二、ReentrantLock的三个特性的实现。

1.公平锁

公平锁:当一个新的线程来获取锁的时候,他会看看阻塞队列里面是否有线程在排队。如果有,则不去尝试获取锁,会继续排在队列后面。如果没有才会去获取锁。

非公平锁:当一个新的线程来获取锁的时候。不管阻塞队列里面是否有线程排队,都会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

ReentrantLock根据构造方法传入的参数不同,实现不同类型的锁。

无参构造方法默认是非公平锁。有参构造,传入true为公平锁。false为非公平锁。

在代码里,这两个实现类获取锁的区别则是:

公平锁:

非公平锁:

只相差了一句:hasQueuedPredecessors()

公平锁有这行代码,这行代码用来判断队列里面是否有元素。

在公平锁里,靠这句代码判断出阻塞队列没有等待的线程了,才会去尝试获取锁

2.可中断锁

可中断就是:可以根据打断(interrupt)来退出获取锁的流程。

可中断锁是依靠异常来实现的。当获取锁的途中发现线程被打断了,则会抛出异常。根据异常的特性我们知道,有异常出现的时候,不会继续执行剩下的代码。而会执行catch和finally代码块的内容。正是依靠这个特性,使得线程能依靠中断退出获取锁的过程。

使用lockInterruptibly方法就是使用可打断锁;

lockInterruptibly获取锁的主要实现是:通过for死循环来获取锁。而要跳出这个循环,只有成功获取锁或者被打断抛出异常。

3.超时锁

超时锁:当锁在约定时间内获取不到锁资源后,就会退出获取锁的流程。

作用:为了防止线程因为长时间获取不到锁而一直阻塞住,影响业务的执行。

调用tryLock方法使用超时锁

如何实现超时操作的?

1.循环获取锁的时候,每次都判断剩余时间是否足够,如果不足则放弃获取锁。

2.通过 LockSupport.parkNanos(this, nanosTimeout) 防止获取锁的途中阻塞超时

三、AbstractQueuedSynchronizer类

        ReentrantLock的功能的核心就是AbstractQueuedSynchronizer类,要了解ReentrantLock就要先了解AbstractQueuedSynchronizer

ReentrantLock和AbstractQueuedSynchronizer的关联:

        ReentrantLock的构造方法可以传入一个参数,根据传入的值,则会创建对应的AbstractQueuedSynchronizer实现类的对象。true为公平锁,false为公平锁。

        ReentrantLock的主要方法有加锁(lock)与解锁(unlock),ReentrantLock的加锁(lock)调用的是AbstractQueuedSynchronizer的acquire方法,解锁(unlock)调用的则是realse方法

1.AbstractQueuedSynchronizer结构

        AbstractQueuedSynchronizer名字是队列,但是本质是一个双向链表。有head和tail指针指向队首和对尾节点。同步器中有个Node类,Node类便是链表的节点,每个节点对应一个Node类的实例。Node有prev和next指向前后节点,waitStatus用来存节点的状态,每个节点最主要的就是存有一个线程(初始化的首节点不存线程)。也就是说,AbstractQueuedSynchronizer其实是存储线程的队列。

        该队列的有一个重要的参数就是state,被volatile修饰。同步器(AbstractQueuedSynchronizer)并没有对该变量进行操作,只是提供了操作该变量的方法。而在ReentrantLock中的tryAcquire方法会操作该变量,当state等于0的时候代表没有线程持有锁,当state大于0的时候就代表有线程持有锁,而state的大小就代表该线程重入锁的次数

        该队列还有个Thread参数,保存当前持有该同步器的线程。AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer类,也就是说这个Thread参数来自于AbstractOwnableSynchronizer类。

2.AbstractQueuedSynchronized相关实现类

        AbstractQueuedSynchronizer类不止服务于ReentrantLock类,还有ArrayBlockingQueueSemaphoreCountDownLatchExecutors、CyclicBarrier、Executors等等,因此该类里面有很多跟ReentrantLock不想关的代码存在,本文提取ReentrantLock类和AbstractQueuedSynchronizer类的核心代码进行解释说明。

2.acquire方法

        AbstractQueuedSynchronizer的核心就是acquire方法。对于该同步器的设计思路来说,执行acquire方法就是 尝试入队,acquire方法执行完就是 出队/不需要入队。

        对于ReentrantLock来说acquire方法执行完就是获取锁成功。

(1)tryAcquire方法,同步器并没有实现tryAcquire方法,而是由具体的实现类实现。

        

(2)addWaiter方法,实际的入队方法

(3)acquireQueued方法,入队后的节点会在一个死循环中尝试出队

(4)shouldParkAfterFailedAcquire方法和parkAndCheckInterrupt方法

3.release方法

(1)unparkSuccessor方法。realease方法判断了waitStatus!=0,说明了队列有节点需要被处理,但是该处仍未直接unpark。因为节点可能出现异常状态,需要被处理。

四 、ReentrantLock类

        ReentrantLock类有三个内部类,分别是继承了AbstractQueuedSynchronizer的Sync,和继承了Sync的FairSyncNonFairSync

Sync方法实现了FairSync和FairSync的共同逻辑,也就是tryRelease()方法。而FairSync和NonFairSync为了实现公平锁和非公平锁,则是在tryAcquire方法的实现上有细微区别。

        ReentrantLock主要实现了AbstractQueuedSynchronizeracquire()和release()方法。具体对应的就是ReentrantLocklock()和unlock()方法

ReentrantLock默认是非公平锁,但是可以根据传入的布尔值改变

1.tryAcquire方法

        在公平锁和非公平锁中,该方法的实现区别只有是否有hasQueuedPredecessors方法的这个判断。hasQueuedPredecessors方法判断队列中是否有节点在排队。在公平锁中,必须要按照先后顺序获取锁,所以需要这个判断。

        state变量表示是否加了锁,如果c==0则是没加锁。那么就进行cas,把同步器中的线程设置为当前线程。也就是说,那个线程等于同步器中保存的线程,就表面那个线程获取了锁。如果c!=0的话,也不一定就不能获取锁了,因为该锁是可重入锁。如果尝试获取锁的线程等于同步器中保存的线程,那么就再把state变量加一。

2.hasQueuedPredecessors方法

        判断队列里面是否有节点存在

五、测试该锁,并且附上源码

1.代码测试:

测试结果

2.ReentrantLock类源码:

package tools.myLock;


/**
 * @Auther: duanYL
 * @Date: 2023/10/11/14:39
 * @Description:
 */
public class ReentrantLock {
    private final Sync sync;

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

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

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


    abstract static class Sync extends AbstractQueuedSynchronizer {
        //因为reentrantLock是可以重入的锁,所以tryRelease并不是一定返回true的
        protected final boolean tryRelease(int releases) {

            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();

            int newState = getState() - releases;
            boolean free = false;
            if (newState == 0) {
                setExclusiveOwnerThread(null);
                free = true;
            }
            setState(newState);
            return free;
        }
    }

    //类上加final有啥用
    static final class NonfairSync extends Sync {

        protected final boolean tryAcquire(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 newState = c + acquires;
                if (newState < 0) {
                    throw new Error("Maximum lock count exceeded");
                }
                setState(newState);
                return true;
            }
            return false;
        }
    }

    static final class FairSync extends Sync {
        protected final boolean tryAcquire(int acquires) {
            Thread thread = Thread.currentThread();
            int state = getState();
            if (state == 0) {   //说明该线程是第一个竞争锁的
                if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(thread);
                    return true;
                }
            } else if (thread == getExclusiveOwnerThread()) {
                int newState = state + acquires;
                if (newState < 0) {
                    throw new Error("Maximum lock count exceeded");
                }
                setState(newState);
                return true;
            }
            return false;
        }
    }

}

3.AbstractQueuedSynchronizer 源码

package tools.myLock;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.locks.LockSupport;

/**
 * @Auther: duanYL
 * @Date: 2023/10/11/14:51
 * @Description: 该同步器的核心就是acquire方法。对于该同步器的思路来说,
 * 执行acquire方法就是入队,acquire方法执行完就是出队。
 */
public class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {

    private transient volatile Node head;
    private transient volatile Node tail;
    private volatile int state; //同步器并没有操作该变量,而是交于实现类来操作

    // VarHandle mechanics  每个VarHandle对应一个变量,用来操作变量
    private static final VarHandle HEAD;
    private static final VarHandle TAIL;
    private static final VarHandle STATE;

    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class);
            HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", AbstractQueuedSynchronizer.Node.class);
            TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", AbstractQueuedSynchronizer.Node.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
        Class<?> ensureLoaded = LockSupport.class;
    }

    protected final int getState() {
        return state;
    }

    protected final void setState(int newState) {
        state = newState;
    }

    protected final boolean compareAndSetState(int expect, int update) {
        return STATE.compareAndSet(this, expect, update);
    }

    public final void acquire(int arg) {
        /**     一.同步器并没有实现tryAcquire方法,而是由具体的实现类实现。
         *          该方法为false表示需要入队,该方法为true表示 不需要入队 或者 出队
         *          该方法就是出入队的条件,由使用者实现
         *          在ReentrantLock中,tryAcquire方法保证了同一时间只有一个线程调用该方法会返回true
         *
         *      二.addWaiter方法 实际是将线程封装成节点,放入队列里面
         *
         *      三.acquireQueued方法有两种作用 1.用来出队,2.用来阻塞当前线程
         *      1.acquireQueued方法会尝试一次出队,如果出队失败就进入睡眠
         *      2.当线程阻塞时,release方法被调用就是一次出队信号,如果符合条件该线程会被唤醒,
         *          并且满足tryAcquire的出队条件,那就出队成功
         */
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();    //只有acquireQueued返回的打断标记为true才会执行该方法,该方法重新給线程添上打断标记
    }


    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

    private Node addWaiter(Node mode) {
        Node node = new Node(mode);     //一个node对应一个线程,创建node时,会保存当前线程信息

        for (; ; ) {        //死循环保证node必须入队列
            Node oldTail = tail;
            if (oldTail != null) {      //初始的时候队列head和tail都指向null,oldTail不为空说明了初始化完成了
                node.setPrev(oldTail);      //这三条边的连接顺序,保证了只需要一次cas
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                /** 初始化。 该队列的本质是一个双向链表,初始化完成后head和tail都指向一个空Node,
                 *  空Node用来方便出入队列,以及后续操作
                 */
                initializeSyncQueue();
            }
        }
    }

    private boolean compareAndSetTail(Node oldTail, Node newNode) {
        return TAIL.compareAndSet(this, oldTail, newNode);
    }

    private void initializeSyncQueue() {
        Node node;
        if (HEAD.compareAndSet(this, null, node = new Node())) {
            tail = node;
        }
    }

    final boolean acquireQueued(final Node node, int arg) {
        /**
         *   死循环保证一定能出队,循环时尝试出队一次,如果出队失败线程就park住,等待
         *   伴随着循环,Node中的waitStatus的变化是0->-1
         *
         *  为什么这里要单独用个interrupted变量来存。
         *  因为被打断的线程,如果不消除打断标记,那么tryAcquire失败,就再也不能park住了
         *  而消除了打断标记就必须靠其他变量来反馈回去
         */
        boolean interrupted = false;
        for (; ; ) {
            Node preNode = node.predecessor();

            //只有头结点才允许出队,(此处的头结点并不是初始化后的空节点,而是空节点后的那个节点)
            if (head == preNode && tryAcquire(arg)) {
                setHead(node);
                preNode.next = null;
                return interrupted;     //返回打断标记,表明线程是正常出队还是被打断出队的
            }
            if (shouldParkAfterFailedAcquire(preNode, node))
                interrupted |= parkAndCheckInterrupt();
        }
    }

    private void setHead(Node node) {
        head = node;
        head.thread = null;
        head.prev = null;
    }

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        /** 只修改前一个节点的值,而不修改当前节点的值
         * 也就是说前一个节点的waitStatus才是当前节点的状态
         */

        int waitStatus = pred.waitStatus;
        if (waitStatus == Node.SIGNAL) {
            return true;
        } else {
            pred.compareAndSetWaitStatus(waitStatus, Node.SIGNAL);
        }
        return false;
    }

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park();
        return Thread.interrupted();    //返回线程是被打断唤醒还是正常唤醒,重置打断标记
    }

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

    public final boolean release(int arg) {
        //tryRelease方法由具体的实现类实现
        if (tryRelease(arg)) {
            Node node = head;   //node为null说明队列还未初始化
            if (node != null && node.waitStatus != 0) {
                unparkSuccessor(node);
            }
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

    private void unparkSuccessor(Node node) {
        Node s = node.next;
        /**
         * 此处将-1改为0,如果在分公平锁的情况下,如果被唤醒的线程获取锁失败
         * 则不会直接park住,会多一次循环的机会。
         * 多一次循环的机会就会降低被park的可能性
         */
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);

        /**
         * 为什么会判断s==null,因为在真正的AbstractQueuedSynchronizer中
         * 可能该线程会出现异常情况,需要将该node删除,在删除的过程中就有可能为null
         */
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev) {
                if (p.waitStatus < 0)
                    s = p;
            }
        }
        if (s != null) {
            LockSupport.unpark(s.thread);
        }
    }

    public final boolean hasQueuedPredecessors() {
        Node h, s;
        /**
         * 为什么会判断s==null,因为在真正的AbstractQueuedSynchronizer中
         * 可能该线程会出现异常情况,需要将该node删除,在删除的过程中就有可能为null
         */
        if ((h = head) != null) {
            if ((s = h.next) == null || s.waitStatus > 0) {
                s = null;
                for (Node p = tail; p != h && p != null; p = p.prev) {
                    if (p.waitStatus <= 0)
                        s = p;
                }
            }
            if (s != null && s.thread != Thread.currentThread())
                return true;

        }
        return false;
    }

    static final class Node {
        static final Node EXCLUSIVE = null;
        static final int CANCELLED = 1;
        static final int SIGNAL = -1;

        volatile int waitStatus;    //节点的状态

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

        Node nextWaiter;

        private static final VarHandle NEXT;
        private static final VarHandle PREV;
        private static final VarHandle THREAD;
        private static final VarHandle WAITSTATUS;

        static {
            try {
                MethodHandles.Lookup l = MethodHandles.lookup();
                NEXT = l.findVarHandle(Node.class, "next", Node.class);
                PREV = l.findVarHandle(Node.class, "prev", Node.class);
                THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
                WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
            } catch (ReflectiveOperationException e) {
                throw new ExceptionInInitializerError(e);
            }
        }

        public Node() {

        }

        public Node(Node node) {
            this.nextWaiter = node;
            THREAD.set(this, Thread.currentThread());
        }

        public void setPrev(Node prev) {
            PREV.set(this, prev);
        }

        final Node predecessor() {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        final boolean compareAndSetWaitStatus(int expect, int update) {
            return WAITSTATUS.compareAndSet(this, expect, update);
        }

    }
}

 4.AbstractOwnableSynchronizer 类源码

package tools.myLock;

/**
 * @Auther: duanYL
 * @Date: 2023/10/16/10:37
 * @Description:
 */
public class AbstractOwnableSynchronizer {

    protected AbstractOwnableSynchronizer() { }

    private transient Thread exclusiveOwnerThread;

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

    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

总结

麻烦点个赞~~~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值