ReentrantLock实现思路分析

ReentrantLock是依赖CAS机制写的。

CAS全名Compare And Swap,比较如果相同就交换,全名更容易记住,并且具有意义。

CAS在ReentrantLock中的实现,样例1:

    //同步器状态,如果是0,同步器中没有线程在运行,也就是lock释放状态
    //如果是1,则代表同步器中有线程在运行,lock没有被释放
    private volatile int state;  

    //获取state变量在当前对象所占内存里的偏移量(就是state的位置)
    private static final long stateOffset;
    stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));

    //调用这个方法来更新state值
    //expect:期望值
    //update:更新值
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

变量一定要volatile修饰,保证变量的可见性。对volatile的简要说明:ReentrantLock实现就是用来保证多线程的并发数据统一性的安全控制,在多线程下,要修改一个公共变量,可能会因为更新不及时其他线程导致脏读,而volatile就是能保证各个线程能及时看到操作后最新的值。(volatile的实现原理涉及比较底层了)。

votaile修饰完变量,为了在多线程情况下,能原子性的进行更新操作。这里的原子性操作只有两步,首先比较值,然后更新。

拿上面的这行代码unsafe.compareAndSwapInt(this, stateOffset, expect, update);来说,this代表当前对象,stateOffset代表变量的位置,expect代表此变量我们期望的值,update代表此变量将要更新的值。实际场景:如果此变量现在的值和我们预期的一样,那么就更新值。

了解完CAS,再来讲一下ReentrantLock大概结构:

ReentrantLock它继承了AbstractQueuedSynchronizer抽象类,当你看到ReentrantLock是依赖CAS和AQS的时候,不要疑惑AQS是啥,就是AbstractQueuedSynchronizer,AQS是一个很重要的类。java中实现的很多锁的重要实现都依赖这个类。

AQS的实现大概思路:想象一下在多线程环境下,我怎么控制它们一个个执行呢。首先,我们要把这些要执行的线程给存储起来,这个存储器就叫它同步器吧。AQS采用的是双向链表结构去存储。定义一个Node类,主要变量包括Thread和mode,Thread当然是线程,mode是模式类别(独占式和共享式),ReentrantLock就是独占式模式,因为锁一次只能被一个线程获取。存储好了,那么如何控制这些线程一个个运行呢?ReentrantLock的控制就是根据上面这个state变量来实现的。

我们来尝试解读一下ReentrantLock中的非公平锁里的lock()方法:

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

首先判断compareAndSetState(0, 1)是否执行成功,执行成功的条件就是期望state=0,代表这时候同步器里没有线程在运行,锁是空闲的。我们把state设置为1了,代表锁被占用了,其他的线程执行lock()方法时就会被挂起。获取到了锁,再执行setExclusiveOwnerThread(Thread.currentThread());这个是将当前线程给变量赋值操作,存储是哪个线程获取到了锁。如果没有成功则执行acquire(1)。

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

这里tryAcquire(arg)方法仍然尝试去获取锁,如果不成功,再往下执行。先执行addWaiter(Node.EXCLUSIVE),这个是将当前线程设置为独占式节点插入到同步器的末端(将当前线程存储起来)。再执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法

 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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

可以看到这里是一个for(;;)无限循环,一直尝试去获取锁,那么这不是死循环了吗。parkAndCheckInterrupt()会调动Unsafe的unPark(false,0L)方法,相当于sleep(0),让当前线程放弃cpu,让cpu可以去做其他任务,这样完成一个为抢占锁不断阻塞的程序。关于sleep(0),这篇文章写的不错:https://blog.csdn.net/qiaoquan3/article/details/56281092/

具体情形还请自寻查看源码。ReentrantLock的unlock()方法比它lock()方法简单多了。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页