抛出问题:
1、ReentrantLock是如何实现线程等待的?
2、ReentrantLock是如何实现重入锁的?
3、ReentrantLock是如何实现公平锁和非公平锁?
J.U.C的ReentranLock底层是通过AQS同步器实现的,如图先看一下调用链
这里是非公平锁的调用链
ReentrantLock类有一个抽象静态内部类为Sync
/**
* 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 {
其中他有两个实现类NonfairSync,FairSync为非公平锁和公平锁
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
下面分析ReentrantLock的非公平锁
/**
* 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();
}
此方法调用了NonfairSync的lock()方法
/**
* 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);
}
NonfairSync的lock()方法先通过CAS判断AQS的state的属性是否为0,如果为0,
说明没有线程占用锁,将state设置为1将当前线程设置成为独占线程。
这是公平锁和非公平锁的区别,此时AQS的等待队列里可以有节点在等待,
对于那些等待的节点来说不公平的。
否则调用AQS的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();
}
此方法调用NonfairSync的tryAcquire的(arg)
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
然后代用Sync的nonfairTryAcquire(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;
}
首先获取AQS的state属性,如果为0,则尝试使用CAS更改值为1,如果成功就获得锁
如果不成功,判断当前线程是不是独占锁的线程,如果是,则重入,将state值加1
如何返回false,则执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg);
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
此方法是将当前线程封装成为AQS队列里的一个节点,首先是正常入队,如果队列尾不为空的话,将当前节点插入到队列尾,并且返回当前节点。否则执行enq(node);
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
循环操作,创建一个没有没有数据的Node节点为头节点,将当前节点插入到队列的尾部,循环操作指导插入成功为止
执行 /**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
判断当前节点的前驱节点是不是头结点,如果是则尝试后去锁,否则将节点线程挂起,使用LockSupport.park()方法挂起线程,这里具体实现细节就不说了,自己看源码。
1、ReentrantLock是如何实现线程等待的?
通过AQS同步器,里边有一个双向链表的队列实现的
2、ReentrantLock是如何实现重入锁的?
通过AQS的属性state,为0时没有线程获取锁,大于0时,有线程获取锁,等于几当前线程就重入多少次,重入多少次就得释放多少次
3、ReentrantLock是如何实现公平锁和非公平锁?*
通过源码中是否可以直接通过CAS将state的值从0变为1,对于队列里的线程是不公平的
总结:
1)首先基于CAS将state(锁数量)从0设置为1,如果设置成功,设置当前线程为独占锁的线程;–>请求成功–>第一次插队
2)如果设置失败(即当前的锁数量可能已经为1了,即在尝试的过程中,已经被其他线程先一步占有了锁),这个时候当前线程执行acquire(1)方法
* 2.1)acquire(1)方法首先调用下边的tryAcquire(1)方法,在该方法中,首先获取锁数量状态,
* 2.1.1)如果为0(证明该独占锁已被释放,当下没有线程在使用),这个时候我们继续使用CAS将state(锁数量)从0设置为1,如果设置成功,当前线程独占锁;–>请求成功–>第二次插队;当然,如果设置不成功,直接返回false
* 2.2.2)如果不为0,就去判断当前的线程是不是就是当下独占锁的线程,如果是,就将当前的锁数量状态值+1(这也就是可重入锁的名称的来源)–>请求成功
*
* 下边的流程一句话:请求失败后,将当前线程链入队尾并挂起,之后等待被唤醒。
*
* 2.2.3)如果最后在tryAcquire(1)方法中上述的执行都没成功,即请求没有成功,则返回false,继续执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
* 2.2)在上述方法中,首先会使用addWaiter(Node.EXCLUSIVE)将当前线程封装进Node节点node,然后将该节点加入等待队列(先快速入队,如果快速入队不成功,其使用正常入队方法无限循环一直到Node节点入队为止)
* 2.2.1)快速入队:如果同步等待队列存在尾节点,将使用CAS尝试将尾节点设置为node,并将之前的尾节点插入到node之前
* 2.2.2)正常入队:如果同步等待队列不存在尾节点或者上述CAS尝试不成功的话,就执行正常入队(该方法是一个无限循环的过程,即直到入队为止)–>第一次阻塞
* 2.2.2.1)如果尾节点为空(初始化同步等待队列),创建一个dummy节点,并将该节点通过CAS尝试设置到头节点上去,设置成功的话,将尾节点也指向该dummy节点(即头节点和尾节点都指向该dummy节点)
* 2.2.2.1)如果尾节点不为空,执行与快速入队相同的逻辑,即使用CAS尝试将尾节点设置为node,并将之前的尾节点插入到node之前
* 最后,如果顺利入队的话,就返回入队的节点node,如果不顺利的话,无限循环去执行2.2)下边的流程,直到入队为止
* 2.3)node节点入队之后,就去执行acquireQueued(final Node node, int arg)(这又是一个无限循环的过程,这里需要注意的是,无限循环等于阻塞,多个线程可以同时无限循环–每个线程都可以执行自己的循环,这样才能使在后边排队的节点不断前进)
* 2.3.1)获取node的前驱节点p,如果p是头节点,就继续使用tryAcquire(1)方法去尝试请求成功,–>第三次插队(当然,这次插队不一定不会使其获得执行权,请看下边一条),
* 2.3.1.1)如果第一次请求就成功,不用中断自己的线程,如果是之后的循环中将线程挂起之后又请求成功了,使用selfInterrupt()中断自己
* (注意p==head&&tryAcquire(1)成功是唯一跳出循环的方法,在这之前会一直阻塞在这里,直到其他线程在执行的过程中,不断的将p的前边的节点减少,直到p成为了head且node请求成功了–即node被唤醒了,才退出循环)
* 2.3.1.2)如果p不是头节点,或者tryAcquire(1)请求不成功,就去执行shouldParkAfterFailedAcquire(Node pred, Node node)来检测当前节点是不是可以安全的被挂起,
* 2.3.1.2.1)如果node的前驱节点pred的等待状态是SIGNAL(即可以唤醒下一个节点的线程),则node节点的线程可以安全挂起,执行2.3.1.3)
* 2.3.1.2.2)如果node的前驱节点pred的等待状态是CANCELLED,则pred的线程被取消了,我们会将pred之前的连续几个被取消的前驱节点从队列中剔除,返回false(即不能挂起),之后继续执行2.3)中上述的代码
* 2.3.1.2.3)如果node的前驱节点pred的等待状态是除了上述两种的其他状态,则使用CAS尝试将前驱节点的等待状态设为SIGNAL,并返回false(因为CAS可能会失败,这里不管失败与否,都返回false,下一次执行该方法的之后,pred的等待状态就是SIGNAL了),之后继续执行2.3)中上述的代码
* 2.3.1.3)如果可以安全挂起,就执行parkAndCheckInterrupt()挂起当前线程,之后,继续执行2.3)中之前的代码
* 最后,直到该节点的前驱节点p之前的所有节点都执行完毕为止,我们的p成为了头节点,并且tryAcquire(1)请求成功,跳出循环,去执行。
* (在p变为头节点之前的整个过程中,我们发现这个过程是不会被中断的)
* 2.3.2)当然在2.3.1)中产生了异常,我们就会执行cancelAcquire(Node node)取消node的获取锁的意图。