ReentrantLock源码解读

本文深入解析ReentrantLock的工作原理,包括非公平锁的获取与释放流程、条件变量机制等,并对比synchronized关键字的不同之处。

RTL 是用于管理线程并发的解决方案。接下来我们从最基本的用法来分析,为什么这个类会做到线程的原子性。一般用法 ReentrantLock rtl=new ReentranLock()。实际上内部默认的是利用非公平锁来实现。

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

此方法就是NonfairSync里面的lock()。

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

注意这个compareAndSetState(0, 1),其实就是直接获取锁。大家注意获取锁其实就是将同步状态的state值置为1,这个同步状态的值就是stateOffset。为啥这个变量能够起到这个作用呢,原因在于这是一个private static final long stateOffset;什么意思,java里面你更改不了,但是通过cas算法确实可以直接变更,这就是同步的基石。这个跟synchronized 这个JVM里面的关键字的同步机制不一样,这个synchronized 锁的获取和释放是由JVM 直接控制。
继续看代码。这里我不分析cas 的原理,compareAndSetState(0, 1),实际调用的unsafe.compareAndSwapInt(this, stateOffset, expect, update)。这个方法很有意思,首先判断state的值是否为0,意思代表这个锁并没有被任何线程占用,那么我们可以占用,这个时候设置为1,然后接下来
setExclusiveOwnerThread(Thread.currentThread());设置当前线程占用了这个锁。如果有另外一个线程也要跑这个代码的时候,这个时候stateOffset 的值就是1了,这个时候就走到了 acquire(1);这块属于核心逻辑了。

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

首先是tryAcquire(arg),是钩子方法,可以看到AQS 是采用模板设计模式。

  final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //如果没有线程占据锁,那么直接获取,返回true.
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //这个时候获取到占据锁的线程,判断独占线程是不是自身。如果是的话。 state 加1.这个代表了非公平锁属于可重入。如果还是没有获取那么返回了false。
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 这个方法首先来看。addWaiter(Node.EXCLUSIVE)。
实际上是将自身线程加入到等候队列。

 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //如果末尾Node有值,说明有线程在排队了。
        if (pred != null) {
            //将末尾Node的引用赋值给Node。
            node.prev = pred;
            //设置末尾Node为现有Node,意思就是加入到末尾。
            if (compareAndSetTail(pred, node)) {
                 //如果加入成功,那么将现有node引用赋值给之前末尾指针的next。这样现有node就排在了原先末尾的后面。
                pred.next = node;
                return node;
            }
        }
        //如果有tail和没有tail都会走这个路基。
        enq(node);
        return node;
    }

分析enq(node)。实际上主要的也就是将Node设置为head或者设置为tail。如果没有tail那么必然没有head,这个时候直接设置head。如果有tail,那么将node加载后面。注意这里是个死循环,代表但是肯定会跳回来的。

  private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

至此addWaiter分析完毕。实际看到传入的参数是Node.EXCLUSIVE,代表将独占模式的的Node入队尾了。接下来分析acquireQueued(Node)方法。都写着final呢,这个是模板方法了。

   final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取到node前面的那个Node,实际就是没有加Node.EXCLUSIVE之前的末尾结点。
                final Node p = node.predecessor();
                //如果p就是head,那么代表只有一个节点。
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //判断是否需要阻塞现场。如果阻塞线程了,那么是需要等待唤醒的。否则会一直走死循环,直到获取到锁。阻塞的前提是前面的线程信号是SIGNAL.
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //如果当前线程的前置节点为SIGNAL,代表可以阻塞了,线程阻塞会
        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.
             */
             //设置前置线程SIGNAL 标志。
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

以上就是获取锁的流程。现在分析下解锁的流程。

  public final boolean release(int arg) {
        if (tryRelease(arg)) {
        //如果释放成功,那么
            Node h = head;
            if (h != null && h.waitStatus != 0)
            //下一个结点唤醒。
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease是钩子方法。只有当state==0 才能进行真正释放。

 protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

这就是非公平锁的锁机制,为啥说非公平呢,以为不管任何线程想要获取锁,都是先去请求锁,取不到才进入等待队列。公平锁的机制和这差不多。至于说Condition机制后续再找时间分析吧。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值