可重入的独占锁——ReentrantLock源码分析

  ReentrantLock面试题分析
  
  1、ReentrantLock是怎么实现的?
  
  2、ReentrantLock的公平锁和非公平锁是如何实现的?
  
  1.ReentrantLock类图结构
  
  从类图我们可以直观地了解到,ReentrantLock最终还是使用AQS来实现地,并且根据参数来决定其内部是一个公平??还是非公平锁??,默认是非公平锁??。
  
  public ReentrantLock() {
  
  sync = new NonfairSync();
  
  }
  
  public ReentrantLock(boolean fair) {
  
  sync = fair ? new FairSync(www.tdcqpt.cn) : new NonfairSync();
  
  }
  
  其中Sync类直接继承自AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平与公平策略。
  
  如果读者对AQS还不了解的话,可以去看看我的这篇文章:抽象同步队列AQS——AbstractQueuedSynchronizer锁详解
  
  在这里,AQS的state状态值表示线程获取该锁的可重入次数,在默认情况下,state的值为0表示当前锁没有被任何线程持有。当一个线程第一次获取该锁时,会尝试使用CAS设置state的值为1,
  
  如果CAS成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程。在该线程没用释放锁的情况下第二次获取该锁后,状态值被设置为2,这就是可重入次数。
  
  在该线程释放锁时,会尝试使用CAS让状态值减1,如果减1后状态值为0,则当前线程释放该锁。
  
  2.获取锁的主要方法
  
  2.1 void lock()方法
  
  lock()获取锁,其实就是把state从0变成n(重入锁可以累加)。实际调用的是sync的lock方法,分公平和非公平。
  
  public void lock() {
  
  sync.lock();
  
  }
  
  在如上代码中,ReentrantLock的lock()委托给sync类,根据创建的ReentrantLock构造函数选择sync的实现是NonfairSync还是FairSync,先看看sync的子类NonfairSync(非公平锁??)的情况
  
  final void lock() {
  
  if (compareAndSetState(0, 1))//CAS设置状态值为1
  
  setExclusiveOwnerThread(Thread.currentThread());//设置该锁的持有者为当前线程
  
  else //CAS失败的话
  
  acquire(1);//调用AQS的acquire方法,传递参数为1
  
  }
  
  下面是AQS的acquire的核心源码
  
  public final void acquire(int arg) {
  
  if (!tryAcquire(arg) &&//调用ReentantLock重写tryAcquire方法
  
  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//tryAcquire返回false会把当前线程放入AQS阻塞队列
  
  selfInterrupt();
  
  }
  
  之前说过,AQS并没有提供可用的tryAcquire方法,tryAcquire方法需要子类自己定制化,所以这里代码会调用ReentantLock重写的tryAcquire方法。我们看下非公平锁??的实现
  
  protected final boolean tryAcquire(int acquires) {
  
  return nonfairTryAcquire(acquires);
  
  }
  
  final boolean nonfairTryAcquire(int acquires) {
  
  final Thread current = Thread.currentThread(www.dongfangyuld.com);
  
  int c = getState();
  
  if (c == 0http://www.wanxinyulept.com/chaoyue/ ) {//当前AQS状态为0,acquires参数传递默认为1,因为之前CAS失败,再次获取锁
  
  if (compareAndSetState(0, acquires)) {//CAS设置状态值为1
  
  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;http://www.zzhehong.com/chaoyue/ //如果当前线程不是该锁的持有者,则返回false,然后会放入AQS阻塞队列
  
  }
  
  结束完非公平锁??的实现代码,回过头来看看非公平在这里是怎么体现的。首先非公平是说先尝试获取锁的线程并不一定比后尝试获取锁的线程优先获取锁??。
  
  而是使用了抢夺策略。那么下面我们看看公平锁??是怎么实现公平的。
  
  protected final boolean tryAcquire(int acquires) {
  
  final Thread current = Thread.currentThread();
  
  int c = getState();
  
  if (c == 0) {//当前AQS状态为0
  
  if (!hasQueuedPredecessors() &&//公平性策略,判断队列还有没有其它node,要保证公平
  
  compareAndSetState(www.haojiangyule.com, acquires)) {//CAS设置状态
  
  setExclusiveOwnerThread(current);//设置获取锁的线程
  
  return true;
  
  }
  
  }
  
  else if (current == getExclusiveOwnerThread()) {//如果当前线程是该锁的持有者
  
  int nextc = c + acquires;//重入次数+1
  
  if (nextc < 0)
  
  throw new Error("Maximum lock count exceeded");
  
  setState(nextc);//重新设置锁的状态
  
  return true;
  
  }
  
  return false;
  
  }
  
  }
  
  如上代码所示,公平的tryAcquire策略与非公平的类似,不同之处在于,代码在设置CAS操作之前添加了hasQueuedPredecessors()方法,该方法是实现公平性的核心代码。代码如下
  
  public final boolean hasQueuedPredecessors() {
  
  Node t = tail;www.yfykLe2019.com // Read fields in reverse initialization order
  
  Node h = head;
  
  Node s;
  
  return h != t &&
  
  ((s = h.next) == null || s.thread != Thread.currentThread());
  
  }
  
  2.2void lockInterruptibly()方法
  
  该方法与lock()方法类似,不同在于对中断进行响应,如果当前线程在调用该方法时,其它线程调用了当前线程的interrupt()方法,则该线程抛出异常而返回
  
  public void lockInterruptibly() throws InterruptedException {
  
  sync.acquireInterruptibly(www.zbzxyL12.com);
  
  }
  
  public final void acquireInterruptibly(int arg)
  
  throws InterruptedException {
  
  if (Thread.interrupted())//如果当前线程被中断,则直接抛出异常
  
  throw new InterruptedException();
  
  if (!tryAcquire(arg))//尝试获取资源
  
  doAcquireInterruptibly(arg);//调用AQS可被中断的方法
  
  }
  
  2.3 boolean tryLock()方法
  
  尝试获取锁,如果当前锁没用被其它线程持有,则当前线程获取该锁并返回true,否则返回false。注意,该方法不会引起当前线程阻塞
  
  public boolean tryLock(www.zbzxyL12.com) {
  
  return sync.nonfairTryAcquire(1);
  
  }
  
  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;
  
  }
  
  如上代码与非公平锁的tryAcquire()方法代码类似,所以tryLock()使用的是非公平策略。
  
  2.4 boolean tryLock(long timeout, TimeUnit unit)方法
  
  尝试获取锁,与tryLock()的不同之处在于,它设置了超时时间,如果超时时间到了,没用获取到锁,则返回false,以下是相关代码
  
  public boolean tryLock(long timeout, TimeUnit unit)
  
  throws InterruptedException {
  
  return sync.tryAcquireNanos(1, unit.toNanos(timeout));//调用AQS的tryAcquireNanos方法
  
  }
  
  public final boolean tryAcquireNanos(int arg, long nanosTimeout)
  
  throws InterruptedException {
  
  if (Thread.interrupted())
  
  throw new InterruptedException();
  
  return tryAcquire(arg) ||
  
  doAcquireNanos(arg, nanosTimeout);
  
  }
  
  private boolean doAcquireNanos(int arg, long nanosTimeout)
  
  throws InterruptedException {
  
  if (nanosTimeout <= 0L)
  
  return false;
  
  final long deadline = System.nanoTime() + nanosTimeout;
  
  final Node node = addWaiter(Node.EXCLUSIVE);
  
  boolean failed = true;
  
  try {
  
  for (;;) {
  
  final Node p = node.predecessor();
  
  if (p == head && tryAcquire(arg)) {
  
  setHead(node);
  
  p.next = null; // help GC
  
  failed = false;
  
  return true;
  
  }
  
  nanosTimeout = deadline - System.nanoTime();
  
  if (nanosTimeout <= 0L)
  
  return false;
  
  if (shouldParkAfterFailedAcquire(p, node) &&
  
  nanosTimeout > spinForTimeoutThreshold)
  
  LockSupport.parkNanos(this, nanosTimeout);
  
  if (Thread.interrupted())
  
  throw new InterruptedException();
  
  }
  
  } finally {
  
  if (failed)
  
  cancelAcquire(node);
  
  }
  
  }
  
  3 释放锁相关方法
  
  3.1 void unlock()方法
  
  尝试获取锁,如果当前线程持有锁,则调用该方法会让该线程持有的AQS状态值减1,如果减1后当前状态值为0,则当前线程会释放该锁,否则仅仅减1而已。
  
  如果当前线程没用持有该锁而调用了该方法则会抛出异常,代码如下:
  
  public void unlock() {
  
  sync.release(1);
  
  }
  
  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;//AQS状态值减1
  
  if (Thread.currentThread() != getExclusiveOwnerThread())
  
  throw new IllegalMonitorStateException();
  
  boolean free = false;
  
  if (c == 0) {//如果当前可重入次数为0,则清空锁持有线程
  
  free = true;
  
  setExclusiveOwnerThread(null);
  
  }
  
  setState(c);//设置可重入次数为原始值减1
  
  return free;
  
  }
  
  4.案例介绍
  
  下面使用ReentrantLock来实现一个简单的线程安全的list集合
  
  public class ReentrantLockList {
  
  //线程不安全的list
  
  private ArrayList<String>arrayList=new ArrayList<>();
  
  //独占锁
  
  private volatile ReentrantLock lock=new ReentrantLock();
  
  //添加元素
  
  public void add(String e){
  
  lock.lock();
  
  try {
  
  arrayList.add(e);
  
  }finally {
  
  lock.unlock();
  
  }
  
  }
  
  //删除元素
  
  public void remove(String e){
  
  lock.lock();
  
  try {
  
  arrayList.remove(e);
  
  }finally {
  
  lock.unlock();
  
  }
  
  }
  
  //获取数据
  
  public String get(int index){
  
  lock.lock();
  
  try {
  
  return arrayList.get(index);
  
  }finally {
  
  lock.unlock();
  
  }
  
  }
  
  }
  
  如上代码在操作arrayList元素前进行加锁保证同一时间只有一个线程可用对arrayList数组进行修改,但是也只能一个线程对arrayList进行访问。
  
  如图,假如线程Thread-1,Thread-2,Thread-3同时尝试获取独占锁ReentrantLock,加上Thread-1获取到了??,则Thread-2和Thread-3就会被转换为Node节点并放入ReentrantLock对应的AQS阻塞队列,而后阻塞挂起。
  
  如图,假设Thread-1获取锁后调用了对应的锁创建的条件变量1,那么Thread-1就会释放获取到的??,然后当前线程就会被转换为Node节点插入条件变量1的条件队列。由于Thread-1释放了??,所以阻塞到AQS队列里面的
  
  Thread-2和Thread-3就会有机会获取到该锁,假如使用的是公平性策略,那么者时候Thread-2会获取到锁,从而从AQS队列里面移除Thread-2对应的Node节点。
  
  小结:
  
  本章介绍了ReentrantLock的实现原理,ReentrantLock的底层使用AQS实现的可重入独占锁。在这里AQS状态值为0表示当前??空闲,为大于1的值则说明该??已经被占用了。
  
  该??内部有公平与非公平实现,默认情况下是非公平的实现,另外,由于该锁的独占锁,所以某一时刻只有一个线程可以获取到该??。
  
  本文参考书籍
  
  Java并发编程之美

原文地址:https://www.cnblogs.com/qwangxiao/p/11220321.html

时间: 2024-08-27 22:53:37

可重入的独占锁——ReentrantLock源码分析的相关文章

Java并发编程 ReentrantLock 源码分析

ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(AbstractOwnableSynchronizer)封装的 公平与非公平锁. 所谓公平锁就是指 在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程,换句话说也就是先被锁定的线程首先获得锁. 非公平锁正好相反,解锁时没有固定顺序. 让我们边分析源代码边学习如何使用该类 先来看一下构造参数,默认

ReentrantLock源码分析--jdk1.8

JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分析--jdk1.8 ReentrantLock概述 ??1. ReentrantLock是独占锁.??2. ReentrantLock分为公平模式和非公平模式.??3. ReentrantLock锁可重入(重新插入) ReentrantLock源码分析 /** * @since 1.5 * @aut

Java并发编程之ReentrantLock源码分析

ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比ReentrantLock锁要差,而且ReentrantLock锁功能要比synchronized关键字功能强大. 特点 synchronized关键字和ReentrantLock锁都是重入锁,可重入锁是指当一个线程获取到锁后,此线程还可继续获得这把锁,在此线程释放这把锁前其他线程则不可获得这边锁.相比

ReentrantLock(重入锁)简单源码分析

1.ReentrantLock是基于AQS实现的一种重入锁. 2.先介绍下公平锁/非公平锁 公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁.有可能,会造成优先级反转或者饥饿现象. 3.重入锁/不可重入锁 可重入锁:广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁. 不可重入锁

被面试官吊打系列之JUC之 可重入读写锁ReentrantReadWriteLock 之 源码详尽分析

可重入读写锁 ReentrantReadWriteLock 其实基本上模拟了文件的读写锁操作.ReentrantReadWriteLock 和ReentrantLock 的差别还是蛮大的: 但是也有很多的相似之处: ReentrantReadWriteLock 的 writerLock 其实就是相当于ReentrantLock,但是它提供更多的细腻的控制:理解什么是读锁.写锁非常重要,虽然实际工作中区分读写锁这样的细分使用场景比较少. ReentrantReadWriteLock 把锁进行了细化

Java并发系列[5]----ReentrantLock源码分析

在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性.在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等.而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能.因此,在Java5.0中增加了一种新的机制:Reentrant

java并发-ReentrantLock源码分析

1关于可重入锁 ReentrantLock是基于AQS实现的可重入的同步工具类,它提供了两种同步器的实现即公平锁FairSync和非公平锁NonfairSync.它提供了一种无条件的.可轮询的.定时的以及可中断的锁获取操作,所有的加锁和解锁都是显式的.tryLock(),tryLock(long ,TimeUnit)分别提供了可轮询的.可定时的锁获取方式. Lock()提供了无条件地轮询获取锁的方式.lockInterruptibly()提供了可中断的锁获取方式. ReentrantLock它提

ReentrantLock源码分析

深入理解java同步.锁机制我们主要讲解了关于synchronized的实现和各种锁的类型,本节将尝试从源码的角度去理解可重入锁ReentrantLock的实现.由于个人水平有限,文中出现错误的地方还请指出,避免误导更多人. 要理解ReentrantLock需要先理解所有锁的基础.AQS(AbstractQueuedSynchronizer)主要利用硬件原语指令(CAS compare-and-swap),来实现轻量级多线程同步机制,并且不会引起CPU上文切换和调度,同时提供内存可见性和原子化更

Java并发系列(5)ReentrantLock源码分析

在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性. 在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等.而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能. 因此,在Java5.0中增加了一种新的机制:Reentra