目录
ReentrantLock 和 Synchronized的对比
ReentrantLock基础
ReentrantLock的功能是实现代码段的并发访问控制,也就是通常意义上所说的锁,java中实现锁有两种方式,一种是本文所提的ReentrantLock,另一种是synchronized
ReentrantLock
是可重入的互斥锁,底层基于AbstractQueuedSynchronizer
实现
可重入性是指当一个线程拥有一个方法的锁以后,是否还可以进入该方法,一般这种情况出现在递归中。ReentrantLock的可重入性是基于Thread.currentThread()实现的,是线程粒度的,也就是说当前线程获得一个锁以后,当前线程的所有方法都可以获得这个锁。
Reentrant支持公平锁和非公平锁
非公平锁的效率高于公平锁
非公平锁可能出现 线程饥饿问题——部分线程迟迟无法获得资源
ReentrantLock 和 Synchronized的对比
关系图
ReentrantLock
实现了Lock
接口,Lock
接口是Java
中对锁操作行为的统一规范
AbstractQueuedSynchronizer
AbstractQueuedSynchronizer
抽象类定义了一套多线程访问共享资源的同步模板,解决了实现同步器时涉及的大量细节问题,能够极大地减少实现工作,用大白话来说,AbstractQueuedSynchronizer
为加锁和解锁过程提供了统一的模板函数,只有少量细节由子类自己决定
AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态。
AQS是一个同步器,设计模式是模板模式。核心数据结构:双向链表 + state(锁状态);底层操作:CAS
AQS中有几个重要的概念:
-
state:用来记录可重入锁的上锁次数;
-
exclusiveOwnerThread:AQS继承了AbstractOwnableSynchronizer,而其中有个属性exclusiveOwnerThread,用来记录当前独占锁的线程是谁;
-
CLH同步队列:FIFO双向链表队列,此CLH队列是原CLH的变种,由原来的不断自旋改为了阻塞机制。队列中有头节点和尾节点两个指针,尾节点就是指向最后一个节点,而头节点为了便于判断,永远指向一个空节点,之后才是第一个有数据的节点;
-
条件队列:能够使某些线程一起等待某个条件具备时,才会被唤醒,唤醒后会被放到CLH队列中重新争夺锁资源。
AQS定义资源的访问方式有两种:
-
独占模式:只有一个线程能够获取锁,如ReentrantLock;
-
共享模式:多个线程可以同时获取到锁,如Semaphore、CountDownLatch和CyclicBarrier。
ReentrantLock
ReentrantLock
实现了Lock
接口,lock定义如下
ReentrantLock内部结构
ReentrantLock
要去实现这些函数,遵循着解耦可扩展设计,ReentrantLock
内部定义了专门的组件Sync
, Sync
继承AbstractQueuedSynchronizer
提供释放资源的实现,NonfairSync
和FairSync
是基于Sync
扩展的子类,即ReentrantLock
的非公平模式与公平模式
在ReentrantLock
中,它对AbstractQueuedSynchronizer
的state
状态值定义为线程获取该锁的重入次数,state
状态值为0
表示当前没有被任何线程持有,state
状态值为1
表示被其他线程持有,因为支持可重入,如果是持有锁的线程,再次获取同一把锁,直接成功,并且state
状态值+1
,线程释放锁state
状态值-1
,同理重入多次锁的线程,需要释放相应的次数。
lock
ReentrantLock的lock是用的sync实现的,sync依赖AQS实现,AQS使用模板模式,一些函数让子类实现(公平锁和非公平锁)
sync的lock代码
其中initialTryLock会调用公平锁/非公平锁的方法,acquire调用AQS的方法
Sync
Sync
继承了AbstractQueuedSynchronizer,
可以说是ReentrantLock
的核心,后面的NonfairSync
与FairSync
都是基于Sync
扩展出来的子类。
sync源码
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs non-fair tryLock.
*/
@ReservedStackAccess
final boolean tryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (getExclusiveOwnerThread() == current) {
if (++c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
/**
* Checks for reentrancy and acquires if lock immediately
* available under fair vs nonfair rules. Locking methods
* perform initialTryLock check before relaying to
* corresponding AQS acquire methods.
*/
abstract boolean initialTryLock();
@ReservedStackAccess
final void lock() {
if (!initialTryLock())
acquire(1);
}
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
boolean free = (c == 0);
if (free)
setExclusiveOwnerThread(null);
setState(c);
return free;
}
}
非公平锁
NonfairSync 类继承了 Sync类,表示采用非公平策略获取锁,其实现了 Sync类中抽象的 lock方法,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。Acquire方法是 FairSync和 UnfairSync的父类 AQS中的核心方法。
FairSync 类也继承了 Sync类,表示采用公平策略获取锁,其实现了 Sync类中的抽象 lock方法,源码如下
公平锁
FairSync
流程与NonfairSync
基本一致,唯一的区别就是在C A S
执行前,多了一步hasQueuedPredecessors
函数,这一步就是判断当前线程是不是CLH
队列被唤醒的线程,如果是就执行C A S
,否则获取资源失败
当资源空闲时,它总是会先判断 sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。
只要资源被其他线程占用,该线程就会添加到 sync queue中的尾部,而不会先尝试获取资源。这也是和 Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。
加锁:
- 通过ReentrantLock的加锁方法Lock进行加锁操作。
- 会调用到内部类Sync的Lock方法,由于Sync#lock是抽象方法,根据ReentrantLock初始化选择的公平锁和非公平锁,执行相关内部类的Lock方法,本质上都会执行AQS的Acquire方法。
- AQS的Acquire方法会执行tryAcquire方法,但是由于tryAcquire需要自定义同步器实现,因此执行了ReentrantLock中的tryAcquire方法,由于ReentrantLock是通过公平锁和非公平锁内部类实现的tryAcquire方法,因此会根据锁类型不同,执行不同的tryAcquire。
- tryAcquire是获取锁逻辑,获取失败后,会执行框架 AQS的后续逻辑,跟ReentrantLock自定义同步器无关。
解锁:
- 通过 ReentrantLock的解锁方法 Unlock进行解锁。
- Unlock会调用内部类 Sync的 Release方法,该方法继承于AQS。
- Release中会调用 tryRelease方法,tryRelease需要自定义同步器实现,tryRelease只在ReentrantLock中的Sync实现,因此可以看出,释放锁的过程,并不区分是否为公平锁。
- 释放成功后,所有处理由AQS框架完成,与自定义同步器无关。
通过上面的描述,大概可以总结出 ReentrantLock加锁解锁时 API层核心方法的映射关系。