河北智能网站建设多少钱,上海杨浦区建设网站,做网站企业经营范围,黄页官网JUC第十三讲#xff1a;JUC锁: ReentrantLock详解 本文是JUC第十三讲#xff0c;JUC锁#xff1a;ReentrantLock详解。可重入锁 ReentrantLock 的底层是通过 AbstractQueuedSynchronizer 实现#xff0c;所以先要学习上一章节 AbstractQueuedSynchronizer 详解。 文章目录 …JUC第十三讲JUC锁: ReentrantLock详解 本文是JUC第十三讲JUC锁ReentrantLock详解。可重入锁 ReentrantLock 的底层是通过 AbstractQueuedSynchronizer 实现所以先要学习上一章节 AbstractQueuedSynchronizer 详解。 文章目录 JUC第十三讲JUC锁: ReentrantLock详解1、带着BAT大厂的面试问题去理解2、ReentrantLock源码分析2.1、类的继承关系2.2、类的内部类2.3、类的属性2.4、类的构造函数2.5、核心函数分析 3、示例分析3.1、公平锁 4、参考文章 1、带着BAT大厂的面试问题去理解 请带着这些问题继续后文会很大程度上帮助你更好的理解相关知识点。 什么是可重入什么是可重入锁? 它用来解决什么问题? 一定程度避免死锁ReentrantLock的核心是AQS那么它怎么来实现的继承吗? 说说其类内部结构关系。独占模式ReentrantLock是如何实现公平锁的?ReentrantLock是如何实现非公平锁的?ReentrantLock默认实现的是公平还是非公平锁? 非公平使用ReentrantLock实现公平和非公平锁的示例?ReentrantLock和Synchronized的对比?
2、ReentrantLock源码分析
2.1、类的继承关系
ReentrantLock实现了Lock接口Lock接口中定义了lock与unlock相关操作并且还存在newCondition方法表示生成一个条件。
public class ReentrantLock implements Lock, java.io.Serializable2.2、类的内部类
ReentrantLock总共有三个内部类并且三个内部类是紧密相关的下面先看三个类的关系。
说明ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类NonfairSync与FairSync类继承自Sync类Sync类继承自 AbstractQueuedSynchronizer 抽象类。下面逐个进行分析。
Sync类
Sync类的源码如下:
abstract static class Sync extends AbstractQueuedSynchronizer {// 序列号private static final long serialVersionUID -5179523762034025860L;// 获取锁abstract void lock();// 非公平方式获取final boolean nonfairTryAcquire(int acquires) {// 当前线程final Thread current Thread.currentThread();// 获取状态int c getState();// 表示没有线程正在竞争该锁if (c 0) {// 比较并设置状态成功状态0表示锁没有被占用if (compareAndSetState(0, acquires)) {// 设置当前线程独占setExclusiveOwnerThread(current); return true; // 成功}}// 当前线程拥有该锁else if (current getExclusiveOwnerThread()) {// 增加重入次数int nextc c acquires; if (nextc 0) // overflowthrow new Error(Maximum lock count exceeded);// 设置状态setState(nextc); // 成功return true; }// 失败return false;}// 实现AQS提供的拓展点// 试图在共享模式下获取对象状态此方法应该查询是否允许它在共享模式下获取对象状态如果允许则获取它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 dont need to do so to check if current thread is ownerreturn getExclusiveOwnerThread() Thread.currentThread();}// 新生一个条件final ConditionObject newCondition() {return new ConditionObject();}// Methods relayed from outer class// 返回资源的占用线程final Thread getOwner() {return getState() 0 ? null : getExclusiveOwnerThread();}// 返回状态final int getHoldCount() {return isHeldExclusively() ? getState() : 0;}// 资源是否被占用final boolean isLocked() {return getState() ! 0;}/*** Reconstitutes the instance from a stream (that is, deserializes it).*/// 自定义反序列化逻辑private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();setState(0); // reset to unlocked state}
} Sync类存在如下方法和作用如下。 NonfairSync类
NonfairSync类继承了Sync类表示采用非公平策略获取锁其实现了Sync类中抽象的lock方法源码如下
// 非公平锁
static final class NonfairSync extends Sync {// 版本号private static final long serialVersionUID 7316153563782823691L;// 获得锁final void lock() {// 比较并设置状态成功状态0表示锁没有被占用if (compareAndSetState(0, 1))// 把当前线程设置独占了锁setExclusiveOwnerThread(Thread.currentThread());else // 锁已经被占用或者set失败// 以独占模式获取对象忽略中断acquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}说明从lock方法的源码可知每一次都尝试获取锁而并不会按照公平等待的原则进行等待让等待时间最久的线程获得锁。
FairSync类
FairSync类也继承了Sync类表示采用公平策略获取锁其实现了Sync类中的抽象lock方法源码如下
// 公平锁
static final class FairSync extends Sync {// 版本序列化private static final long serialVersionUID -3000897897090466540L;final void lock() {// 以独占模式获取对象忽略中断acquire(1);}/*** Fair version of tryAcquire. Dont grant access unless* recursive call or no waiters or is first.*/// 尝试公平获取锁 AQS抽象类提供的拓展点protected final boolean tryAcquire(int acquires) {// 获取当前线程final Thread current Thread.currentThread();// 获取状态int c getState();if (c 0) { // 状态为0// 不存在已经等待更久的线程 并且比较并且设置状态成功if (!hasQueuedPredecessors() compareAndSetState(0, acquires)) {// 设置当前线程独占setExclusiveOwnerThread(current);return true;}}// 状态不为0即资源已经被线程占据else if (current getExclusiveOwnerThread()) {// 下一个状态int nextc c acquires;if (nextc 0) // 超过了int的表示范围throw new Error(Maximum lock count exceeded);// 设置状态setState(nextc);return true;}return false;}
}说明跟踪lock方法的源码可知当资源空闲时它总是会先判断sync队列AbstractQueuedSynchronizer中的数据结构是否有等待时间更长的线程如果存在则将该线程加入到等待队列的尾部实现了公平获取原则。其中FairSync类的lock的方法调用如下只给出了主要的方法。
说明可以看出只要资源被其他线程占用该线程就会添加到sync queue中的尾部而不会先尝试获取资源。这也是和Nonfair最大的区别Nonfair每一次都会尝试去获取资源如果此时该资源恰好被释放则会被当前线程获取这就造成了不公平的现象当获取不成功再加入队列尾部。
2.3、类的属性
ReentrantLock类的sync非常重要对 ReentrantLock 类的操作大部分都直接转化为对Sync和 AbstractQueuedSynchronizer 类的操作。
public class ReentrantLock implements Lock, java.io.Serializable {// 序列号private static final long serialVersionUID 7373984872572414699L; // 同步队列private final Sync sync;
}2.4、类的构造函数
ReentrantLock() 型构造函数
默认是采用的非公平策略获取锁
public ReentrantLock() {// 默认非公平策略sync new NonfairSync();
}ReentrantLock(boolean) 型构造函数
可以传递参数确定采用公平策略或者是非公平策略参数为true表示公平策略否则采用非公平策略:
public ReentrantLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync();
}2.5、核心函数分析
通过分析ReentrantLock的源码可知对其操作都转化为对Sync对象的操作由于Sync继承了AQS所以基本上都可以转化为对AQS的操作。如将ReentrantLock的lock函数转化为对Sync的lock函数的调用而具体会根据采用的策略(如公平策略或者非公平策略)的不同而调用到Sync的不同子类。
所以可知在ReentrantLock的背后是AQS对其服务提供了支持由于之前我们分析AQS的核心源码遂不再累赘。下面还是通过例子来更进一步分析源码。
3、示例分析
3.1、公平锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class MyThread extends Thread {private Lock lock;public MyThread(String name, Lock lock) {super(name);this.lock lock;}public void run () {lock.lock();try {System.out.println(Thread.currentThread() running);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}
}public class AbstractQueuedSynchronizerDemo {public static void main(String[] args) throws InterruptedException {Lock lock new ReentrantLock(true);MyThread t1 new MyThread(t1, lock); MyThread t2 new MyThread(t2, lock);MyThread t3 new MyThread(t3, lock);t1.start();t2.start(); t3.start();}
}运行结果(某一次):
Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running说明: 该示例使用的是公平策略由结果可知可能会存在如下一种时序。 说明: 首先t1线程的lock操作 - t2线程的lock操作 - t3线程的lock操作 - t1线程的unlock操作 - t2线程的unlock操作 - t3线程的unlock操作。根据这个时序图来进一步分析源码的工作流程。
t1线程执行lock.lock下图给出了方法调用中的主要方法。
说明: 由调用流程可知t1线程成功获取了资源可以继续执行。
t2线程执行 lock.lock下图给出了方法调用中的主要方法。 说明: 由上图可知最后的结果是t2线程会被禁止因为调用了LockSupport.park。
t3线程执行lock.lock下图给出了方法调用中的主要方法。 说明由上图可知最后的结果是t3线程会被禁止因为调用了LockSupport.park。
t1线程调用了lock.unlock下图给出了方法调用中的主要方法。
说明如上图所示最后head的状态会变为0t2线程会被unpark即t2线程可以继续运行。此时t3线程还是被禁止。
t2获得cpu资源继续运行由于t2之前被park了现在需要恢复之前的状态下图给出了方法调用中的主要方法。 说明在setHead函数中会将head设置为之前head的下一个结点并且将pre域与thread域都设置为null在acquireQueued返回之前sync queue就只有两个结点了。
t2执行lock.unlock下图给出了方法调用中的主要方法。
说明: 由上图可知最终unpark t3线程让t3线程可以继续运行。
t3线程获取cpu资源恢复之前的状态继续运行。 说明: 最终达到的状态是sync queue中只剩下了一个结点并且该节点除了状态为0外其余均为null。
t3执行lock.unlock下图给出了方法调用中的主要方法。 说明: 最后的状态和之前的状态是一样的队列中有一个空节点头节点为尾节点均指向它。
使用公平策略和Condition的情况可以参考上一篇关于AQS的源码示例分析部分不再累赘。
4、参考文章
【JUC】JDK1.8源码分析之ReentrantLock三