一、初识
ReentrantLock
出身自Java 1.5
,中文名可重入锁
是Java JDK自带独占锁的唯一实现,也是最常用的锁,是synchronized的升级版。
1. 我们中间有个synchronized
我们已经认识过synchronized了,知道她能帮我们实现线程同步提供原子性语义,同时又有可重入性。同时我们也已经知道了可重入性是什么意思,也知道公平性的含义。
当然,我们很JRE如何实现synchronized,实现它的可重入性。
但我们可以通过阅读ReentrantLock的源码,来加深对sychronized一些特性的理解。
2. 升级版,升了什么
前面说了ReentrantLock是sychronized的升级版,那么ReentrantLock升级了什么,为我们带哪些新特性呢?
- tryLock 尝试获取锁,直接穿透(无视)
公平性
- isLocked 当前锁是不是被持有(含自己和他人),用来监控系统(锁)状态
- hasQueuedThreas(has) 提供更多监控,这一系列含has方法都是实现状态监控
- Fair And Non-Fair Model ReentrankLock提供公平与非公平两个模式
- Condition 在Lock内用Condition代替Synchronized的Object监控
这些都是非常好用,非常实用的功能,而synchronized却没有的特征。
还有还有,ReentrankLock还提供了一个可中断的方法。
3. Condition
当把ReentrantLock当成synchronized时,你需要把Condition当成Object监控。功能和用法都一样,只是名字不同而已。
项目 | 等待 | 唤醒 | 唤醒所有 | 用法 |
---|---|---|---|---|
Object | wait | notify | notifyAll | 在synchronized语块内 |
Condition | await | signal | signalAll | 在Lock语块内 |
二、你好!ReentrankLock
我们已经认识过公平性了,我们知道她的语义是是否先到先得
。那么她怎么实现的呢,我们好像还没看过。接下来我们将通过阅读ReentrantLock的源码,来看看她是怎么实现公平
性的。先贴代码吧
1. 公平
// ReentranLock$FiarSync
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
你应该从一小段代码里看出三个东西,
1. 公平
2. 可重入
3. 独占式
JDK在实现锁的时候通常用’0’和’大于0`表示锁状态,即没锁上和已上锁。
从这一小段源码里面可以看到
– ReentrantLock先自己尝试去获取锁,即是检查state的状态码
1. 对于state为0(即锁没被持有的时候),且前面没人在排队,理论上对这种情况特别简单直接上锁就可以了,如锁操作是线程安全的。然后并没有,我们之前在整理AQS框架
的时候已经了解了AQS是通过CAS实现同步
,即通过一组原子性的操作完成的。
当state不为0时,她就会去判断是谁持有,如果是自己的话,依然可以再次获得,这就是可重入性。同时state自增,此时可以反映两个问题,state还代表递归层级,最多能支持
Integer.MAX_VALUE
层。- ReentrantLock在尝试失败之后,将进入等待队列继续等待,就是阻塞
2. 不公平
// ReentrantLock#NonFairSync
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) { // #1
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
乍一看,好像没啥差。
只不过一来就尝试通过CAS修改条件来获取独占锁。其它,好像没啥了。对了,还改了个不要脸的名字叫nonfairTryAcquire
。
细心的你应该还能看到#1
语句里少了条件!hasQueuedPredecessors()
,即是不再管前面有没有人在排队,就伸手要去取锁。正因为这两个“不道德”的插队操作完成了非公开性,从而获到更高的OPS。
ReentrankLock默认实现就是非公平的,因为它能有更高的吞吐量。
三、再见AQS框架
在整理AQS框架
的时候,我们说AQS框架
提供一个基于FIFO等待队列,可以用于构建锁或者其他同步装置的基础框架。意在能够成为实现大部分同步需求的基础,她用法可以参考ReentrantLock的实现。
这里再多说几句,ReentrantLock是实现Lock接口、同时也实现Serializable。Serializable是说它的状态可以被系列,而Lock提供Lock的相关语义和操作。到这里为止,好像跟AQS没什么关系。对对对您说得对,没错没错是这样。不过我们说Lock是通过AQS实现同步的。因此,ReentrantLock实际上是通过一个承继AQS的内部类——同步器(Sync),实现同步,从而完全Lock功能的。
再看看ReentrantLock提供两种Sync的实现完全公平性语义,即FairSync
和NonFairSync
两个类。而Lock的功能完全Forwarding到Sync,由Sync具体实现。所以说AQS可以用来实现同步锁。
四、CU! ReentrantLock
- ReentrantLock是一个独占锁,可重入。
- ReentrantLock可实现公平和非公平,通过她的构造器的参数设定
- ReentrantLock是synchronized的升级版,可代替synchonized,更好用、更高性能