We can understand that it is a reentrant lock, let's take a look at its underlying implementation~

Constructor

When we use it, we always new it first, so let's take a look at its constructor. There are two main ones:

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

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

Literally, the difference between them lies in fair, which means fair in translation. It can be roughly guessed that it is used to build fair locks and unfair locks. Before continuing to look at the source code, let’s give you some popular science. Both locks.

Fair Lock & Unfair Lock

  • Fair lock Multiple threads acquire locks in the order in which they apply for locks. Threads will directly enter the queue to queue, and the first place in the queue can always get the lock. (e.g. bank account number)

The advantages of this kind of lock are obvious. Each thread can obtain resources. The disadvantages are also obvious. If a thread is blocked, other threads will also be blocked. However, the CPU wake-up cost is very high. I have told you before.

  • Unfair locks: Multiple threads try to acquire the lock. If they cannot acquire the lock, they enter the waiting queue, and the CPU does not need to wake up.

The advantages and disadvantages are just the opposite of the above, the advantages reduce the overhead, and the disadvantages are also obvious, which may result in the lock being unable to be acquired all the time or the lock being unable to be acquired for a long time.

Well, after we have the basic concept, let's continue to look down

NonfairSync

First, let's look at unfair locks. By default, we apply for unfair locks, that is, new ReentrantLock(), and then we look at the source code

 static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
        * Performs lock.  Try immediate barge, backing up to normal
        * acquire on failure.
        */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

It inherits from Sync, which is an abstract class with static content:

 abstract static class Sync extends AbstractQueuedSynchronizer {...}

Divided into fair and unfair, the AQS state is used to represent the number of times the lock is held, and there is sync = ... when the constructor is initialized. Let's look at NonfairSync next. When using it, we call the lock.lock() method, which is an instance method of ReentrantLock

 // 获取锁
 public void lock() {
        sync.lock();
    }

In fact, the internal method of sync is still adjusted internally, because we apply for an unfair lock, so let's look at the lock implementation under NonfairSync:

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

The compareAndSetState method is an internal method of AQS, which means that if the current state value is equal to the expected value, the synchronization state is automatically set to the given update value. This operation has the memory semantics of volatile read and write.

 protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

You can see that the execution of the lock method will count through the AQS mechanism. setExclusiveOwnerThread sets the thread exclusive access permission, which is an internal method of AbstractOwnableSynchronizer. Subclasses use it to manage thread exclusive access.

 public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {}

You can see that it inherits AbstractOwnableSynchronizer. Let's look at it next. We say that if the actual value is equal to the expected value, the above method will be executed, and acquire(1) will be executed when it is not expected.

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

This method is acquired in exclusive mode, ignoring interrupts, it will try to call tryAcquire, it will return successfully, and it will enter the thread queue if it is unsuccessful, and it can repeatedly block and unblock. Look at this method inside AQS

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

We can see that the implementation is definitely not here, its specific implementation is in NonfairSync

 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

You can see that it is called, the nonfairTryAcquire method, this method is unfair tryLock, the specific implementation is inside Sync, here we have to focus on

 final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();

    // 返回同步状态值,它是AQS内部的一个方法 
    //  private volatile int state;
    // protected final int getState() {
    //     return state;
    // }
    int c = getState();
    if (c == 0) {
        // 为0就比较一下,如果与期望值相同就设置为独占线程,说明锁已经拿到了
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 否则 判断如果当前线程已经是被设置独占线程了
    else if (current == getExclusiveOwnerThread()) {

        // 设置当前线程状态值 + 1 并返回成功
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 否则返回失败 没拿到锁
    return false;
}

OK, let's go back and look at acquire

 public final void acquire(int arg) {
         // 如果当前线程没有获取到锁 并且 在队列中的线程尝试不断拿锁如果被打断了会返回true, 就会调用 selfInterrupt   
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

selfInterrupt很好理解,线程中断

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

In fact, the focus of our attention is this method acquireQueued. First, let's pay attention to the input parameters. It passes in an addWaiter inside, and finally it returns to the NODE node.

 private Node addWaiter(Node mode) {
       // mode 没啥好说的就是一个标记,用于标记独占模式   static final Node EXCLUSIVE = null;
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

We can roughly guess that Node is a node class that waits for a queue, and it is a linked list structure. We also encountered this structure when we talked about the FutureTask source code. It is usually used for spin locks. In this place, it is for blocking synchronizers

 +------+  prev +-----+       +-----+
head |      | <---- |     | <---- |     |  tail
     +------+       +-----+       +-----+

OK, let's focus on acquireQueued

 final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        // 默认是 false
        boolean interrupted = false;
        // 进入阻塞循环遍历 线程队列
        for (;;) {
            // 返回前一个节点
            final Node p = node.predecessor();

            // 判断如果前一个节点是头部节点,并且拿到锁了,就会设置当前节点为头部节点
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                // 这里可以看到注释 help gc ,
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 检查并更新未能获取的节点的状态。如果线程应该阻塞,则返回 true 并且线程中断了
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // 如果失败  取消正在尝试获取的节点
        if (failed)
            cancelAcquire(node);
    }
}

Judging from the above source code, is it better to understand the concept of unfair lock mentioned above, and then release the lock unlock, we can see that this method is an instance method under ReentrantLock, so the fair lock This method is also called to release the lock. In fact, it can be guessed that the sync method is called in the end.

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

Sync继承AQS,release是AQS的内部方法

 public final boolean release(int arg) {
       // 尝试释放锁  tryRelease 在Sync内部
        if (tryRelease(arg)) {
            Node h = head;
            // 如果节点存在 并且状态值不为0
            if (h != null && h.waitStatus != 0)
                // 唤醒下个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

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来唤醒
            LockSupport.unpark(s.thread);
    }

Let's look at tryRelease again, the same implementation is in Sync

 protected final boolean tryRelease(int releases) {
            // 同样释放锁的时候 依然使用 AQS计数
            int c = getState() - releases;
            // 判断当前线程是否是独占线程,不是抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 如果是0 表示是释放成功
            if (c == 0) {
                free = true;
                // 并且把独占线程设为null
                setExclusiveOwnerThread(null);
            }
            // 更新状态值
            setState(c);
            return free;
        }

FairSync

The difference between fair lock FairSync is that its implementation of acquiring locks is within it, and Sync implements unfair locks internally by default.

 static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        // 这个方法最终调用 tryAcquire
        final void lock() {
            acquire(1);
        }

        // 公平锁的实现
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 这边和非公平锁的实现有些相似 同样判断状态
            if (c == 0) {
                // 判断排队队列是否存在, 不存在并且比较期望值
                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;
        }
    }

Its implementation is relatively simple. Through the implementation, it can be found that it acquires the lock in the order of applying for the lock, and the first one gets the lock first. After understanding the above concepts, it is easy to understand.

Release the lock unlock, as we have already mentioned above~

concluding remarks

The content of this section may be a bit too much, mainly to look at the source code, you can break the point and adjust it yourself, draw inferences from one case, and understand what is fair lock and unfair lock through the source code, and where is the ReentrantLock reentrant lock experience.


Java架构师
179 声望66 粉丝