前言
ReentrantLock作为Java并发包显示锁的典型实现,又被称作可重入的独占锁,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,等同于synchronized的使用,但是ReentrantLock提供了比synchronized更强大、灵活的锁机制,可以减少死锁发生的概率。
因为ReentrantLock是直接实现Lock接口的显示锁,所以它不但实现了阻塞式获取同步锁的方法,而且还实现了非阻塞的、响应超时或中断获取同步锁的功能,跟进一步的是,ReentrantLock还实现了对公平锁(FairSync)和非公平锁(NonfairSync)的支持,我们可以在使用的时候根据需要选择使用不同的公平策略,公平锁与非公平锁的区别在于公平锁的锁获取是有顺序的。但是公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。
ReentrantLock重入锁源码分析
有了前面对AQS源码的分析,以及如何自定义同步器的学习,我们再来分析ReentrantLock的源码就显得完全不值一提,因为它完全就是按照我们之前自定义同步器的方式编写的,所不同的是它同时实现了公平锁与非公平锁两种机制,并额外提供了很多查询同步队列状态的方法供开发者使用而已。
从上面的ReentrantLock类结构可以看出,首先ReentrantLock实现了Lock、Serializable接口,ReentrantLock的抽象静态内部类Sync继承AQS基类,再分别由静态内部类FairSync和NonfairSync实现了公平锁和非公平锁的逻辑。
构造方法
public ReentrantLock() {
sync = new NonfairSync();//默认使用非公平锁
}
public ReentrantLock(boolean fair) { //根据参数采用何种锁
sync = fair ? new FairSync() : new NonfairSync();
}
通过构造方法可以看出,ReentrantLock默认使用了非公平锁,原因无他,只是因为非公平锁的运行效率,吞吐量更高。要想使用公平锁外面只能通过参数指定。
非公平获取锁
我站在使用者的角度从最上层往下分析源码,首先从默认的非公平锁的获取操作开始。值得注意的是,对于ReentrantLock锁的获取操作在内部类Sync中被定义为了抽象方法,其具体的实现由FairSync和NonfairSync来。所以我们首先看NonfairSync对lock()方法的实现:
final void lock() {
if (compareAndSetState(0, 1)) //直接尝试获取锁,
setExclusiveOwnerThread(Thread.currentThread()); //设置当前线程为该独占锁的拥有者
else
acquire(1); //走正常流程获取锁
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); //这里nonfairTryAcquire被定义在父类Sync中
}
从以上非公平锁在堆获取锁的方法lock()的实现可以看出,在走正常的AQS队列等待获取锁的方式之前,它会直接通过CAS快速的尝试获取同步锁,万一这时候刚好锁被其他拥有线程释放,后面等待的线程还没来得及获取锁呢,所以不公平的特性在这里也得到了体现,新的尝试获取锁的线程反而有可能会抢占等待队列中的线程获取锁的机会。
我们接着往下看,只有在直接尝试获取锁失败之后,它才会执行AQS的独占式获取锁的顶层入口方法acquire(int)方法,我们知道acquire(int)方法最终又会调用被我们覆写的tryAcquire(int)方法尝试获取锁,成功则返回,失败则加入同步等待队列被阻塞等待被前驱唤醒。所以我们接着看被我们覆写的tryAcquire()方法, 这里的tryAcquire()方法的实现其实执行的是其父类Sync中的nonfairTryAcquire(int)方法,所以我们移步到抽象父类Sync中的nonfairTryAcquire()方法。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//获取同步状态
if (c == 0) { //state == 0,表示没有该锁处于空闲状态
//直接尝试获取锁成功,设置为当前线程所有
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果占用锁的线程时当前线程则表示重入,
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; //重入则加1(acquires固定为1)
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc); //因为不存在竞争,直接修改状态即可
return true;
}
return false;
}
由上面的代码注释已经很清楚了,所以非公平获取锁的逻辑总结如下:
- 直接通过CAS尝试修改同步状态state获取锁,成功设置当前线程为锁的占有者,失败走AQS的acquire(),tryAcquire()逻辑。
- 走AQS的acquire()的时候,通过调用tryAcquire()执行自定义的逻辑,即通过判断同步状态进行不同的逻辑处理。
- 如果同步状态为空闲,再次直接通过CAS尝试修改同步状态state获取锁,成功之后设置当前线程为锁的占有者,返回true.
- 如果同步状态不为空闲,但是是重入的情况,则直接对同步状态进行加1,返回true.
- 如果通过状态不为空闲,也不是重入的情况,则通过AQS独占式的内部逻辑,添加到同步等待队列,等待唤醒。
公平式获取锁
分析了默认的非公平式获取锁的过程,我们接着分析公平锁的获取,它对lock()方法的实现在FairSync内部类中:
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//同步状态位空闲时,还要判断当前同步等待队列是否存在不是当前线程的下一个等待执行的线程。
//hasQueuedPredecessors方法很简单,如果队列为空,或者当前线程是头节点的后继节点就返回false
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;
}
通过以上公平锁的获取源码我们可以得出它的获取逻辑如下:
- 在进入同步等待队列之前,只会利用AQS的acquire(),tryAcquire()逻辑来尝试获取同步锁,而在尝试获取锁的时候,通过判断同步状态进行不同的逻辑处理
- 如果同步状态为空闲,还要对同步等待队列进行检查,如果同步队列为空,或者自己是排在第二位的代表下一个即将有资格获取锁的线程时才会通过CAS获取同步锁。
- 如果同步状态不为空闲,但是是重入的情况,则直接对同步状态进行加1,返回true.
- 其他情况都需要进入同步等待队列排队等待。
通过分析公平锁与非公平锁的获取过程可以看到,它们至少存在两个不同的地方:第一个不同点在于,非公平获取锁的过程中存在两次强行使用CAS直接获取锁的操作,在强行获取失败之后才会心甘情愿的进入FIFO的同步等待队列老老实实的排队等候,而公平锁只有在同步状态为空闲,并且同步队列为空或者自己就是下一个即将有资格尝试获取锁的线程的时候才会通过CAS进行尝试获取锁,其他情况(当然不包括重入)都会毫不犹豫的直接进入等待队列排队等候。这两个不同点也正是非公平锁与公平锁的差异,总结起来所谓的公平,只有在轮到自己的时候才做出尝试,绝不插队。而所谓的非公平,其实就是在进入睡眠等待队列之前,会不放过任何一个可以利用的空闲时机,让自己早点获取到锁。非公平锁可能会导致某个线程长时间处于饥饿中,但是极少线程切换,提升锁的获取成功率,增大系统吞吐量。
ReentrantLock锁的释放
不同于锁的获取过程,锁的释放对于公平锁和非公平锁的过程都一样,所以我们接着看看ReentrantLock的锁释放方法unlock().
public void unlock() {
sync.release(1);//直接调用的AQS独占锁释放的顶层入口方法
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //直接减去releases
//在真正进行释放之前要进行锁的拥有者判断,如果不是当前线程在占有锁则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //state ==0 表示彻底释放了,其他线程才能够获取到锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
锁的释放过程很简单,直接执行的AQS独占锁的锁释放顶层入口方法,通过回调自定义的tryRelease()方法真正进行释放操作。在释放之前要判断是否是当前线程占用锁,并且将重入彻底释放之后,才会清空当前锁的拥有者,并返回true, 在没有彻底释放之前,返回false.
对于 ReentrantLock的其他非阻塞式、响应超时或中断的锁获取方法,原理非常简单,就不在一一熬述。另外,ReentrantLock类还提供了一系列的对同步等待队列和条件等待队列的查询方法,这里也都不在介绍,在需要的时候可以自行查阅。
- 大小: 13.6 KB
分享到:
相关推荐
java并发包讲解 可以找我要代码,qq 3341386488 ## 线程安全-并发容器JUC--原理以及分析 1.arrayList --copyonWriteArraylist 优缺点 2.HashSet,TreeSet -- CopyONWriteArraySet,ConcurrentSkipListSet 3....
深入java并发编程,使用ReentrantLock和 Synchronized加锁
java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能。而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。这篇文章主要是从使用的角度来分析...
【Java并发】当年因为Reentrantlock被拒,今天把它源码里里外外给扒了
1、ReentrantLock简介 2、ReentrantLock函数列表 3、重入的实现 4、公平锁与非公平锁 5、ReentrantLock 扩展的功能 6
Java多线程ReentrantLock1
重入锁ReentrantLock 相对来说是synchronized、Object.wait()和Object.notify()方法的替代品(或者说是增强版),在JDK5.0的早期版本,重入锁的性能远远好于synchronized,但从JDK6.0开始,JDK在synchronized上做了...
主要介绍了Java中的显示锁ReentrantLock使用与原理详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
Java并发包源码分析(JDK1.8):囊括了java.util.concurrent包中大部分类的源码分析,其中涉及automic包,locks包(AbstractQueuedSynchronizer、ReentrantLock、ReentrantReadWriteLock、LockSupport等),queue...
java语言 并发编程 ReentrantLock与synchronized区别 详解
ReentrantLock.java
使用ReentrantLock和Lambda表达式让同步更纯净Java开发Java经验技巧共5页.pdf.zip
Java并发编程基础主要包括以下几个核心方面: ...并发工具类:掌握Java并发包java.util.concurrent中提供的各种工具类,如CountDownLatch、CyclicBarrier、Semaphore等,它们简化了并发编程的复杂性。
这份资源旨在详细讲解 Java 中的 Locks 框架,特别关注 ReentrantLock 的使用和原理。Locks 框架提供了比传统的 synchronized 关键字更强大、更灵活的线程同步机制,而 ReentrantLock 是其中的一种重要实现。 Locks ...
带你看看Javad的锁-ReentrantLock前言ReentrantLock简介Synchronized对比用法源码分析代码结构方法分析SyncNonfairSyncFairSync非公平锁VS公平锁什么是公平非公平ReentrantLockReentrantLock的构造函数lock加锁方法...
ReentrantLock的基本用法 2.1 创建ReentrantLock 2.2 获取锁和释放锁 公平性与非公平性 3.1 公平锁 3.2 非公平锁 中断响应 条件变量与Condition 5.1 创建Condition 5.2 await()和signal() 可重入性 ReentrantLock与...
Java多线程并发的程序中使用互斥锁有synchronized和ReentrantLock两种方式,这里我们来详解Java多线程编程中互斥锁ReentrantLock类的用法:
主要介绍了Java多线程 ReentrantLock互斥锁详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
同步控制是并发程序必不可少的重要手段,本文我们将通过重入锁、读写锁、信号量、倒计数器和循环栅栏以及他们的实例来介绍Java并发程序中的同步控制。 目录线程安全 Thread Safety重入锁 ReentrantLock读写锁 ...