Java源码解析之可重入锁ReentrantLock

作者:李灿辉 时间:2021-12-28 22:32:40 

本文基于jdk1.8进行分析。

ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock。

首先看一下源码中对ReentrantLock的介绍。如下图。ReentrantLock是一个可重入的排他锁,它和synchronized的方法和代码有着相同的行为和语义,但有更多的功能。ReentrantLock是被最后一个成功lock锁并且还没有unlock的线程拥有着。如果锁没有被别的线程拥有,那么一个线程调用lock方法,就会成功获取锁并返回。如果当前线程已经拥有该锁,那么lock方法会立刻返回。这个可以通过isHeldByCurrentThread方法和getHoldCount方法进行验证。除了这部分介绍外,类前面的javadoc文档很长,就不在这里全部展开。随着后面介绍源码,会一一涉及到。


/**
* A reentrant mutual exclusion {@link Lock} with the same basic
* behavior and semantics as the implicit monitor lock accessed using
* {@code synchronized} methods and statements, but with extended
* capabilities.
* <p>A {@code ReentrantLock} is <em>owned</em> by the thread last
* successfully locking, but not yet unlocking it. A thread invoking
* {@code lock} will return, successfully acquiring the lock, when
* the lock is not owned by another thread. The method will return
* immediately if the current thread already owns the lock. This can
* be checked using methods {@link #isHeldByCurrentThread}, and {@link
* #getHoldCount}.

首先看一下成员变量,如下图。ReentrantLock只有一个成员变量sync,即同步器,这个同步器提供所有的机制。Sync是AbstractQueuedSynchronizer的子类,同时,Sync有2个子类,NonfairSync和FairSync,分别是非公平锁和公平锁。Sync,NonfaireSync和FairSync的具体实现后面再讲。


 /** Synchronizer providing all implementation mechanics **/
 private final Sync sync;

下面看一下构造函数。如下图。可以看到,ReentrantLock默认是非公平锁,它可以通过参数,指定初始化为公平锁或非公平锁。


 /**
  * Creates an instance of {@code ReentrantLock}.
  * This is equivalent to using {@code ReentrantLock(false)}.
  **/
 public ReentrantLock() {
   sync = new NonfairSync();
 }
 /**
  * Creates an instance of {@code ReentrantLock} with the
  * given fairness policy.
  * @param fair {@code true} if this lock should use a fair ordering policy
  **/
 public ReentrantLock(boolean fair) {
   sync = fair ? new FairSync() : new NonfairSync();
 }

下面看一下ReentrantLock的主要方法。首先是lock方法。如下图。lock方法的实现很简单,就是调用Sync的lock方法。而Sync的lock方法是个抽象的,具体实现在NonfairSync和FairSync中。这里我们先不展开讲,而是先读一下lock方法的注释,看看它的作用。lock方法的作用是获取该锁。分为3种情况。

1,如果锁没有被别的线程占有,那么当前线程就可以获取到锁并立刻返回,并把锁计数设置为1。

2,如果当前线程已经占有该锁了,那么就会把锁计数加1,立刻返回。

3,如果锁被另一个线程占有了,那么当前线程就无法再被线程调度,并且开始睡眠,直到获取到锁,在获取到到锁时,会把锁计数设置为1。

lockInterruptibly方法与lock功能类似,但lockInterruptibly方法在等待的过程中,可以响应中断。


 /**
  * Acquires the lock.
  * <p>Acquires the lock if it is not held by another thread and returns
  * immediately, setting the lock hold count to one.
  * <p>If the current thread already holds the lock then the hold
  * count is incremented by one and the method returns immediately.
  * <p>If the lock is held by another thread then the
  * current thread becomes disabled for thread scheduling
  * purposes and lies dormant until the lock has been acquired,
  * at which time the lock hold count is set to one.
  **/
 public void lock() {
   sync.lock();
 }
 public void lockInterruptibly() throws InterruptedException {
   sync.acquireInterruptibly(1);
 }

下面,详细看一下非公平锁和公平锁中对lock函数的实现。如下图。下图同时列出了公平锁和非公平锁中lock的实现逻辑。从注释和代码逻辑中,都可以看出,非公平锁进行lock时,先尝试立刻闯入(抢占),如果成功,则获取到锁,如果失败,再执行通常的获取锁的行为,即acquire(1)。


   /**
    * 非公平锁中的lock
    * 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);
   }
   //公平锁中的lock
   final void lock() {
     acquire(1);
   }

那么,我们首先了解下,非公平锁“尝试立刻闯入”,究竟做了什么。稍后再继续讲解通常的获取锁的行为。下图是立即闯入行为compareAndSetState(0, 1)的实现。从compareAndSetState函数的注释中,可以知道,如果同步状态值与期望值相等,那么就把它的值设置为updated值。否则同步状态值与期望值不相等,则返回false。这个操作和volatile有着相同的内存语义,也就是说,这个操作对其他线程是可见的。compareAndSetState函数注释里描述的功能,是通过unsafe.compareAndSwapInt方法实现的,而unsafe.compareAndSwapInt是一个native方法,是用c++实现的。那么继续追问,c++底层是怎么实现的?C++底层是通过CAS指令来实现的。什么是CAS指令呢?来自 * 的解释是,CAS,比较和交换,Compare and Swap,是用用于实现多线程原子同步的指令。它将内存位置的内容和给定值比较,只有在相同的情况下,将该内存的值设置为新的给定值。这个操作是原子操作。那么继续追问,CAS指令的原子性,是如何实现的呢?我们都知道指令时CPU来执行的,在多CPU系统中,内存是共享的,内存和多个cpu都挂在总线上,当一个CPU执行CAS指令时,它会先将总线LOCK位点设置为高电平。如果别的CPU也要执行CAS执行,它会发现总线LOCK位点已经是高电平了,则无法执行CAS执行。CPU通过LOCK保证了指令的原子执行。

现在来看一下非公平锁的lock行为,compareAndSetState(0, 1),它期望锁状态为0,即没有别的线程占用,并把新状态设置为1,即标记为占用状态。如果成功,则非公平锁成功抢到锁,之后setExclusiveOwnerThread,把自己设置为排他线程。非公平锁这小子太坏了。如果抢占失败,则执行与公平锁相同的操作。


 /**
  * Atomically sets synchronization state to the given updated
  * value if the current state value equals the expected value.
  * This operation has memory semantics of a {@code volatile} read
  * and write.
  * @param expect the expected value
  * @param update the new value
  * @return {@code true} if successful. False return indicates that the actual
  *     value was not equal to the expected value.
  **/
 protected final boolean compareAndSetState(int expect, int update) {
   // See below for intrinsics setup to support this
   return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
 }
 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

下面看一下公平锁获取锁时的行为。如下图。这部分的逻辑有些多,请阅读代码中的注释进行理解。


 /**
  * 公平锁的lock
  **/
 final void lock() {
   acquire(1);
 }
 /**
  * Acquires in exclusive mode, ignoring interrupts. Implemented
  * by invoking at least once {@link #tryAcquire},
  * returning on success. Otherwise the thread is queued, possibly
  * repeatedly blocking and unblocking, invoking {@link
  * #tryAcquire} until success. This method can be used
  * to implement method {@link Lock#lock}.
  * @param arg the acquire argument. This value is conveyed to
  *    {@link #tryAcquire} but is otherwise uninterpreted and
  *    can represent anything you like.
  **/
 public final void acquire(int arg) {
   /**
    * acquire首先进行tryAcquire()操作。如果tryAcquire()成功时则获取到锁,即刻返回。
    * 如果tryAcquire()false时,会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
    * 操作。如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg)true时,则当前线程中断自己。
    * 如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg)false,则返回。
    * 其中tryAcquire()操作在NonfairSync中和FairSync中实现又有所区别。
    **/
   if (!tryAcquire(arg) &&
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
     selfInterrupt();
 }
 /**
  * NonfairSync中的tryAcquire。
  * @param acquires
  * @return
  **/
 protected final boolean tryAcquire(int acquires) {
   return nonfairTryAcquire(acquires);
 }
 /**
  * Performs non-fair tryLock. tryAcquire is implemented in
  * subclasses, but both need nonfair try for trylock method.
  **/
 final boolean nonfairTryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   //首先获取当前同步状态值
   int c = getState();
   if (c == 0) {
     //c为0,表示目前没有线程占用锁。没有线程占用锁时,当前线程尝试抢锁,如果抢锁成功,则返回true。
     if (compareAndSetState(0, acquires)) {
       setExclusiveOwnerThread(current);
       return true;
     }
   }
   else if (current == getExclusiveOwnerThread()) {
     //c不等于0时表示锁被线程占用。如果是当前线程占用了,则将锁计数加上acquires,并返回true。
     int nextc = c + acquires;
     if (nextc < 0) // overflow
       throw new Error("Maximum lock count exceeded");
     setState(nextc);
     return true;
   }
   //以上情况都不是时,返回false,表示非公平抢锁失败。
   return false;
 }
 /**
  * Fair version of tryAcquire. Don't grant access unless
  * recursive call or no waiters or is first.
  * 这个是公平版本的tryAcquire
  **/
 protected final boolean tryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
     //c=0时表示锁未被占用。这里是先判断队列中前面是否有别的线程。没有别的线程时,才进行CAS操作。
     //公平锁之所以公平,正是因为这里。它发现锁未被占用时,首先判断等待队列中是否有别的线程已经在等待了。
     //而非公平锁,发现锁未被占用时,根本不管队列中的排队情况,上来就抢。
     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;
 }
 /**
  * Acquires in exclusive uninterruptible mode for thread already in
  * queue. Used by condition wait methods as well as acquire.
  * 当抢锁失败时,先执行addWaiter(Node.EXCLUSIVE),将当前线程加入等待队列,再执行该方法。
  * 该方法的作用是中断当前线程,并进行检查,知道当前线程是队列中的第一个线程,并且抢锁成功时,
  * 该方法返回。
  * @param node the node
  * @param arg the acquire argument
  * @return {@code true} if interrupted while waiting
  **/
 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);
   }
 }

接下来是tryLock方法。代码如下。从注释中我们可以理解到,只有当调用tryLock时锁没有被别的线程占用,tryLock才会获取锁。如果锁没有被另一个线程占用,那么就获取锁,并立刻返回true,并把锁计数设置为1. 甚至在锁被设置为公平排序的情况下,若果锁可用,调用tryLock会立刻获取锁,而不管有没有别的线程在等待锁了。从这里我们总结出,不管可重入锁是公平锁还是非公平锁,tryLock方法只会是非公平的。


/**
  * Acquires the lock only if it is not held by another thread at the time
  * of invocation.
  * <p>Acquires the lock if it is not held by another thread and
  * returns immediately with the value {@code true}, setting the
  * lock hold count to one. Even when this lock has been set to use a
  * fair ordering policy, a call to {@code tryLock()} <em>will</em>
  * immediately acquire the lock if it is available, whether or not
  * other threads are currently waiting for the lock.
  * This &quot;barging&quot; behavior can be useful in certain
  * circumstances, even though it breaks fairness. If you want to honor
  * the fairness setting for this lock, then use
  * {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) }
  * which is almost equivalent (it also detects interruption).
  * <p>If the current thread already holds this lock then the hold
  * count is incremented by one and the method returns {@code true}.
  * <p>If the lock is held by another thread then this method will return
  * immediately with the value {@code false}.
  * @return {@code true} if the lock was free and was acquired by the
  *     current thread, or the lock was already held by the current
  *     thread; and {@code false} otherwise
  **/
 public boolean tryLock() {
   return sync.nonfairTryAcquire(1);
 }
 public boolean tryLock(long timeout, TimeUnit unit)
     throws InterruptedException {
   return sync.tryAcquireNanos(1, unit.toNanos(timeout));
 }

接下来是释放锁的方法unlock。代码如下。unlock方式的实现,是以参数1来调用sync.release方法。而release方法是如何实现的呢?release方法首先会调用tryRelease方法,如果tryRelease成功,则唤醒后继者线程。而tryRelease的实现过程十分清晰,首先获取锁状态,锁状态减去参数(放锁次数),得到新状态。然后判断持有锁的线程是否为当前线程,如果不是当前线程,则抛出IllegalMonitorStateException。然后判断,如果新状态为0,说明放锁成功,则把持有锁的线程设置为null,并返回true。如果新状态不为0,则返回false。从tryRelease的返回值来看,它返回的true或false,指的是否成功的释放了该锁。成功的释放该锁的意思是彻底释放锁,别的线程就可以获取锁了。这里要认识到,即便tryRelease返回false,它也只是说明了锁没有完全释放,本次调用的这个释放次数值,依然是释放成功的。



/**
  * Attempts to release this lock.
  * <p>If the current thread is the holder of this lock then the hold
  * count is decremented. If the hold count is now zero then the lock
  * is released. If the current thread is not the holder of this
  * lock then {@link IllegalMonitorStateException} is thrown.
  * @throws IllegalMonitorStateException if the current thread does not
  *     hold this lock
  **/
 public void unlock() {
   sync.release(1);
 }
 /**
  * Releases in exclusive mode. Implemented by unblocking one or
  * more threads if {@link #tryRelease} returns true.
  * This method can be used to implement method {@link Lock#unlock}.
  * @param arg the release argument. This value is conveyed to
  *    {@link #tryRelease} but is otherwise uninterpreted and
  *    can represent anything you like.
  * @return the value returned from {@link #tryRelease}
  **/
 public final boolean release(int arg) {
   if (tryRelease(arg)) {
     Node h = head;
     if (h != null && h.waitStatus != 0)
       unparkSuccessor(h);
     return true;
   }
   return false;
 }
 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;
   }
 /**
  * Wakes up node's successor, if one exists.
  * @param node the node
  **/
 private void unparkSuccessor(Node node) {
   /**
    * If status is negative (i.e., possibly needing signal) try
    * to clear in anticipation of signalling. It is OK if this
    * fails or if status is changed by waiting thread.
    **/
   int ws = node.waitStatus;
   if (ws < 0)
     compareAndSetWaitStatus(node, ws, 0);
   /**
    * Thread to unpark is held in successor, which is normally
    * just the next node. But if cancelled or apparently null,
    * traverse backwards from tail to find the actual
    * non-cancelled successor.
    **/
   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);
 }

接下来是newCondition方法。关于Condition这里不展开介绍,只是了解下该方法的作用。如下图。该方法返回一个和这个锁实例一起使用的Condition实例。返回的Condition实例支持和Object的监控方法例如wait-notify和notifyAll相同的用法。

  • 1,如果没有获取锁,调用Condition的await,signal,signalAll方法的任何一个时,会抛出IllegalMonitorStateException异常。

  • 2,调用Condition的await方法时,锁也会释放,在await返回之前,锁会被重新获取,并且锁计数会恢复到调用await方法时的值。

  • 3,如果一个线程在等待的过程中被中断了,那么等待就会结束,并抛出InterruptedException异常,线程的中断标志位会被清理。

  • 4,等待的线程以FIFO的顺序被唤醒。

  • 5,从await方法返回的线程们的获取到锁的顺序,和线程最开始获取锁的顺序相同,这是未指定情况下的默认实现。但是,公平锁更钟爱那些已经等待了最长时间的线程。


 /**
  * Returns a {@link Condition} instance for use with this
  * {@link Lock} instance.
  * <p>The returned {@link Condition} instance supports the same
  * usages as do the {@link Object} monitor methods ({@link
  * Object#wait() wait}, {@link Object#notify notify}, and {@link
  * Object#notifyAll notifyAll}) when used with the built-in
  * monitor lock.
  * <ul>
  * <li>If this lock is not held when any of the {@link Condition}
  * {@linkplain Condition#await() waiting} or {@linkplain
  * Condition#signal signalling} methods are called, then an {@link
  * IllegalMonitorStateException} is thrown.
  * <li>When the condition {@linkplain Condition#await() waiting}
  * methods are called the lock is released and, before they
  * return, the lock is reacquired and the lock hold count restored
  * to what it was when the method was called.
  * <li>If a thread is {@linkplain Thread#interrupt interrupted}
  * while waiting then the wait will terminate, an {@link
  * InterruptedException} will be thrown, and the thread's
  * interrupted status will be cleared.
  * <li> Waiting threads are signalled in FIFO order.
  * <li>The ordering of lock reacquisition for threads returning
  * from waiting methods is the same as for threads initially
  * acquiring the lock, which is in the default case not specified,
  * but for <em>fair</em> locks favors those threads that have been
  * waiting the longest.
  * </ul>
  * @return the Condition object
  **/
 public Condition newCondition() {
   return sync.newCondition();
 }

可重入锁还有一些其他的方法,这里就不一一介绍了。This is the end.

来源:https://blog.csdn.net/li_canhui/article/details/85006114

标签:java,reentrantlock,解析
0
投稿

猜你喜欢

  • C#(asp.net)多线程用法示例(可用于同时处理多个任务)

    2022-03-09 01:41:30
  • C#中的尾递归与Continuation详解

    2021-12-05 16:35:15
  • Java流程控制break和continue

    2023-06-16 09:49:54
  • Android下拉列表spinner的实例代码

    2023-07-31 20:39:47
  • 解决在Unity中使用FairyGUI遇到的坑

    2023-10-27 13:04:24
  • 不依赖于Activity的Android全局悬浮窗的实现

    2022-04-08 00:42:43
  • 自定义log4j日志文件命名规则说明

    2021-11-21 16:55:51
  • Java中继承thread类与实现Runnable接口的比较

    2022-06-09 12:57:09
  • Java操作Excel的示例详解

    2021-07-08 00:56:56
  • Java ArrayList中存放引用数据类型的方式

    2023-11-16 15:23:46
  • WPF中不规则窗体与WindowsFormsHost控件兼容问题的解决方法

    2022-09-26 12:47:19
  • Android调用相机并将照片存储到sd卡上实现方法

    2023-11-06 00:41:41
  • Flutter应用集成极光推送的实现示例

    2023-06-24 03:51:04
  • Spring MVC请求参数接收的全面总结教程

    2023-11-28 19:44:47
  • C#实现跨线程操作控件方法

    2023-06-30 13:47:39
  • 详解Java如何实现在PDF中插入,替换或删除图像

    2022-04-07 22:40:36
  • Android RecyclerView 基础知识详解

    2022-10-04 13:40:20
  • C#微信公众号开发之接收事件推送与消息排重的方法

    2022-01-31 08:44:46
  • Spring注解驱动之BeanPostProcessor后置处理器讲解

    2023-01-04 17:13:26
  • C语言内存操作函数详解

    2021-09-01 01:50:36
  • asp之家 软件编程 m.aspxhome.com