ReentrantLock源码解析

224 阅读7分钟

ReentrantLock的加锁原理是根据一个变量的值来确定是否可以加锁,这个变量值就是state

1:ReentrantLock默认实现的是非公平锁

2: ReentrantLock是可重入锁

3:后面进来的线程没有获取到锁,就会使用LockSupport.park();方法阻塞当前线程,并把这个线程添加到队列中去

4:队列不是在ReentrantLock初始化的时候就初始化的,而是在需要队列存放元素的时候在初始化

5:在释放锁的时候就会唤醒队列的头节点元素,让头节点的线程继续执行

ReentrantLock reentrantLock = new ReentrantLock();

查看它的构造函数,默认是实现非公平锁。

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

其实在实例化ReentranLock是有一个boolean fair的参数的

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

如果为true就是公平锁,false是非公平锁.

接下来看下最主要的一个方法 lock()方法

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

继续跟进sync.lock()方法

可以看到一个抽象类Sync,抽象类一般都是有具体实现的,所以我们可以知道lock的具体实现就在Sync类当中。 在Sync的lock()方法可以知道Sync有二个子类,FairSync和NonfairSync,也就是公平锁类和非公平锁类。首先看下公平锁的lock()方法实现

    final void lock() {
        acquire(1);
    }

继续调用了acquire方法,并传进了一个int值等于1.

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

这里有二个判断,首先看下第一个条件,跟进tryAcquire()方法,但是这个方法在AQS类中并没有实现。所以可以知道是在子类中自己实现了

protected final boolean tryAcquire(int acquires) {
        //得到当前线程
        final Thread current = Thread.currentThread();
        //获取到state的值,reentranLock就是根据state这个值来判断是否可以持有锁的
        int c = getState();
        //如果等于0
        if (c == 0) {
            //当前线程会尝试获取锁,而不是一定能得到锁
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                //设置成独占锁
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //如果当前线程就是之前获取锁的线程,也就是本身
        else if (current == getExclusiveOwnerThread()) {
            //这里也就是重入锁的实现,将state的值 加 1
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            //设置state的值
            setState(nextc);
            return true;
        }
        return false;
    }
}

现在难点来了,我们看下!hasQueuedPredecessors()这个判断,里面就返回一个boolean的值

      return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());

首先看下第一个条件 h != t,就是头节点不等于尾节点,现在有三种情况 1):队列没有初始化:那么 h != t == false,不成立,直接返回false,但是在条件判断前面加 反 ,所以!hasQueuedPredecessors()为true,然后再尝试获取锁,如果成功获取到锁,那么就直接上锁了,如果没有成功获取到锁那么tryAcquire()方法直接返回false。现在又要回到下面这个方法了啊

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

就是当队列没有初始化而且当前线程也没有获取到锁,那么就要执行后面的判断了,先看看addWaiter方法,这个方法就是当队列没有初始化就初始化一个队列,队列已经初始化了就将新元素添加到队列中,并维护好。

 private Node addWaiter(Node mode) {
    //将当前对象封装成一个Node对象
    Node node = new Node(Thread.currentThread(), mode);
    //这个是队列的尾节点,如果队列初始化了那么 tail是绝对不会为空的
    Node pred = tail;
    if (pred != null) {
        //如果队列已经初始化了,那么直接维护该链表,将该线程设置到队列的尾部
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //这个方法就是初始化队列的
    enq(node);
    return node;
}

private Node enq(final Node node) {
        //这里是个死循环
        for (;;) {
            
            Node t = tail;
            //因为队列没有初始化,所以t == null,
            if (t == null) { // Must initialize
                //然后创建一个新的节点,并且头节点和尾节点都是它本身,执行完if,因为是个死循环,所以会再次执行这个判断,但是在第二次执行的时候尾节点已经不为空了,因为第一次循环已经创建了一个节点了,但是这个节点是个空对象,并不是我们传进来的node
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //第二次循环就是将我们传进来的node添加到尾部,现在队列中就有二个node节点了,第二个才是我们自己的节点元素,最后返回
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

addWaiter方法返回当前节点元素,然后执行acquireQueued()方法

  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;
                }
                //执行到这里有二种情况,1:上个节点不是头节点。2:上一个节点是头节点,但是获取锁失败了。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

看下shouldParkAfterFailedAcquire这个方法做了什么

  private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //节点的默认值就是为 0,所以 ws = 0,所以会走esle模块,并且将上一个节点的waitStatus的值设置成 -1,然后返回false
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

返回false之后,我们知道这个判断直接返回false了,所以会再次执行一次循环,也就是上面的acquireQueued()方法

 if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;

然后继续执行,又进入shouldParkAfterFailedAcquire方法,但是这次waitStatus的值等于 -1,所以会执行上面的第一个if语句块,然后返回true。接着就会执行parkAndCheckInterrupt方法,这个方法就是为了将当前线程阻塞。让持有锁的线程独占。这就是整个加锁的过程

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

这是公平锁实现加锁,现在来看看非公平锁加锁过程

final void lock() {
            //这里少了个判断,也就是非公平锁在来新线程的时候不会排队,直接尝试获取锁,如果获取到了就持有锁,没获取到自然也是要去排队
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

公平锁:所有的线程都要排队,先来先执行

非公平锁:新线程会直接尝试获取锁,获取到了就持有锁,也就不会按顺序来排队

这就是我理解的公平锁跟非公平锁的区别

以上就是加锁过程,现在来看看解锁的过程,也就是lock.unlock()方法,跟进unlock方法,知道会执行release()方法

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()方法

protected final boolean tryRelease(int releases) {
            //首先获取state的值,然后 -1
            int c = getState() - releases;
            //判断当前线程是不是持有锁的线程,不是直接抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //-1之后等于0,就释放锁
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //最后重新设置state的值,因为其它线程要根据这个值来判断是否可以获取锁的
            setState(c);
            return free;
        }

会继续判断头节点会不会为空,不为空且waitStatus != 0 ,在加锁的过程中我们知道都把这个值设置成 -1了,所以会执行unparkSuccessor()方法

 private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    获取下一个节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //如果下一个节点不为空,就唤醒,然后下一个节点就会尝试加锁,然后执行任务
    if (s != null)
        LockSupport.unpark(s.thread);
}

以上就是个人观看源码所理解的AQS锁机制。加锁和释放锁的整个过程。有错误欢迎提出。(在初始化队列最好用笔画一下,我也是按照源码一步一步画出来才理解队列的初始化,队列初始化理解不清楚后面看起来会比较吃力)