扫描下方二维码或者微信搜索公众号
菜鸟飞呀
飞,便可关注微信公众号,阅读更多Spring源码分析
和Java并发编程
文章。java
在阅读本文以前能够先思考一下如下两个问题node
1.
ReentrantLock是如何在Java层面(非JVM层面)实现锁的?2.
什么是公平锁?什么是非公平锁?Lock
是JUC包下的一个接口,里面定义了获取锁、释放锁等和锁相关的方法,ReentrantLock
是Lock接口的一个具体实现类,它的功能是可重入地独占式地获取锁。这里有两个概念,可重入
和独占式
。可重入表示的是同一个线程能屡次获取到锁。独占式表示的是,同一时刻只能有一个线程获取到锁。ReentrantLock
实现的锁又能够分为两类,分别是公平锁
和非公平锁
,分别由ReentrantLock类中的两个内部类FairSync
和NonfairSync
来实现。FiarSync和NonfairSync均继承了Sync类,而Sync类又继承了AbstractQueuedSynchronizer(AQS)类,因此ReentrantLock最终是依靠AQS来实现锁的。关于AQS的知识能够参考如下两篇文章。/** * 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()
和unlock()
方法实现是由FairSync类中的lock()
和release()
方法实现的(其中release()方法定义在AQS中)。public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock(true);
try{
lock.lock();
System.out.println("Hello World");
}finally {
lock.unlock();
}
}
复制代码
static final class FairSync extends Sync {
final void lock() {
// 调用acquire()获取同步状态
acquire(1);
}
}
复制代码
在AQS的acquire()方法中会先调用子类的tryAcquire()方法,此时因为咱们建立的是公平锁,因此会调用FairSync类中的tryAcquire()方法。(关于acquire()方法的详细介绍,能够参考:队列同步器(AQS)源码分析)。编程
tryAcquire()方法的做用就是获取同步状态(也就是获取锁),若是当前线程成功获取到锁,那么就会将AQS中的同步状态state加1,而后返回true,若是没有获取到锁,将会返回false。FairSync类上tryAcquire()方法的源码以下。设计模式
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取同步状态state的值(在AQS中,state就至关于锁,若是线程能成功修改state的值,那么就表示该线程获取到了锁)
int c = getState();
if (c == 0) {
// 若是c等于0,表示尚未任何线程获取到锁
/** * 此时可能存在多个线程同时执行到这儿,均知足c==0这个条件。 * 在if条件中,会先调用hasQueuedPredecessors()方法来判断队列中是否已经有线程在排队,该方法返回true表示有线程在排队,返回false表示没有线程在排队 * 第1种状况:hasQueuedPredecessors()返回true,表示有线程排队, * 此时 !hasQueuedPredecessors() == false,因为&& 运算符的短路做用,if的条件判断为false,那么就不会进入到if语句中,tryAcquire()方法就会返回false * * 第2种状况:hasQueuedPredecessors()返回false,表示没有线程排队 * 此时 !hasQueuedPredecessors() == true, 那么就会进行&&后面的判断,就会调用compareAndSetState()方法去进行修改state字段的值 * compareAndSetState()方法是一个CAS方法,它会对state字段进行修改,它的返回值结果又须要分两种状况 * 第 i 种状况:对state字段进行CAS修改为功,就会返回true,此时if的条件判断就为true了,就会进入到if语句中,同时也表示当前线程获取到了锁。那么最终tryAcquire()方法会返回true * 第 ii 种状况:若是对state字段进行CAS修改失败,说明在这一瞬间,已经有其余线程获取到了锁,那么if的条件判断就为false了,就不会进入到if语句块中,最终tryAcquire()方法会返回false。 */
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 当前线程成功修改了state字段的值,那么就表示当前线程获取到了锁,那么就将AQS中锁的拥有者设置为当前线程,而后返回true。
setExclusiveOwnerThread(current);
return true;
}
}
// 若是c等于0,则表示已经有线程获取到了锁,那么这个时候,就须要判断获取到锁的线程是否是当前线程
else if (current == getExclusiveOwnerThread()) {
// 若是是当前线程,那么就将state的值加1,这就是锁的重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 由于此时确定只有一个线程获取到了锁,只有获取到锁的线程才会执行到这行代码,因此能够直接调用setState(nextc)方法来修改state的值,这儿不会存在线程安全的问题。
setState(nextc);
// 而后返回true,表示当前线程获取到了锁
return true;
}
// 若是state不等于0,且当前线程也等于已经获取到锁的线程,那么就返回false,表示当前线程没有获取到锁
return false;
}
}
复制代码
1.
若是等于0,就表示目前尚未线程持有到锁,那么这个时候就会先调用hasQueuedPredecessors()
方法判断同步队列中有没有等待获取锁的线程,若是有线程在排队,那么当前线程确定获取锁失败(由于AQS的设计的原则是FIFO,既然前面有人已经在排队了,你就不能插队,老老实实去后面排队去),那么tryAcquire()方法会返回false。若是同步队列中没有线程排队,那么就让当前线程对state进行CAS操做,若是设置成功,就表示当前获取到锁了,返回true;若是CAS失败,表示在这一瞬间,锁被其余线程抢走了,那么当前线程就获取锁失败,就返回false。2.
若是不等于0,就表示已经有线程获取到锁了,那么此时就会去判断当前线程是否是等于已经持有锁的线程(getExclusiveOwnerThread()方法的做用就是返回已经持有锁的线程
),若是相等,就让state加1,这就是所谓的重入锁,重入锁就是容许同一个线程屡次获取到锁。3.
state既不等于0,当前线程也不等于持有锁的线程,那么就返回false,表示当前线程获取到锁失败。public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
/** * 该方法的做用是判断队列中是否有线程在排队,返回true表示有线程排队,返回false表示没有线程排队 * * 首先判断 h!=t,即判断头结点和尾结点是否相等,头结点和尾结点相等只有两种状况, * 第一:队列尚未被初始化,此时head = null, tail = null,此时线程不须要排队。 * 第二:已经有一个线程获取到了锁,当第二个线程进来获取锁时,由于获取不到锁,因此会被须要入队,入队以前它须要先初始化队列, * 在初始化时,在enq()方法中,第一步是先将tail = head,而后第二步再将当前线程表明的Node设置为tail。因此在这两步操做 * 的中间的一瞬间,是存在tail = head这个可能性的。此时对全部线程而言是也不须要排队 * 当 h!=t 的结果为true时,接下来会判断((s = h.next) == null || s.thread != Thread.currentThread()),先判断(s = h.next) == null * * 判断(s = h.next) == null 时,先令 s = h.next ,表示获取到头结点的的下一个节点,即第二个节点,若是 s == null 则表示第二个节点为null,因为前面已经判断了h!=t, * 说明此时已经有第二个线程进入到了队列中,只不过它还没来及将head节点的next指针的指向修改,因此此时线程线程须要排队, * 由于||运算的短路缘由,当(s = h.next) == null 的结果为true时,就不会进入到后面的判断了,而此时hasQueuedPredecessors()会返回true,表示线程须要排队。 * 当s不为null时,会进行s.thread != Thread.currentThread() 的判断 * 它会判断s节点中的线程是否不等于当前线程,若是不等于当前线程,hasQueuedPredecessors()会返回true,说明当前线程须要排队。 * 由于当第二个节点不是当前线程,那么就说明当前线程应该至少是排在队列中的第三位,那么它须要排队。 * 若是s节点中的线程等于当前线程,那么说明当前线程是排在队列中的第二位(第一位是已经获取到锁的线程),此时线程是不须要排队的, * 由于可能在这一瞬间已经获取到锁的线程释放了锁,那么排在队列中第二位的线程还排啥子队哦,直接去尝试获取锁便可。 * * * */
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
复制代码
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 当释放锁成功之后,须要唤醒同步队列中的其余线程
Node h = head;
// 当waitStatus!=0时,表示同步队列中还有其余线程在等待获取锁
if (h != null && h.waitStatus != 0)
// 唤醒同步队列中下一个能尝试获取锁的线程
unparkSuccessor(h);
return true;
}
return false;
}
复制代码
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 判断当前线程是否是持有锁的线程,若是不是就抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 由于上面一步已经确认了是当前线程持有锁,因此在修改state时,确定是线程安全的
boolean free = false;
// 由于可能锁被重入了,重入了几回就须要释放几回锁,因此这个地方须要判断,只有当state=0时才表示彻底释放了锁。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
复制代码
LockSupport.unpark()
。private void unparkSuccessor(Node node) {
/* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
/** * 同步队列中的节点锁表明的线程,可能被取消了,此时这个节点的waitStatus=1 * 所以这个时候利用for循环从同步队列尾部开始向前遍历,判断节点是否是被取消了 * 正常状况下,当头结点释放锁之后,应该唤醒同步队列中的第二个节点,可是若是第二个节点的线程被取消了,此时就不能唤醒它了, * 就应该判断第三个节点,若是第三个节点也被取消了,再依次日后判断,直到第一次出现没有被取消的节点。若是都被取消了,此时s==null,因此不会唤醒任何线程 */
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
复制代码
static final class NonfairSync extends Sync {
final void lock() {
// 先尝试获取锁,若是获取锁失败,就再去调用AQS的acquire()方法
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
复制代码
protected final boolean tryAcquire(int acquires) {
// 调用nonfairTryAcquire()方法
return nonfairTryAcquire(acquires);
}
复制代码
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 与公平锁的不一样之处在于,公平在在进行CAS操做以前,会先判断同步队列中是否有人排队。
// !hasQueuedPredecessors()
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;
}
复制代码
c == 0
时,非公平锁没有去判断同步队列中是否有人在排队
,而是直接
调用CAS方法去设置state的值。而公平锁则是先判断同步队列中是否有人排队
,若是没有人排队,才调用CAS方法去设置state的值。即非公平锁没有调用hasQueuedPredecessors()
方法。其余的逻辑,公平锁与非公平锁的逻辑就同样了。ReentrantLock
来建立公平锁和非公平锁,经过默认的无参构造方法建立的是非公平锁;当使用有参构造器建立时,若是参数传入的是false,则建立的是非公平锁,若是传入的是true,则建立的是公平锁。FairSync
,实现非公平锁的同步组件的类是NonfairSync
,它们都是队列同步器AQS的子类。tryAcquire()
方法,分析了公平锁和非公平锁的获取锁的流程。同时对比二者的实现代码,发现非公平锁在获取锁时不会判断同步队列中有没有线程在排队,而是直接尝试去修改state的值,而公平锁在获取锁时,会先判断同步队列中有没有线程在排队,若是没有才会尝试去修改state的值。1.
ReentrantLock是如何在Java层面(非JVM实现)实现锁的?组合一个同步组件Sync来实现锁
,这个同步组件继承了AQS,并重写了AQS中的tryAcquire()、tryRelease()等方法,最终实际上仍是经过AQS以及重写的tryAcquire()、tryRelease()等方法来实现锁的逻辑。ReentrantLock实现锁的方式,也是JUC包下其余类型的锁的实现方法,经过组合一个自定义的同步组件,这个同步组件须要继承AQS,而后重写AQS中的部分方法便可实现一把自定义的锁,一般这个同步组件被定义成内部类。2.
什么是公平锁?什么是非公平锁?FairSync
同步组件实现的锁是公平锁,它获取锁的原则是,在同步队列中等待时间最长的线程获取锁,所以称它为公平锁。由NonfairSync
同步组件实现的锁是非公平锁,它获取锁的原则是,同步队列外的线程在尝试获取锁时,不会判断队列中有没有线程在排队,而是上来就抢,抢到锁了就走,抢不到了才去排队,所以称它为不公平的。