可重入锁 - ReentrantLock
概念
可重入锁,是指当前线程可以对某个资源重复加锁也不会导致死锁发生的锁。
注意:在使用可重入锁的时候,需要注意,线程对资源a加了多少次锁,就要释放多少次,如果加了5层锁而释放了4层,则资源不会被释放。
JAVA中的ReentrantLock
家族树
可重入锁的家族树非常简单,实现了Lock接口以及可序列化,可以说可重入锁的几个主要方法,都是由Lock要求的,下面我们就细看一下ReentrantLock的几个主要方法。
ReentrantLock的主要方法
- 构造器:ReentrantLock() & ReentrantLock(boolean fair)
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁的构造器主要干了一件事,初始化这把锁持有的同步类,默认是持有一把非公平锁。
公平锁和非公平锁的概念,不在这篇里详细地阐述了,但可以用一句话解释一下,即,等待队列中所有线程获取锁的机会均等,那么这把锁就是公平锁,否则,如果是有一定顺序的让线程获取资源,那这个资源上的锁就是非公平锁。
- lock()
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
跟入到ReentrantLock的锁方法,可以看到,ReentrantLock使用的确是一个类似于门面的方式,底层调用的都是内部实现的同步类。
在注释中,我们可以看到,可重入锁声明了锁资源的三种情况:
- 如果资源未被另一个线程持有,则马上返回,并让锁统计变为1。
- 如果线程已经持有了当前资源,则锁统计+1,并且马上返回。
- 如果线程已经被另一个线程持有了,那么当前线程就会进入休眠状态,直到锁被获取。
- lockInterruptibly()
获取锁,逻辑是和lock()一样的,但是能响应中断。
- trylock() & tryLock(long timeout, TimeUnit unit)
尝试获取锁,获取逻辑和lock()差不多,但是并不会等待锁
- unlock()
解锁,使加在资源上的锁统计-1,直到锁统计为0,资源才被真正释放。
- newCondition()
返回一个锁的实例,可以用于多线程之间的通信,功能比synchronize还要强大。
ReentrantLock中锁方法的细节实现
非公平锁
此处的非公平锁是ReentrantLock的内部抽象类Sync的内部实现类NonfairSync,Sync类继承了AbstractQueuedSynchronizer类
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
首先,非公平锁会先尝试使用unsafe类的CAS方法获取锁,失败则执行acquire(1这个方法)。值得注意的是,这里要求原值为0,也就是资源无锁,这个方法才有可能执行成功,但凡有一把锁,不管是不是本线程持有的,都会走进acquire(1)这个方法。
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这个方法是AbstractQueuedSynchronizer类中实现的方法,看似只有一个逻辑分支,其实有三步逻辑:
- tryAcquire(arg),获取锁,这里arg=1。
- 如果这步没有成功,才会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),将线程放入请求等待队列。
- 如果没获取到锁,还成功加入了请求队列,那就等着呗,于是有了第三步,selfInterrupt()-自停。
而tryAcquire(arg)底层调用的是Sync#nonfairTryAcquire(int acquires)
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
return false;
}
在这段代码里面,我们还看到了nextc这个变量会做一个正值判断,没错,对一个线程来说,加在一个资源上的锁最多只能有32767把,思考的还挺周全的(笑)