简介
ReentrantLock即可重入锁(当前线程获取该锁再次获取不会被阻塞),是一种递归无阻塞的同步机制。ReentrantLock基于AQS来实现,相对于内置锁synchronized关键字功能更强大,多了等待可中断、公平性、绑定多个条件等机制,还可以tryLock()避免死锁,而若单独从性能角度出发,更推荐synchronized
ReentrantLock
锁获取锁流程:
lock方法:
public void lock() {
sync.lock();
}
Sync为ReentrantLock里面的一个内部类,它继承AQS,它有两个子类:公平锁FairSync和非公平锁NonfairSync,ReentrantLock里面大部分的功能都是委托给Sync来实现的,以非公平锁为例其lock()方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
若锁未线程占有,把同步器中的exclusiveOwnerThread设置为当前线程
若锁已有线程占有,nonfairTryAcquire方法中,会再次尝试获取锁,在这段时间如果该锁被成功释放,就可以直接获取锁而不用挂起,其完整流程:
公平锁与非公平锁
公平锁与非公平锁的区别在于获取锁的时候是否按照FIFO的顺序。
ReentrantLock默认采用非公平锁(组合方式)
public ReentrantLock() {
sync = new NonfairSync();
}
实现非公平锁的核心方法nonfairTryAcquire(),其源码如下:
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取同步状态
int c = getState();
// 若同步状态为0,表明该锁未被任何线程占有
if (c == 0) {
// CAS设置同步状态
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;
}
return false;
}
其主要逻辑:判断同步状态是否为0,若为0表明该锁未被任何线程占有,CAS设置同步状态;若不为0表明该锁已被线程占有,判断锁占有线程是否是当前线程,若是增加同步状态(可重入性机制实现的关键)
公平锁,通过ReentrantLock有参构造方法传入true
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
实现公平锁的核心方法tryAcquire(),其源码如下:
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;
}
可以很明显地发现与nonfairTryAcquire()方法唯一的区别在于CAS设置尝试设置state值之前,调用了hasQueuedPredecessors()判断当前线程是否位于CLH同步队列中的第一个,若不是先执行完同步队列中结点的线程,当前线程进入等待状态
public final boolean hasQueuedPredecessors() {
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());
}
可重入性
可重入性需要解决以下两个问题:
①.线程再次获取锁:锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是则再次成功获取
次成功获取
②.锁的最终释放:线程重复n次获取了锁,只有在n次释放该锁后,其他线程才能获取到该锁
在nonfairTryAcquire()、tryAcquire()方法中都有这段代码:
if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
为了支持可重入性,若同步状态不为0时,还会再判断锁持有线程是否是当前请求线程,若是再次获取该锁,同步状态加1。再来看看释放锁:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 同步状态为0时,锁才能释放,将其持有线程置为null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
绑定多个条件
每一个Lock可以有任意数据的Condition对象,Condition是与Lock绑定的。Condition接口定义的方法,await对应于Object.wait,signal对应于Object.notify,signalAll对应于Object.notifyAll。
生产者消费者简单demo:
public class Resource {
private int num = 1;//当前数量
private int maxNum = 10;//极值
private Lock lock = new ReentrantLock();
private Condition productCon = lock.newCondition();
private Condition consumerCon = lock.newCondition();
public void product() {
lock.lock();
try {
while (num >= maxNum) {
try {
System.out.println("当前已满");
productCon.await();
} catch (InterruptedException e) {
}
}
num++;
System.out.println("生产者" + Thread.currentThread().getName() + "当前有" + num + "个");
consumerCon.signal();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
while (num == 0) {
try {
System.out.println("当前已空");
consumerCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println("消费者" + Thread.currentThread().getName() + "当前有" + num + "个");
productCon.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final Resource r = new Resource();
// 生产者
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
r.product();
}
}
}).start();
// 消费者
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
r.consume();
}
}
}).start();
}
}
感谢
《java并发编程的艺术》
https://www.jianshu.com/p/4358b1466ec9