前言
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
分享到:
相关推荐
它们都继承自AbstractQueuedSynchronizer(AQS),AQS是Java并发包中一个重要的抽象类,用于构建锁和其他同步组件的基础框架。 Sync是ReentrantLock的抽象静态内部类,它扩展了AQS,并定义了获取锁的抽象方法lock()...
ReentrantLock 是 Java 并发包中的一个重要组件,它支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。这种机制可以让开发者更方便地编写并发程序。 ReentrantLock 的实现原理 ...
它是实现Java并发包中锁和其他同步器的基础框架,例如ReentrantLock(可重入锁)、Semaphore(信号量)、CountDownLatch(倒计时门闩)、CyclicBarrier(循环栅栏)以及ReentrantReadWriteLock(可重入读写锁)等。...
它提供了实现排他模式(独占式)和共享模式锁的机制,被广泛应用于Java并发包中的ReentrantLock、Semaphore、CountDownLatch等组件中。 首先,AQS是一个抽象类,它的主要特点是使用一个int类型的变量(state)表示...
Java并发包是Java SE平台的核心组成部分,它包含了各种同步原语、线程池和并发集合等组件。在这个自然选择模拟项目中,我们可以预见以下关键知识点的运用: 1. **线程**:Java中的`Thread`类是创建和管理线程的基础...
2. **同步机制**:详述了Java中同步的几种方法,如`synchronized`关键字、volatile变量、Lock接口(如ReentrantLock)以及Condition。这些机制用于防止数据竞争和确保线程安全。 3. **并发工具类**:Java并发包...
AQS是Java并发包中用于构建锁和同步组件的核心抽象类,它基于一种FIFO(先进先出)的等待队列机制。AQS维护了一个int类型的state字段,用于表示资源的状态。当线程试图获取资源时,如果资源不可用,线程会被添加到...
AQS是`AbstractQueuedSynchronizer`的缩写,它是Java并发包中的一个核心组件,提供了一种基于状态(state)的线程同步机制。它维护了一个双端等待队列,用于管理那些未能立即获取锁的线程。AQS的子类通过定义两种...
ReentrantLock,即可重入锁,是Java并发包(java.util.concurrent.locks)中的一个核心组件,它提供了比synchronized更灵活的锁机制。ReentrantLock实现了Lock接口,具备公平锁与非公平锁两种模式,同时支持中断等待...
关于AbstractQueuedSynchronizer,它是java.util.concurrent包中并发控制的核心组件,为Java并发工具类提供了底层机制支持,例如ReentrantLock、Semaphore、CountDownLatch等,都依赖于这个框架来实现同步。...
线程池(ThreadPoolExecutor)是Java并发包中的重要组件,它用于管理线程的生命周期,提高性能并减少在建立和销毁线程上的开销。线程池使用一组预定义的线程来执行任务,可以定义线程池的类型和参数,以适应不同的...
`AbstractQueuedSynchronizer`(AQS)是`ReentrantLock`的核心组件之一,它提供了一个框架来构建锁和其他同步组件。AQS维护了一个FIFO线程等待队列,当线程无法获取锁时,会被插入到队列中。队列中的线程会被暂时挂...
14. **阻塞队列(Blocking Queue)和阻塞栈(Blocking Stack)**:Java并发包中的LinkedBlockingQueue和ArrayBlockingQueue是典型的阻塞队列实现,它们在插入和移除元素时能自动处理线程阻塞。Deque接口的实现如...
- **并发工具类**:Java并发包(`java.util.concurrent`)提供了大量工具类,如`Future`、`Callable`等,用于处理异步计算结果;`AtomicInteger`、`AtomicLong`等原子类用于实现高效的线程安全的整数操作;`...
ReentrantLock是Java并发包java.util.concurrent.locks中的一个类,它是可重入的互斥锁,具备了synchronized的同步特性,同时提供了更多的高级功能。可重入性意味着一个线程可以获取同一锁多次而不被阻塞,这对于...
Java并发包(java.util.concurrent)包含各种并发工具类,如ConcurrentHashMap、BlockingQueue和CountDownLatch等。这些工具类为并发编程提供了强大支持,例如,ConcurrentHashMap在并发环境下提供高效的键值对存储...