问题是最好的老师!
问题一:关于Node节点的属性(waitStatus和nextWaiter)?
第一篇中我们了解到AQS中队列的Node节点的数据结构,其中Node有几个属性waitStatus和nextWaiter,我们只是从注释翻译了解到其基本概念,那么AQS中不同情况下这两个属性的取值是怎么样的,这一点需要我们取思考。
问题二:AQS内部是如何使用队列的?
我们通过前面了解到AQS队列中存储的是线程,那么什么情况下将线程入队,怎样入队,入队之后怎样唤醒。
问题三:AQS中阻塞和唤醒线程采用的是什么方式?
我们知道常用的阻塞唤醒有Object.wait/notify,但是AQS内部内部并没有一个共享对象Object,其只是维护了一个state,那么它是怎样阻塞和唤醒的。而且一般阻塞唤醒我们都采用线程间通信的方式,而线程间通信一般采用Condition.await/signal和Object.wait/notify,那么AQS内部是如何进行线程间通信的。
问题四:共享模式和独占模式究竟有什么区别,是如何实现的?
独占模式比较好理解,当某个线程独占锁之后,其他线程阻塞即可。但是共享模式我们可能比较模糊,即一个线程获取锁之后,其他线程依然可以获取,那么这里就有疑问,既然其他线程都可以获取,那阻塞在哪里?阻塞谁?什么情况下阻塞?
问题五:ReentrantLock公平锁和非公平锁有什么区别?
我们将从实现原理去剖析一下这种差异性。
要了解ReentrantLock,必然要了解如何使用它,而ReentrantLock的一般使用方式为:
//该锁默认为非公平锁
ReentrantLock lock=new ReentrantLock();
public void synchronizeMethod(){
lock.lock();
try{
doSomeThing();
}finally{
lock.unlock();
}
}
在上面这段代码中,关于ReentrantLock有三个定义:
1.ReentrantLock的定义:我们可以定义为非公平锁(默认)或者公平锁(调用ReentrantLock(boolean fair)方法)。
2.线程获取锁:lock.lock()该方法不响应中断,若响应中断还可调用lock.lockInterruptibly()方法。
3.线程释放锁:lock.unlock()。
那么接下来我们将从这三个定义去分析ReentrantLock的源码和AQS的源码,因为ReentrantLock为独占锁,在第二篇中我们有提到,如果只需要独占模式,那么实现AQS的子类是不需要定义共享模式锁所涉及方法的,所以该篇先只分析独占模式的实现。
//默认为非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
还记得我们第二节所讲的AQS使用方式吗,我们先通过ReentrantLock去论证前两点:
1.实现AQS的子类需要定义为内部类
2.需要实现AQS定义的受保护方法
//注意java中并不存在多个继承,这里只是为了看的更直观
//AQS使用方式一:子类需要定义为内部类
static final class NonfairSync extends Sync extends AbstractQueueSynchronizer{
//这里只列举部分方法,说明AQS使用方式
//以下这些方法的解释请看AQS第二篇,具体实现请看下文分析
//这里主要通过构造函数初始化之后,AQS的state初始值为0,而ReentrantLock中state为0,则代表
//锁未被占有
NonfairSync() {
}
Sync() {
}
//AQS实现方式二:子类实现受保护方法tryAccquire
protected final boolean tryAcquire(int acquires) {
}
//AQS实现方式二:子类实现受保护方法tryRelease
protected final boolean tryRelease(int releases) {
}
//AQS实现方式二:子类实现受保护方法isHeldExclusively
protected final boolean isHeldExclusively() {
}
}
该部分比较重要,也是AQS的实现原理,会分析的比较细,故内容比较多,请大家耐心看完,分析不对的地方请大家多指点。
Method:lock()
//线程独占锁
final void lock() {
//类似快速失败方式
//尝试设置sate为1(sate为0上文解释过),获取锁
//注意这里可能有多个线程并发调用,那么我们知道这里必须要保证state值的可见性,看AQS中
//state定义:private volatile int state(volatile保证可见性);
//具体compareAndSetState实现请看下文
if (compareAndSetState(0, 1))
//如果通过快速尝试的方式获取锁成功,则设置当前占有锁的线程为当前线程
//具体setExclusiveOwnerThread请看下文
setExclusiveOwnerThread(Thread.currentThread());
else
//通过快速尝试的方式获取锁失败,则要么再次尝试(可能还会成功),要么入队
//在第二篇中也将到调用accquire方法的传参一般都是1,代表将要获取锁,因为为0则代表
//锁没有被占用
//具体accquire方法实现请看下文
acquire(1);
}
Method:compareAndSetState(int state)
//expect:期望原值 update:新值
//语义:如果原值为expect,则更新为update
//返回值:true代表更新成功,失败则代表其他线程可能已经更新了原值
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Method:setExclusiveOwnerThread(Thread thread)
protected final void setExclusiveOwnerThread(Thread thread) {
//独占线程,null代表无线程占用锁
exclusiveOwnerThread = thread;
}
Method:acquire(int arg)
//在第二篇tryAccquire方法介绍中有提到,线程需要通过调用accquire方法从而调用tryAccquire方法,这里
//得到论证
public final void acquire(int arg) {
//如果尝试获取锁失败,则将该线程入队并阻塞(这是重点)
//如果阻塞被唤醒之后acquireQueued方法返回true则中断自己,这里我们先不着急,一步一步看
//这里具体各个方法的解释请看下文
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Method:tryAcquire(int acquires)
//该方法由子类自己实现
protected final boolean tryAcquire(int acquires) {
//请看下文nonfairTryAcquire方法
return nonfairTryAcquire(acquires);
}
Method:nonfairTryAcquire(int acquires)
//通过非公平方式尝试获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//上文中有讲到快速尝试,这里再进行一次尝试获取锁
if (compareAndSetState(0, acquires)) {
//获取成功,设置独占线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//因为ReentrantLock为可重入锁,支持同一线程多次获取锁
//判断如果是同一线程再次获取锁
else if (current == getExclusiveOwnerThread()) {
//对于同一线程获取锁state依次递增
int nextc = c + acquires;
//int最大值加一会变成负数
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//这里因为是同一线程,其已经占用锁,所以必然是现场安全的,直接设置state值即可。
//setSate方法解释请看下文
setState(nextc);
return true;
}
//获取锁失败,请移步accquire方法,看下一步对应的方法调用
return false;
}
Method:setState(int newState)
//为state赋值
protected final void setState(int newState) {
state = newState;
}
Method:addWaiter(Node mode)
//采用指定的模式为当前线程创建新的Node,并将其入队
//mode:当前线程占用锁的模式,accquire方法中传入为Node.EXCLUSIVE
//返回值:代表当前线程的Node
private Node addWaiter(Node mode) {
//创建一个新的Node,请看下文Node的构造函数
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//通过快速尝试的方式入队,如果失败,则执行enq()方法
Node pred = tail;
if (pred != null) {
node.prev = pred;
//如果尾节点不为空,则通过CAS入队,这里队列的相关知识,不再进行介绍
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//快速尝试的方式失败,则调用enq方法入队,请看下文enq方法
enq(node);
//成功入队之后,请移步acquireQueued方法
return node;
}
Method:Node(Thread thread,Node mode)
Node(Thread thread, Node mode) { // Used by addWaiter
//这里我们设置了nextWaiter的值,如果是独占模式就是Node.EXCLUSIVE
//共享模式就是null
//那么该值是如何用的呢,请接着看
this.nextWaiter = mode;
this.thread = thread;
}
Method:enq(Node node)
//将指定Node入队
private Node enq(final Node node) {
//CAS加失败重试机制
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//队列为空,队列初始化,具体请看下文compareAndSetHead方法
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//队列不为空,入队具体请看下文compareAndSetTail方法
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
Method:compareAndSetHead(Node update)
//CAS设置队头节点
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
Method:compareAndSetTail(Node update)
//CAS设置队尾节点
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
Method:acquireQueued(Node node,int arg)
//在看这个方法之前,我们先思考一下整个流程
//前面我们判断当前线程无法获取到锁,通过addWaiter已经将node加入了队列。
//那既然该线程无法获取锁,我们需要做的就是将该线程阻塞。但是如果直接阻塞的话,我们试想这种情况,
//此时其他线程已经执行完毕,释放了锁,那么该线程就没必要阻塞了。或者该线程在判断无法获取锁之后到
//入队,这中间有一个时间差,如果在这个时间差内其它线程都已经执行完毕 并释放了锁,然后判断队列中还
//没有该线程,自然无法唤醒它,那么该线程将会一直阻塞下去。
//通过以上分析,我们可以猜测该方法做了以下事情:
//
//1.阻塞之前判断该线程是否需要阻塞。
//2.如果需要阻塞则阻塞当前线程,否则直接返回。
//返回值:该方法在队列中阻塞获取锁的过程中有没有被中断
final boolean acquireQueued(final Node node, int arg) {
//最终是否成功获取到锁
boolean failed = true;
try {
boolean interrupted = false;
//自旋直到获取到锁为止
for (;;) {
//获取到当前node的前驱节点,详情请看下文predecessor方法。
final Node p = node.predecessor();
//如果其前一个节点为队头节点,则尝试获取锁
//记得我们在第一篇分析AQS数据结构时,讲到head就代表当前拥有锁的线程,原因就在于
//以下这段代码
//如果p当前正在运行的节点,就尝试获取,因为p为head说明没有其他线程等待了
//而且p有可能刚好执行完成了
if (p == head && tryAcquire(arg)) {
//如果获取锁成功,则将head设置为当前获取锁的线程
setHead(node);
//将前一个执行成功的线程进行垃圾回收
p.next = null; // help GC
//成功获取到锁
failed = false;
//未被中断
return interrupted;
}
//两种情况将走到这里
//1.该Node前一个节点不是head,即当前线程的前一个线程不是正在运行的线程,那么
//就不进行尝试,因为可能还有其他线程在等待。
//2.前一个线程是正在运行的节点,但是该线程获取锁失败,说明锁还被前一个线程占用
//这里在阻塞之前先判断是否应该阻塞,如果需要则阻塞
//详情请看下文shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//parkAndCheckInterrupt返回true,表示等待的过程中被中断
//但是此时我们并不能直接返回,因为可能会有后续的线程已经把当前线程设置为
//前驱节点,所以我们仍然自旋让该线程获取到锁,但是获取到锁之后,会中断该
//线程,然后在释放锁的时候唤醒其后继线程,不然其后继线程是没法被唤醒的。
interrupted = true;
}
} finally {
//前面如果正常执行结束,线程正常获取到锁,那么failed必然为false,只有在有异常抛出的
//情况下,faied将为true。
if (failed)
//该线程在获取锁的过程中异常(这里AQS自己是不会抛出异常的,但是tryAccqure方法
//由子类实现,所以要兼容可能抛出异常的可能性),则取消获取锁
//详情请看cancelAcquire方法
cancelAcquire(node);
}
}
看完该方法请移步accquire方法继续。
Method:predecessor()
final Node predecessor() throws NullPointerException {
//prev指针指向前驱节点
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Method:shouldParkAfterFailedAcquire()
//在分析该方法之前,我们也做一个思考
//在这之前,我们已经判断该线程的前一个线程不是head或者是head但是获取锁失败,那么这种情况下为什么
//不能直接进行阻塞呢,还需要干什么?
//假设我们直接阻塞,那么可能该node的前一个节点已经被中断或者取消等待,那么其前一个节点是没法通知
//该节点的,该线程将无法唤醒。
//该方法将会涉及到我们第一篇中锁提到的waitStatus属性,这里再回忆一下其取值代表的意思:
//0:默认
//1(CANCELLED):由于超时或者中断等原因取消等待,一旦取消,该线程将不会再获取锁
//-1(SIGNAL):表示该线程的后继线程需要被唤醒
//-2(CONDITION):表示该线程在某个条件上等待
//-3(PROPAGATE):表名下一个获取锁的线程需要无条件传播,只有在共享锁才会用到
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果前驱节点已经知道其后继节点是需要被唤醒的则可以安全阻塞,这样保证了该阻塞的线程一定
//能正确的被唤醒。
//这里一为什么会为-1,因为该线程第一次执行该方法时,判断该值不为-1时(一般为默认值0),
//并不会立即阻塞该线程,而是先设置为-1,然后再重新尝试获取一次锁。
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//如果前驱节点已经因为等待超时或者中断原因取消,不再获取锁,那么其将不会通知该线程
//此时需要一直往前遍历,找到一个未被取消的线程,将其设置为该节点的前驱节点
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//如果其前驱节点为0(默认就是0,也是绝大多数情况)或者-2,-3(什么情况下会为这两
//个值后面再分析),则将其前驱节点的waitStatus设置为SIGNAL,让其释放锁之后唤醒该线
//程。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//如果成功设置了前驱节点的waitStatus为-1之后,代表线程可以被正确唤醒,但是这是并不立即
//阻塞该线程,而是再重新尝试获取一次锁看看(这是有可能成功的,此时其他线程已经释放锁)
return false;
}
看完该方法请继续移步accquireQueued方法,继续往下看。
Method:parkAndCheckInterrupt()
//阻塞当前线程,并判断中断标志位
//至于LockSupport方法请移步博文“LockSupport分析”,这里不做过多解释
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
//大家想一下,为什么会走到这行代码,线程中断之后,如果当前线程处于阻塞状态不是会抛出异常吗
//阻塞中断抛异常的问题,请移步博客“线程中断”,这里不做过多解释
//那这里唯一的解释就是,LockSupport.park方法的在被中断之后是不会抛出异常的,而只是设置了
//线程中断标志位
return Thread.interrupted();
}
看完该方法请继续移步accquireQueued方法
Method:cancelAcquire()
//这里我们先思考一下,如果当前线程取消等待,那么可能有这种情况:该线程已经入队列,而且其后继线
//还等着它去唤醒,而唤醒是在释放锁的时候,但是该线程根本不会拥有锁,无法唤醒,所以在这里要找到一
//个能唤醒后继线程的线程。然后将该节点从队列中移除
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
//如果当前节点已经是CANCELLED状态,则往前遍历,找到一个waitStatus小于0的节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
//将当前Node设置为取消等待,先这样做的目的是,避免在执行该方法时,其他线程突然进来,设置
//该Node为其前驱节点
node.waitStatus = Node.CANCELLED;
//如果当前需要需要取消等待的节点是尾节点,则通过CAS设置队尾节点为其前一个节点
if (node == tail && compareAndSetTail(node, pred)) {
//通过CAS将当前节点的前一个节点设置为队尾只有,通过CAS设置尾节点的下一个节点为空
compareAndSetNext(pred, predNext, null);
} else {
//如果当前节点不是尾节点
int ws;
//前面找的这个pred节点是waitStatus不为1的节点
//当满足以下两点时,将当前节点的前驱节点的next指向当前节点的后继节点
//使当前节点的后继可以被正常唤醒
//1.当前节点的前驱节点不为头节点
//2.当前节点的前驱节点为SIGNAL或者通过CAS设置为SIGNAL成功
//3.当前节点的后继节点存在且不为CANCELLD。
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//详情请看下文unparkSuccessor方法
//注意这里唤醒后继节点只是让后继节点继续自旋去获取锁而已(请看accquireQueue方
//法),这是pred线程可能还没执行完成,后继节点依然可能会获取锁失败。
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
Method:unparkSuccessor()
//唤醒Node节点的后继节点(未被取消)所在线程
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//如果当前节点的waitStatus小于0,则将其设置为0,因为小于0代表其需要通知其他线程,但是该方
//会执行通知操作
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//找到当前节点的后继可被唤醒节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//当该节点的下一个节点为空时,从队尾节点往前遍历找到一个第一个(从前往后)未被取消的节
//点。
//为什么从队尾开始找:因为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);
}
至此,ReentrantLock中获取独占锁的源码已经分析完毕,我们会在文章结尾进行统一总结,接下来,我们复习一下ReentrantLock是如何释放锁的。
释放锁的过程相对于获取锁的过程更简单一些,接下来我们看看其实现原理。
Method:unlock()
public void unlock() {
//详情请看下文Sync类的release方法
sync.release(1);
}
Method:release()
//前面有讲过线程通过调用release方法从而调用tryRelease方法释放锁
public final boolean release(int arg) {
//如果尝试释放锁成功,则唤醒后继线程
//详情请看下文tryRelease方法
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//从头结点(代表当前线程)开始往后找到一个可以被唤醒的线程
//详情请看上文unparkSuccessor方法
unparkSuccessor(h);
return true;
}
return false;
}
Method:tryRelease()
//尝试释放锁
//一般情况下release值为1
//返回值:是否成功释放锁
//注意:这里线程已经持有锁,所以调用线程安全的方法取修改state状态
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果state为0代表没有线程占用锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//更新state的值
setState(c);
return free;
}
至此我们分析完了ReentrantLock非公平锁的定义,获取锁和释放锁的实现原理。现在我们来总结一下:
- 非公平锁定义
定义内部类NonfairSync继承AQS并重写其受保护的方法(这里我们并不比较其和公平锁的差异,等讲完公平锁之后,再进行比较)作为ReentrantLock同步辅助类。
- 获取锁
1.state值为0代表锁未被占用,使用CAS设置state值为1,并设置独占锁的线程为当前线程。
2.state值不为0,代表锁已经被占用,如果获取锁的线程为当前线程,则可重入;当其他线程竞争该锁时将其加入队列,并进行阻塞。
- 释放锁
拥有锁的线程设置将state的值减1,如果state为0,设置占用锁线程为null;然后从队列头节点开始唤醒后继节点所代表的线程。
上图是公平锁和非公平锁的实现差别,我们看到唯一不同的是获取锁,而释放锁的过程是相同的,接着我看下获取锁的差异性体现在哪里。
Method:lock()
final void lock() {
//非公平是先采用快速尝试的方式去获取锁,那么可能后执行的线程会先抢锁成功,为了直观把非
//公平锁的获取方式贴到下面,可以对比
//对于该方法的实现请看上文
acquire(1);
}
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
Method:tryAccquire()
//采用非公平的方式获取锁,请大家对比公平锁的获取锁方式看,这里我们只讲唯一区别的地方
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//通过对比发现其与非公平锁只差这一行代码,这里会进行如下校验:
//hasQueuedPredecessors(队列终是否已经有非自身的等待线程)
//详情请看下文:hasQueuedPredecessors方法
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;
}
Method:hasQueuedPredecessors()
//查询是否有其他线程在该线程之前等待
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//当满足以下情况认为当前线程需要等待
//1.h!=t:因为h==t说明要么队列为空,此时线程当然可以获取锁。或者队列中只有一个节点在运行,
//这时锁被队头元素占用(当前占用锁的线程),其可以去尝试获取一下,因为队头线程可能刚好执行完
//成。
//2.h.next==null:该点暂未理解
//3.h.next!=null且h.next代表的线程不是当前线程(不是可重入情况),因为可重入是可以获取锁
//的。
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
至此公平锁和非公平锁已经分析完毕,接下来我们解答一下开篇的几个问题。
问题一:关于Node节点的属性(waitStatus和nextWaiter)?
答:当某个线程获取不到锁时,会创建代表该线程的Node,该Node默认的默认waitStatus取值为0。但是此时并不能安全将其入队,因为该Node的前驱节点锁代表的线程可能已经因为异常等原因取消等待,这将导致该线程无法被唤醒,所以我们需要为该线程找到一个可以正常唤醒自己的节点,并设置该节点的waitStatus为SIGNAL(-1),代表其需要通知其后继线程。在某个线程释放锁时,会从head节点(持有锁的线程)开始找到其后继的一个waitStatus<=0的节点,将其唤醒。对于nextWaiter取值,在ReentrantLock中,对于每个节点都是Node.EXCLUSIVE,但是该值并未使用到,我们将在后续博文中发现其作用。
问题二:AQS内部是如何使用队列的?
答:关于AQS的数据结构在第一篇中我们已经讲到过,这里我们注意一下以下几点:
1.队列的头结点代表当前持有锁的线程(具体请看acquireQueued方法,在线程获取锁之后会调用setHead方法设置对头为当前节点)。
2.阻塞入队时,将Node加入到队尾之后,会为其找到一个可唤醒自己的前驱节点,并将其前驱节点到自己之间已取消等待的节点移除。
3.释放锁,唤醒线程时,从队头开始,找到一个未取消等待的节点将其唤醒。
问题三:AQS中阻塞和唤醒线程采用的是什么方式?
答:采用LockSupport.park()和LockSupport.unpark(),具体请看【博文LockSupport分析(还没写)】。注意这里的唤醒,只是让唤醒其获取锁的阻塞,唤醒之后,其仍然要重新竞争锁。
问题四:共享模式和独占模式究竟有什么区别,是如何实现的?
答:通过ReentrantLock我们了解到了独占模式,即锁只能被一个线程独占,当其他线程获取锁时,将阻塞。对于共享模式请看博文【AQS分析第四篇(通过CountDownLatch分析AQS共享模式实现原理】
问题五:ReentrantLock公平锁和非公平锁有什么区别?
答:其区别在于获取锁时的条件判断。
公平锁:某个线程获取锁时,如果队列中已经有线程等待,则当前线程获取锁必然失败,保证了先获取锁的线程先被通知。
非公平锁:当某个线程获取锁时,即使队列中有线程等待,依然会去竞争锁,这样可能后进来的线程先获取到锁。