通过 CountDownLatch 来研究 AQS:
前面已经说过 CountDownLatch 唤醒的两种方式:还有第三种:
1: 正常唤醒, countDownLatch.countDown(); 让 倒计时的值达到0
2: interrupte, 通过线程打断的方式唤醒阻塞的线程。
3: countDownLatch.await(timeout)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
仍然从具体的代码开始:
子线程:1秒后超时阻塞
主线程:2秒后永久阻塞
boolean isZero = countDownLatch.await(6, TimeUnit.SECONDS);
这个方法的返回值是:超时时间到了返回时,是否已经倒计时为0
返回 true 表示,这个方法返回时,倒计时已经为0
返回 false 表示,时因为超时返回的
private static void t9() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(()->{
try {
sleep(1000);
System.out.println("等待中....");
boolean isZero = countDownLatch.await(6, TimeUnit.SECONDS);
System.out.println("----timeout-----:is zero" + isZero);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
sleep(2000);
countDownLatch.await();
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//如果当前倒计时已经是 0,tryAcquireShared(arg) 返回 1,|| 前面为true,后面不执行
//如果当前倒计时还不是 0,tryAcquireShared(arg) 返回 -1,继续执行 || 后面的
return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
//System.nanoTime() 获取当前相对时间。 deadline 是未来到期的相对时间(纳秒)
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED); //添加节点,
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) { //-----------D
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true; //-----------C
}
}
//距离到期(超时)还有多少纳秒
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
/*
* 1、在阻塞前,这里就成立, 说明指定的超时时间太短了。
* 比如指定超时时间 <= 0。 刚进来这里就超时了。不必阻塞,立刻返回
* 2、如果到期时间还剩不到 1000 纳秒,就不去休眠了,在这个 for 循环中
* 转两圈,1000纳秒就耗没了
* 3、阻塞超时以后,自动被唤醒,接着for寻,这时 if 判断一定小于0
*/
return false; //-----------B
/*
* shouldParkAfterFailedAcquire 自己想要休眠,那就把自己前节点状态改成 SIGNAL
* 如没有改成功,继续循环,直到改成功以后,才能执行 && 后的代码,才能休眠自己。
*
* nanosTimeout > spinForTimeoutThreshold :
* static final long spinForTimeoutThreshold = 1000L; 一个常量,表示1000纳秒
* 如果到期时间还剩不到 1000 纳秒, 就没有必要让线程休眠了。刚休眠就要唤醒。
* 如果到期时间还剩不到 1000 纳秒,&& 后面返回 false, if 进不去, 接着外层for
* 循环,有可能就从 上面 if (nanosTimeout <= 0L) {} 中出去了。
*/
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
//来到这里说明,前节点已经设置状态 SIGNAL, 而且到期时间还足够的长,那就休眠吧
LockSupport.parkNanos(this, nanosTimeout); //-----------A
if (Thread.interrupted())
throw new InterruptedException(); //-----------E
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//LockSupport::parkNanos 阻塞线程
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
//有超时的阻塞,时间到后,自动返回, 如果 nanos < 0 , 立刻返回
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
经过上面代码, 线程要么因为超时时间太短,直接返回了。要么就阻塞了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
此时阻塞的线程想要解除阻塞也有几种途径:
1、超时时间到了,返回
超时返回也是正常解除阻塞,而不是线程打断, 所以从 ----A 中解除阻塞后,
后面异常不会抛出。接着进行 for 循环, 大概率的从 ----B 退出循环,返回到用户
代码。但是也有很小可能从 ----C 处退出。条件是:自己的前节点是 head, 而且,
刚刚从超时阻塞中唤醒,而倒计时也被设置成了0,就是这么巧,本来是超时退出的。
结果变成了正常流程(倒计时达到了0)退出。 (这个小概率事件是正常流程,不再这里说了)
接着还要执行 finally 中的代码 cancelAcquire(node); 把自己从链表中删除,同时
根据情况看看有没有必要唤醒自己的下一个节点。 返回一步步返回到用户代码中。
2、阻塞超时前,倒计时达到了0。
让倒计时为0的那个线程,执行了 doReleaseShared()【或者那个小概率事件,自己阻塞超时
被自动唤醒,然后,倒计时为0的线程在 执行 doReleaseShared 时,被自己抢先来一次for
循环, 而自己正好又是 head 的下一个,此时 ----D 成立了, 这是他们可能会竞争执行
doReleaseShared 不过后续的流程时相同的了】。从 head 开始,解除
head 后一个线程的阻塞, head 的后一个线程开始运行,干掉head, 自己当head,
然后唤醒自己的下一个 .....
本线程是的阻塞虽然还未timeout, 但是也提前唤醒了。从 ----C 开始退出,
finally 中的代码 条件不满足,不执行
3、阻塞中等待超时前,被其他线程打断 【thread1.interrupt();】
线程被打断,从阻塞中返回,----E 处成立,抛出异常,这个异常让 for 循环结束,
而且异常在这里没有捕获,直接抛出用户代码,同时调用 finally 的代码,看看
需不需要唤醒自己的下一个节点。(自己已经唤醒了,如果还需要自己唤醒下一个,那就也
把下一个唤醒,下一个唤醒后如果发现自己还没有到达唤醒条件,就会再次进入休眠)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CoundDownLatch 能看的也都看差不多了,但是对于 AbstractQueuedSynchronizer 的认识
还是没有认识到位,还不能总结出 AbstractQueuedSynchronizer 的意思。 所以还需要
从其他途径找到突破口。
在分析 CoundDownLatch 的时候添加节点,调用了方法:Node node = addWaiter(Node.SHARED);
传入的是 Node.SHARED ,共享模式。以现在的认知水平来结合 CoundDownLatch 说一下对
共享模式的认识。 CoundDownLatch 可以通过调用 await 方法将调用者阻塞。 有多少
个调用者都是可以的。而且所有的调用者全部阻塞(不知道这个阻塞可不可以理解成成功获取
了资源,如果说成锁的话,是不是都获得了锁,所以是共享的??)。所以想要了解什么是
共享的模式 还要 对照另外一个模式 独占模式。
搜索一下 addWaiter 的调用, 发现使用 Node.EXCLUSIVE 作为参数的有三个方法:
public final void acquire(int arg)
private boolean doAcquireNanos(int arg, long nanosTimeout)
private void doAcquireInterruptibly(int arg)
一个公开的方法,两个私有方法,两个私有方法有分别被下面两个公开方法调用:
public final boolean tryAcquireNanos
public final void acquireInterruptibly(int arg)
然后搜索这三个公开的方法,发现他们都被 ReentrantLock 使用。
所以新的研究对象出现了: ReentrantLock
================================================================================
ReentrantLock:
ReentrantLock 一个可重入的独占锁。
可重入:当一个线程已经获取锁,那么再没有释放的情况下还能再次获取该锁。现在不明白的
地方是,线程都已经获取锁了,为什么还要再获取一次呢, 是不是解决递归调用一个带锁
的方法,再释放锁前再次调用,就是再次获取一次了。
独占锁:一旦一个线程获取了这个锁,再未释放前,其他线程将无法获取该锁。
ReentrantLock 又分成公平锁和非公平锁(默认)。
公平锁:多个线程都在等待着同一把锁,锁的占有者释放锁时,谁等待的时间最长,
谁能获取该锁。
非公平锁:一个线程释放锁以后,其他等待的线程一哄而上,但是只有一个能够成功获取。
锁的获取方式:四种:
lock.lock(); 阻塞式获取锁
lock.lockInterruptibly(); 阻塞式获取锁,可以被打断
lock.tryLock() 非阻塞式获取锁,返回 true 表示获取了锁
lock.tryLock(timeOut, unit) 带超时的阻塞式获取锁。
他们的实际上的区别还需要看源码才能明白
锁的释放:
lock.unlock(); 一般要放到 finally 中执行,释放锁是一定要做的。
获取一次锁,就要释放一次, 二者要对应
条件【Condition】:
锁上还可以带条件: Condition cd = lock.newCondition();
条件的使用也应该放到 lock() 和 unlock() 之间。 使用生产者和消费者模型来说。
生产者获取了锁,开始生产,发现仓库已满,不能再生产了, 于是就调用【生产条件】await,
await 的调用让生产者线程放弃了锁, 并且阻塞在 await 上。
生产者放弃了锁,此时消费者就能获取这个锁。于是便来消费。消费者消费一个后,仓库
就空了一个, 此时可以通知生产者继续生产的。 需要注意的是,此时消费者还是持有锁的。
生产者现在虽然被通知可以继续生产,但是消费者此时并没有释放锁。 因此需要消费者
把锁释放以后,生产者才有可能获取锁。 只有生产者获取锁以后,才能从 await 的阻塞
状态中返回。 然后继续执行 await 后的代码
private ReentrantLock lock = new ReentrantLock();
private Condition cp = lock.newCondition();
private Condition cc = lock.newCondition();
生产者 消费者
循环执行 循环执行
lock lock
| |
满了-Y- cp.await 空了-Y- cc.await
|N / |N /
继续生产 继续消费
| |
通知消费 cc.signal 通知生产 cp.signal
| |
unlock unlock
按照上面说的,生产者阻塞再 cp.await 上, 消费者 cp.signal 通知了生产者。
但是只要 消费者还没有 unlock, 生产者仍然无法运行。 直到消费者 unloack,
生产者才有可能 获取锁,并执行 cp.await 下面的代码。消费者 unlock 后,生产者不是
从头开始执行 lock, 而是直接从 cp.await 中唤醒,并执行后面的代码
cp.await 的作用是:阻塞当前线程,并且释放持有的锁
被别的线程 signal 后,阻塞再 cp.await 的线程被唤醒,但是要先获取锁,然后向下执行。
到现在关于表面上的认知已经阐述完了。具体看源码:
================================================================================
两个线程争抢一个锁, 抢到锁的线程,会再次获取一次锁(体现可重入), 然后释放锁(2次)
第一个抢到锁的线程释放锁以后,阻塞再 lock 上的线程才有机会获取锁。
private void t1()
{
ReentrantLock lock = new ReentrantLock();
Runnable r = () -> {
lock.lock();
try {
System.out.println("已获取锁:" + Thread.currentThread().getName());
sleep((int) (Math.random() * 4000 + 1000));
lock.lock();
try {
System.out.println("已获取锁2:" + Thread.currentThread().getName());
}finally {
System.out.println("释放锁2:" + Thread.currentThread().getName());
lock.unlock();
}
} finally {
System.out.println("释放锁:" + Thread.currentThread().getName());
lock.unlock();
}
};
new Thread(r, "[线程1]").start();
new Thread(r, "[线程2]").start();
}
输出:
已获取锁:[线程2]
已获取锁2:[线程2]
释放锁2:[线程2]
释放锁:[线程2]
已获取锁:[线程1]
已获取锁2:[线程1]
释放锁2:[线程1]
释放锁:[线程1]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ReentrantLock 类中有三个 内部类,
abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync
Sync 是继承前面分析的 AQS 。
这里要使用 AQS 的 独占模式, CountDownLatch 使用了 AQS 的共享模式。
ReentrantLock 有两个构造方法,用于构建 公平锁 和 非公平锁(默认)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
非公平锁情况:
lock.lock();
public void lock() {
sync.lock();
}
//NonfairSync::lock
final void lock() {
//进来就想要改 AQS 的 state, 从 0 改成1.
//改成功:说明当前锁还还没有人持有,现在自己持有了
//改失败:1、 说明当前锁已经有人持有,
2、 当前并没有人持有锁,但多线程正在抢锁,自己竞争失败了
3、 自己就是持有锁的线程,又来获取一次(重入)
if (compareAndSetState(0, 1))
//自己获得了锁,把 AQS 中持有锁的线程更新为本线程。 获取锁倒是简单
setExclusiveOwnerThread(Thread.currentThread());
else
//锁早就被人持有,或者,上面竞争失败了,或者自己至少重入而已
acquire(1);
}
//AQS::acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) && //-------------1
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//-------------2
selfInterrupt(); //-------------3
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-------------1
//NonfairSync::tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//Sync::nonfairTryAcquire
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()) {
// 本线程是锁持有值,就是想要重入锁
// 重入锁就是把状态加1, 那么unlock 应该就是-1了,
// 所以获取和释放要一一对应
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//没能获取到锁的线程。
return false;
}
所以 ----1 方法返回后, 持有锁的线程返回 true, 结束后续的代码执行
返回 false, 表示 没有获取到锁的线程, 需要继续执行后续代码。
-------------2
没有获取锁的线程,继续执行:
addWaiter(Node.EXCLUSIVE), arg) :
前面介绍过,创建一个线程专属的 node 对象,并且把 node 添加到第一个节点是 head 的
双向链表中,并且返回这个 node 对象。注意这里的模式是 :Node.EXCLUSIVE
// AQS::acquireQueued
// 这段代码看着好眼熟啊, 可以对比一下: AQS::doAcquireSharedInterruptibly(arg)
final boolean acquireQueued(final Node node, int arg) {
// 进入这里的线程都是没有获取锁的线程,
// 获取锁的那个线程,或者重入锁的情况,是不可能执行到这里的。
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 本线程没有获取锁,自己的前置节点就是 head,
// 又过了这么久使用 tryAcquire(arg) 再试一次,万一获取锁了呢
if (p == head && tryAcquire(arg)) {
//万一的情况发送了,还真的获取了锁。 那就把自己设置成 head
setHead(node);
p.next = null; // help GC
failed = false;
//返回 是否被打断, 但是 finally 中代码不执行
return interrupted;
}
/*
* 自己不是 head 的后一个节点,或者是 head 的后一个,
* 但是再次尝试获取锁还是不成功。
* shouldParkAfterFailedAcquire : 找到一个仍然有效的前节点,把它的
* 状态设置成功 SIGNAl, 让前节点有义务唤醒本线程。 如果设置不成功,
* 就不能休眠,再循环一次,一定要设置成功
* parkAndCheckInterrupt() 设置前置节点的 状态为 SIGNAl 成功,
* 本线程可以安心地无限期休眠了。
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
-------------3:
正常流程是: 没有获取锁的线程,就阻塞在 ----2 里面, 所以 ----3 没有机会执行。
那就看看正常流程下,一个线程释放锁,有干了什么
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
本篇完