时间记录:2019-7-6
在之前的aqs的总结中有看到ReentrantLock的身影,故去看了下ReentrantLock的源码,我在这里进行一下的总结自己整理的内容。
以下【】的定义内容,均是从jdk1.8版本的源码翻译过来的,翻译内容不一定准确,结合自己的理解总结下。
1:ReentrantLock的定义
A reentrant mutual exclusion {@link Lock} with the same basic behavior and semantics as the implicit monitor lock accessed using {@code synchronized} methods and statements, but with extended capabilities.
【可重入互斥锁具有与使用synchronized方法和语句访问的隐式监视器锁相同的基本行为和语义,但具有扩展功能】,这里基本上说明这个锁的是可重入与互斥的锁。
重入: 重入就是多次获取这个锁,但是是在一个线程中
互斥: 互斥就是只能一个线程获取这个锁,必须经过lock和unlock的过程,如果其余线程想要获取到这个锁,会在等待这个锁
A {@code ReentrantLock} is owned by the thread last successfully locking, but not yet unlocking it. A thread invoking {@code lock} will return, successfully acquiring the lock, when the lock is not owned by another thread. The method will return immediately if the current thread already owns the lock. This can be checked using methods {@link #isHeldByCurrentThread}, and {@link#getHoldCount}.
【锁由最后成功锁定的线程拥有,但尚未解锁。当锁不是由另一个线程拥有时,调用lock的线程成功获取锁将返回。如果当前线程已拥有锁,则该方法将立即返回。可以使用isHeldByCurrentThread和getHoldCount方法检查】,这里是指当一个线程获取到了这个锁的时候,另一个线程没有获取到这个锁,那么在调用lock的时候,已经获取到这个锁的线程会立即返回,而没有获取到这个所得线程会在获取到锁的时候返回。
The constructor for this class accepts an optional fairness parameter. When set {@code true}, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation. Note however, that fairness of locks does not guarantee fairness of thread scheduling. Thus, one of many threads using a fair lock may obtain it multiple times in succession while other active threads are not progressing and not currently holding the lock.Also note that the untimed {@link #tryLock()} method does not honor the fairness setting. It will succeed if the lock is available even if other threads are waiting.
【此类的构造函数接受可选的fairness(boolean型)参数,当参数设置为true时,在锁的争用情况下,锁有利于对等待时间最长的线程的访问权限授予。否则,此锁将不保证任何特定的访问顺序。使用由许多线程访问的公平锁的程序可以显示比使用默认设置的程序更低的总吞吐量(即更慢;通常慢得多),但是获得锁的时间变化较小并且保证不饥饿(这里应该指的是线程饥饿状态)。但请注意,锁的公平性并不能保证线程调度的公平性。因此,使用Fair锁的众多线程之一可以连续多次获得,而其他活动线程没有进展,当前没有持有同时注意,不定时的tryLock方法没有尊重公平背景。如果锁即使可用,即使其他线程正在等待,它也会成功。】,这里主要说的是锁的公平的和不公平的,主要只在获取锁时的争夺问题,公平的即等待的线程获取锁时时按照顺序来执行的而不公平的话等待的线程获取锁时不确定,不知道是等待的线程中的哪一个。
It’s a good idea to always follow the {@code lock} call immediately with the {@code try} block, most often before/after construction, for example:【最好始终使用 try块跟随lock调用,最常见的是在构造之前/之后,例如:】
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
整体上来说可重入互斥锁,可以简单的理解为在不同线程时互斥的,而在同一线程时重入的
2:查看构造函数
构造函数从上面的介绍中可以看出来分为公平性和非公平性两种
/**
* 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();
}
从构造函数上来看我们可以发现,包含了FairSync和NonfairSync,我们来看看下这个公平和非公平的不同实现,以及如何实现可重入,至于互斥不仔细描述
3:FairSync 和 NonfairSync
从FairSync和NonfairSync来看我们发现同一继承了Sync ,而Sync 继承了aqs,ReentrantLock的可重入互斥主要就是依据这几个内容实现
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
FairSync : 从方法来看主要是包含了tryAcquire和lock方法,那么这两个方法在ReentrantLock的lock和unlock中充当着什么角色呢。
我们可以发现它是从ReentrantLock.lock()->FairSync.lock()->FairSync.tryAcquire()的一个顺序,那么我们在FairSync.lock()可以看到一个acquire(1)[aqs中的方法]的一个方法那么这个方法是怎么到FairSync.tryAcquire()的呢 以及他具体干了什么事呢
/**
* 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();
}
我们发现其执行了tryAcquire(1),那么也就是FairSync.tryAcquire(1),我们来看这里的流程怎么走的,首先会判断一个state(其实就是一个记数,可以参考之前的AQS中的总结)的值,我们会发现state的值为0后会执行hasQueuedPredecessors的操作,主要是为了查询是否存在等待时间比当前线程等待时间更长的线程,而通过hasQueuedPredecessors方法可以看出是通过队列中的线程顺序进行判断而不是真的多长时间。之后会执行setExclusiveOwnerThread也就是将当前线程设置为这个锁的独占线程。如果c不等于0呢?那么下面就是体现可重入的地方了,我们发现会将当前线程和独占线程exclusiveOwnerThread进行对比,如果是在同一个线程那么会将state的值加一,从这个方法我们就可以看出了为什么可以重入,实际上所谓的重入还是在原有的state上面加1,保证其可以执行被锁的部分【可以同时对比countdownlatch的state的-1操作】。从AQS.acquire可以看出来,如果当前线程获取到了这个锁,那么就会结束操作,如果没有获取到锁会将当前线程以Node的方式保存在队列中,然后将当前线程Interrupt(中断),那么这个被中断的线程如何重新启动,那么对应的就是释放,对应的就是unlock,对应的是aqs.unparkSuccessor(),其按照队列的顺序进行清除线程中断状态。
我们看到了公平是按照队列的顺序进行的,那么非公平的是怎么给等待的线程安排锁的呢
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 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);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
我们看到tryAcquire(1)中调用的方法是nonfairTryAcquire(1),我们对比发现其没有查找对应的等待队列的线程的顺序,那么这就决定如果能获取到所谁到谁就可以获取
注意:这里在获取锁的开始时会调用compareAndSetState(0, 1),这里0表示预期值,1表示跟新的值,也就是预期state的值为0的时候就将当前线程给定为独占锁,也就是立即抢夺【Performs lock. Try immediate barge, backing up to normal acquire on failure.这里是这样说的,个人觉得有点牵强,而compareAndSetState实际保证了state的操作的原子性】,不为0表示已经被占了,那就要保存在队列中。
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* 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;
}
注意:可以结合之前的AQS一起看
时间记录:2019-7-17