Java
并发编程核心在于java.concurrent.util
包
而juc
当中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这个行为的抽象就是基于AbstractQueuedSynchronizer
简称AQS
,AQS
定义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器。
AQS
具备特性
- 阻塞等待队列
- 共享/独占
- 公平/非公平
- 可重入
- 允许中断
ReentrantLock
ReentrantLock
定义了一个内部类Sync
继承了AQS
,通过这种方式将同步器所有调用都映射到Sync对应的方法,同时ReentrantLock
也具备AQS
的特性。ReentrantLock
具备以下特性
- 阻塞等待队列
- 独占
- 公平/非公平
- 可重入
- 允许中断
ReentrantLock
继承关系
ReentrantLock
里面定义了三个内部类Sync
、FairSync
、NonfairSync
,FairSync
、NonfairSync
表示公平锁与非公平锁,通过这两个内部类实现公平与非公平的机制,具体关系如下图:
Lock.png
FairSync
、NonfairSync
是通过继承Sync
实现的,其中FairSync
加锁机制为先判断列队中是否有其它线程正在等待加锁,如果有则直接入队,没有才会尝试加锁;而NonfairSync
加锁机制为先进行尝试加锁,加锁失败才进行入队。
除此之外,ReentrantLock
还可通过lockInterruptibly()
加锁时可使用Thread.interrupt()
进行中断加锁过程。
ReentrantLock
加锁过程(简述)
公平锁加锁过程
- 查看队列中是否有其它线程等待加锁
- 队列为空或者等待加锁的线程为当前线程,尝试
CAS
加锁 - 加锁成功
state+1
,结束 - 加锁失败(只发生在队列为空的情况),或者队列不为空且等待加锁的线程非当前线程,进行入队。
- 入队完毕,将前驱结点信号量(
waitStatus
)改为-1,阻塞等待LockSupport.park(this)
解锁过程
- 解锁
state-1
- 判断
state==0
(可重入锁,每次加锁state
都会+1
,释放锁次数必须等于加锁次数才算已解锁) - 判断队列中是否有等待加锁的线程
- 将线程唤醒
LockSupport.unpark(thread)
ReentrantLock
与synchronized
比较
- 加锁机制比较
ReentrantLock
,基于AQS
实现的锁,需要手动加锁解锁,细粒度和灵活度更高synchronized
是JVM
内部锁,JVM
会自动加锁与解锁
- 底层原理比较
- 都是依赖底层操作系统的
Mutex lock(互斥锁)
实现
- 锁类型比较
synchronized
是一种可重入的非公平锁ReentrantLock
是基于AQS
实现,具体特性上文已有说明
- 性能比较(未实测)
- 在并发量小时,
synchronized
性能更高一些 - 并发量高时,
synchronized
性能将会下降(升级为重量级锁),而ReentrantLock
基本不变。
我这里的理解是,
synchronized
和ReentrantLock
底层阻塞原理虽然是一样的,而ReentrantLock
利用CAS
自旋操作实现锁,能够有效避免线程从用户态到内核态的频繁切换。