关于java:JDK成长记21-ReentrantLock-4-公平非公平可重入锁是什么

35次阅读

共计 7644 个字符,预计需要花费 20 分钟才能阅读完成。

通过后面的三节,置信你对 ReentrantLock 底层的 AQS 原理曾经很分明了。接下来给大家介绍几个 ReentrantLock 中的几个概念:

  • 偏心,非偏心锁的概念
  • ReentrantLock 是如何实现非偏心和偏心的?
  • 可重入锁又是什么货色?

偏心锁 Vs 非偏心锁

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 偏心锁 Vs 非偏心锁 </span></h3></div>

当你把握了 ReentrantLock 加锁,加锁失败入队,开释锁的原理后。其实在 ReenrantLock 中还须要搞明确几个概念,比方独占锁、共享锁、可重入锁,偏心锁和非偏心锁这些都是什么意思。

这一大节,咱们先来聊聊偏心和非偏心锁。

什么是偏心锁?什么又是非偏心锁呢?这里给大家举个例子:

置信你必定有过排队的经验,比方你给女朋友排队买过奶茶。然而你排队排的好好的,忽然当有个老板亲戚或者关系户过去插了一个队,你是什么感觉?是不是感觉不太偏心。然而有的关系户也很有涵养,不会插队,会老老实实去排队,这就很偏心了,因为先来后到么。

这其实就是偏心和非偏心的锁的意思。你能够想想,还是下面的例子,线程 2 在排队了,此时线程 1 开释了锁,可是忽然来了一个线程 3, 也来加锁,是不是可能在线程 2 出队的过程中,线程 3 抢到锁,这就是非偏心的,线程 3 插队了,没有老老实实排队。

然而如果线程 3,老老实实的排队,进入 AQS 的队列中,这样就是偏心锁。如下图所示:

ReentrantLock 是如何实现非偏心和偏心的?

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>ReentrantLock 是如何实现非偏心和偏心的?</span></h3></div>

具体代码是怎么做到呢?外围是通过两个 Sync 的子类。FairSync 和 NonfairSync。从名字上看,你就应该晓得,这两个类是偏心和非偏心 AQS 的 Sync 组件意思。

大家能够它们两个类的找找不同看看:

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


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


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


final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();
 int c = getState();
 if (c == 0) {​    if (compareAndSetState(0, acquires)) {​      setExclusiveOwnerThread(current);
​      return true;
​    }
 }

 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;

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

  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;

  }

}

首先是 lock 办法,区别就是在一个 if 判断,非偏心的锁 NonfairSync 会多了一个判断,先尝试来加个锁。

这个区别是什么意思呢?你能够了解为如果线程 1 开释了,他人过去加锁,间接先尝试插个队的意思,有可能 AQS 队列中的线程 2 还没被唤醒了,被他人抢走了锁,让别的线程加锁胜利了。

如何把锁给开释掉,另外一个是如果锁彻底开释了当前,如何让队列中的队头的那个线程来唤醒尝试获取锁。

而另一个办法,尝试加锁,惟一的区别是一个 if 条件

hasQueuedPredecessors()

这办法从名字就能看进去,判断下队列中有没有有元素。代码如下:

 public final boolean hasQueuedPredecessors() {
  Node t = tail; // Read fields in reverse initialization order
  Node h = head;
  Node s;
  return h != t &&
   ((s = h.next) == null || s.thread != Thread.currentThread());
}

也就是说,在偏心锁的尝试加锁的代码中,有一个限度如果有人排队,其余线程就不能插队加锁。所以就算线程 1 开释锁,线程 3 过去加锁,因为 lock 办法没有了非偏心锁的 if(上来尝试 CAS 批改 state,加锁的代码),线程 3 就只能入队,如果线程 3 执行到尝试获取锁的代码时,偏心锁比非偏心锁的代码多了一个判断,判断队列中是否有期待线程。有的话也只能乖乖排队。如下图所示:

可重入锁 Vs 不可重入锁

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 可重入锁 Vs 不可重入锁 </span></h3></div>

之前提到,ReentrantLock 波及了一些锁的概念,讲过了偏心和非偏心锁的概念后,明天咱们最初聊一下可重入锁。

其实这个比拟好了解,ReentrantLock 通过 AQS 的 state 变量奇妙的实现了可重入加锁。如果是同一个线程调用了 lock 办法,加锁,state 会在现有值上加 +1,每再次加一次锁,就是一次可重入,所以就加锁可重入锁。也就是说:

同一个线程能够应用同一个 ReentrantLock 进行重复加锁。

另外,开释锁的话,必定须要开释所屡次,同一个线程加锁了几次,就须要开释几次,须要将 state 值复原为 0 才算真正的开释锁,别的线程能力获取到。

因为比较简单,就不带大家看源码实现了。你能够本人在源码中找找。外围还是把握 AQS 加锁开释锁的原理最重要。

独占锁 VS 共享锁

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 独占锁 VS 共享锁 </span></h3></div>

至于独占和共享锁的概念之后解说读写锁的时候会提到。这里先简略的讲一下。

所谓独占锁,就是只有有一个线程加锁,其他人都得靠边站,这把锁属于某个线程独占,这就是独占锁。

默认 reentrantLock.lock 创立的锁是什么的呢?非偏心的可重入独占锁!

共享锁 是什么意思呢?意思就是能够和别的线程同时持有一把锁,比方之后要将的读写锁。线程 1 加了读锁,线程 2 还是能够加读锁的,它们共享一把锁。这样的锁就是一把共享锁。

当然读写锁之间是有一些互斥关系的,所以下一节咱们就来摸索下,如何应用读写锁、读写锁的原理具体是什么、以及读写锁的互斥关系。

小结 & 思考

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 小结 & 思考 </span></h3></div>

其实这一节并没有什么特地负责简单的常识,次要带大家看了 ReentrantLock 的重入实现

核心思想就是通过代码执行程序,CAS 操作程序和一个 if 判断队列是否有期待线程实现的。

其次就是介绍了几个概念,可重入锁、偏心、非偏心锁、独占和共享锁别离是什么意思。

ReentrantLock 的原理其实到这里咱们大体就剖析实现了,你起码把握了 ReentrantLock 的基于抽象类封装 3 个组件(变量)的操作设计。

之前咱们提到的 synchronized 的底层 ObjectMonitor,其实是不是也是用过这个思维设计的,只不过一个是 C ++ 封装的 ObjectMonitor 对象,一个是 Java 封装的 ReentrantLock 对象。

大家当学完一个技术的原理和或者源码,或者做完一个我的项目后,肯定要学会进行思考,思考之后能力更好的利用这个技术、更好的解决问题。

另外有趣味的同学能够去深究下,它应用的 CAS 底层 JVM C++ 语言如何的实现,为什么用 LockSupport.park 挂起线程,LockSupport 的 park 办法实现等等。。

下一节咱们开始钻研下 ReentrantReadWriteLock 的实现原理,咱们下一节见!

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0