锁的可重入性是为了防止已经获得锁的线程再次获得锁可能会带来的死锁问题,比如:
public synchronized void A(){
B();
}
public synchronized void B(){
}
如果Java内置的synchronized
锁不是可重入的,则线程A在获得锁进入A()
后调用B()
时,需要等待线程A释放锁,而此时线程A还没有完成A()
,则不会释放锁,因此产生死锁。
一个简单的可重入锁实现
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 简单的可重入锁实现:
* 使用一个计数器记录当前线程重入锁的次数,获得锁时计数器加1,
* 释放锁时计数器减1,当计数器等于0时表示释放了锁
**/
public class SimpleReentrantLock implements Lock{
// 指向已经获得锁的线程
private volatile Thread exclusiveOwnerThread;
// 记录获取了同一个锁的次数
private volatile int holdCount;
private final java.util.concurrent.locks.Lock lock;
// 是否是自己获得锁的条件
private final Condition isCountZero;
public SimpleReentrantLock(){
lock = new ReentrantLock();
isCountZero = lock.newCondition();
holdCount = 0;
}
@Override
public void lock() {
lock.lock();
try{
// 当前线程的引用
Thread currentThread = Thread.currentThread();
// 如果获得锁的线程是自己,那么计数器加1,直接返回
if(exclusiveOwnerThread == currentThread){
holdCount ++;
return;
}
while(holdCount != 0){
try {
isCountZero.await();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
// 将exclusiveOwnerThread设置为自己
exclusiveOwnerThread = currentThread;
holdCount ++;
}finally{
lock.unlock();
}
}
@Override
public void unlock() {
lock.lock();
try{
holdCount --;
if(holdCount == 0){
isCountZero.signalAll();
}
}finally{
lock.unlock();
}
}
}
- 使用一个Thread引用指向获得锁的线程
- 使用一个计数器记录一个线程进入锁的次数,当计数器为0时表示锁是空闲的
- 使用一个内部锁Lock来同步线程
- 使用一个isHoldZero的条件来进行条件队列操作
- 当获得锁的线程是自己时,只修改计数器的值,直接获得锁
- 当获得锁的线程不是自己时,需要在holdCount !=0 这个条件谓词上等待,直到计数器归0,再次竞争锁
- 释放锁时计数器减1,当计数器为0时,唤醒在条件队列中等待的线程
问题1:为什么锁要具备可重入性?