阅读了ReentrantLock
的源码,简单总结了一些实现上的要点如下:
synchronized
是Java原生的互斥同步锁,使用方便,对于synchronized
修饰的方法或同步块,无需再显式释放锁。synchronized
底层是通过monitorenter
和monitorexit
两个字节码指令来实现加锁解锁操作的。
而ReentrantLock
做为API层面的互斥锁,需要显式地去加锁解锁。底层是基于AbstractQueuedSynchronizer
实现的。AbstractQueuedSynchronizer
是JAVA并发很强大的同步器,最终是通过LockSupport
提供的park, unpark
来实现对线程的阻塞和唤醒的。使用
isHeldByCurrentThread()
或者getHoldCount()
判断当前线程是否持有锁。支持公平锁和非公平锁。
- 多线程环境下使用非公平锁时吞吐量会相对比较低,但是可以保证不会饥饿。
- 锁的公平不代表线程调度的公平性。
内部实现包含两个内部类:NonfairSync
和FairSync
Sync
继承自AbstractQueuedSynchronizer
,是个队列。
默认非公平。
public ReentrantLock() {
sync = new NonfairSync();
}
3.使用时,记得try{ ... } catch(){} finally { lock.unlock() }
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
- 调用
tryLock()
的时候是会忽略公平锁设置的。
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
如果还想保留公平性,调用tryLock(0, TimeUnit.SECONDS)
如果想要一个带时限的公平性又没有那么严格的锁,可以如下:
if (lock.tryLock() || lock.tryLock(timeout, unit)) {
...
}
持有线程对锁拥有状态是有计数的,有多少次
lock()
,就需要对应多少次的unlock()
,一次unlock()
只会让计数减1,当计数为0是,当前线程释放锁。类似
wait(), notify(), notifyAll()
与内置锁配合使用,ReentrantLock
可以和Condition
配合使用,相关的方法有await(), signal(), signalAll()
,这些方法均需要在获得锁的情况下调用,否则会报IllegalMonitorStateException
。await()
的时候会释放锁,当被signal
之后,需要重新获得锁,锁计数会恢复。
总结
ReentrantLock是一种可重入的,可实现公平性的互斥锁,它的设计基于AQS框架,可重入和公平性的实现逻辑都不难理解,每重入一次,state就加1,当然在释放的时候,也得一层一层释放。至于公平性,在尝试获取锁的时候多了一个判断:是否有比自己申请早的线程在同步队列中等待,若有,去等待;若没有,才允许去抢占。
引用
JDK1.8中ReentrantLock源码分析
Java并发系列之ReentrantLock源码分析
第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()
Java8源码分析】locks包-ReentrantLock