互联网JAVA面试常问问题(六)

  • 2019 年 10 月 6 日
  • 笔记

前几篇文章,都介绍了JAVA面试中锁相关的知识。其实关于JAVA锁/多线程,你还需要知道了解关于ReentrantLock的知识,本文将从源码入手,介绍可重入锁的相关知识。

ReentrantLock

先来看看ReentrantLock的源码,部分代码块用省略号代替,后面会详细展开介绍:

public class ReentrantLock implements Lock, java.io.Serializable {        private static final long serialVersionUID = 7373984872572414699L;        /** Synchronizer providing all implementation mechanics */     private final Sync sync;        abstract static class Sync extends AbstractQueuedSynchronizer {            private static final long serialVersionUID = -5179523762034025860L;        /**          * Performs {@link Lock#lock}. The main reason for subclassing          * is to allow fast path for nonfair version.          */         abstract void lock();        /**          * Performs non-fair tryLock.  tryAcquire is implemented in          * subclasses, but both need nonfair try for trylock method.          */         final boolean nonfairTryAcquire(int acquires) {             .......         }          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;         }            protected final boolean isHeldExclusively() {              // While we must in general read state before owner,             // we don't need to do so to check if current thread is owner             return getExclusiveOwnerThread() == Thread.currentThread();         }          ........     }    /**      * Sync object for non-fair locks      */     static final class NonfairSync extends Sync {        .......     }    /**      * Sync object for fair locks      */     static final class FairSync extends Sync {        ...........     }  ..........  }

可以看出可重入锁的源码中,其实实现了公平锁和非公平锁。

ReentrantLock中有一个静态内部抽象类Sync,然后有NonfairSync和FairSync两个静态类继承了Sync。

其中Sync继承了AQS(AbstractQueuedSynchronizer),接下来的文章中会介绍详细AQS。

我们在使用可重入锁的时候,需要明显的加锁和释放锁的过程。一般在finally代码中实现锁释放的过程。

Lock lock = new ReentrantLock();    Condition condition = lock.newCondition();  lock.lock();    try {  while(条件判断表达式) {       condition.wait();   }  // 处理逻辑    }  finally  {   lock.unlock();   }
非公平锁的实现
    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);         }     }

可以看出,非公平锁在执行lock的时候,会用CAS来尝试将锁状态改成1,如果修改成功,则直接获取锁,用setExclusiveOwnerThread方法讲当前线程设置为自己。如果没有修改成功,则会执行acquire方法来尝试获取锁。其中,nonfairTryAcquire实现如下:

        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;         }

可以看出这个方法,其实也是在用CAS尝试将线程状态置为1。其实也是一个多次尝试获取的过程。

所以,对于非公平锁,当一线程空闲时候,其他所有等待线程拥有相同的优先级,谁先争抢到资源即可以获取到锁。

公平锁的实现
  static final class FairSync extends Sync {          private static final long serialVersionUID = -3000897897090466540L;        final void lock() {             acquire(1);         }        /**          * Fair version of tryAcquire.  Don't grant access unless          * recursive call or no waiters or is first.          */         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方法的时候,会调用 acquire方法, acquire方法首先尝试获取锁并且尝试将当前线程加入到一个队列中,所以公平锁其实是维护了一个队列,谁等待的时间最长,当线程空闲时候,就会最先获取资源:

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

如果想了解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;                 }                 if (shouldParkAfterFailedAcquire(p, node) &&                     parkAndCheckInterrupt())                     interrupted = true;             }         } finally {             if (failed)                 cancelAcquire(node);         }     }

综上,可重入锁利用CAS原理实现了公平锁和非公平锁,为什么叫做可重入锁呢?其实在代码方法tryAcquire中可以看到,线程可以重复获取已经持有的锁。

if (current == getExclusiveOwnerThread()) {      int nextc = c + acquires;      if (nextc < 0) // overflow          throw new Error("Maximum lock count exceeded");     setState(nextc);      return true;  }

今天小强从源码的角度分析了ReentrantLock,希望对正在学习JAVA的你有所帮助。