title: JUC.ReentrantLock
ReentrantLock提纲
ReentrantLock简介
ReentrantLock是可重入的独占锁,同时只允许一个线程获取该锁,其它被阻塞的锁会放入AQS队列里面。首先看下ReentrantLock的类图
从类图可以看出来ReentrantLock使用AQS实现,并且内部实现了公平锁与非公平锁,默认是非公平锁。
其中Sync继承AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平和公平策略。
公平锁是指按照队列排队唤醒线程,非公平锁是指当释放锁有队列外的线程参与抢占可以不用参与队列。
所以一般来说非公平锁性能高于公平锁,因为不用唤醒线程。公平锁除非有特许业务场景需要控制线程调用情况有可能会用到。(反正我至今没遇到过)
接着往下说ReentrantLock,由于基于AQS实现所以也会用到state状态值表示锁情况。这里的state表示重入次数,默认情况下是0标识当前锁没有被任何线程持有。
当线程尝试获取锁的时候会使用CAS修改设置state的值为1如果成功则获取锁,然后记录锁的持有者是该线程,其余则放入AQS队列当中。
在该持锁线程第二次获取锁的时候state修改为2,这就是重入次数,每释放一次锁就减1当为0的时候就彻底释放了锁。
获取与释放锁
在说到获取与释放锁的时候我会结合源代码来说,比较简洁明了。
首先看下面一段模仿ArrayBlockingQueue的代码
/**
* @ClassName ConsumerAndProducer
* @Description ToDo
* @Author Allen
* @Date 2018/12/4 21:00
* @Version
*/
public class ConsumerAndProducer {
private final static ReentrantLock reentrantLock = new ReentrantLock();
private final static Condition productCondition = reentrantLock.newCondition();
private final static Condition consumeCondition = reentrantLock.newCondition();
private final static Queue <String> queue = new LinkedBlockingQueue <String>();
private final static int QUEUE_SIZE = 10;
public static void main(String[] args) {
Thread producer = new Thread(() -> {
reentrantLock.lock();
try {
//队列满了等待
while (queue.size() == QUEUE_SIZE) {
productCondition.await();
}
//添加元素
queue.add("lock");
//唤醒消费
consumeCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
reentrantLock.unlock();
}
});
Thread consumer = new Thread(() -> {
reentrantLock.lock();
try {
while (0 == queue.size()) {
consumeCondition.await();
}
System.out.println(queue.poll());
productCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
});
producer.start();
consumer.start();
}
}
- 获取锁
看上面的代码首先会调用lock方法获取锁,咱们看一眼源码发现委托给了Sync的lock方法,会根据ReentrantLock构造函数选择Sync的实现是NonfairSync还是FairSync
分别看下非公平和公平的实现,首先看非公平的方式如下代码
final void lock() {
//上来如果锁没被占用则直接占用简直不讲道理,如果锁被占用则走流程该进队列进队列
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
然后咱们再看一眼非公平锁的tryAcquire实现,这个代码也是挺暴躁的。真的丧心病狂的要抢占锁。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//state为0直接占锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
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;
}
//都不是走流程进AQS队列
return false;
}
非公平锁总则来说就是怎么拿锁块怎么来。下面说一下公平锁,先看代码
final void lock() {
//很文明上来走流程
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//锁没被占用先走一个队列判断hasQueuedPredecessors
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;
}
//下面的h!=t队列不为空,然后如果且next不为空更厉害的是还等于当前线程则拿锁
//h!=t且next不为空然后不等于当前线程,则进队列取next的线程出来。相当的公平呀。。。。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
- 获取锁
释放锁如下面的代码其实没啥好看的就是state减少1,并且如果sate为0了释放锁的占有,当前锁的线程为null.
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
Condition讲解
其实在写AQS的时候有提到过这个东西,但是没有举例子详细说明。还是回到本文的第一段代码ConsumerAndProducer
这段代码是模范的ArrayBlockingQueue的代码,其中会有两个condition。每一个condition都是一个队列可以存放调用线程并且唤醒其中的线程。
这里的好处是啥了?比如consumeCondition里面有线程A然后productCondition里面有线程B和D。我唤醒的时候可以只唤醒A,而不会唤醒所有的线程。
而不会存在所有的线程都唤醒的尴尬局面,比如读和写咱们的线程可以灵活分开来管理是不是很cool。
欢迎扫码加入知识星球继续讨论