JDK 1.8.0_152 java.util.concurrent.locks 包下有:
AbstractOwnableSynchronizer
AbstractQueuedLongSynchronizer
AbstractQueuedSynchronizer
Condition
Lock
LockSupport
ReadWriteLock
ReentrantLock
ReentrantReadWriteLock
StampedLock
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AbstractOwnableSynchronizer
| |
AbstractQueuedLongSynchronizer AbstractQueuedSynchronizer(抽象,且没有实现类)
|
|
本类的实现类很多,但都是在其他类的内部类实现。
Subclasses should be defined as non-public internal helper classes (java doc)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Condition
|
|
-------------------------------
| |
AbstractQueuedLongSynchronizer::ConditionObject |
|
AbstractQueuedSynchronizer::ConditionObject
Condition 有两个实现了,都是别人的内部类中实现的
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lock
|
-----------------------------------------------------------
| | | |
StampedLock::ReadLockView | | ReentrantReadWriteLock::ReadLock
StampedLock::WriteLockView | |
| |
ReentrantReadWriteLock::WriteLock |
|
ReentrantLock
|
ConcurrentHashMap::Segment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ReadWriteLock
| |
StampedLock::ReadWriteLockView ReentrantReadWriteLock
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
其他未标记的是没有特殊说明的继承关系。
不过看到这些继承关系还是不知道从哪里开始看起。那就从头开始看,如果是抽象类那就
找到它的子类,从他的子类开始,先要用起来,才能知道那些源码想要干什么。
其中 AbstractQueuedSynchronizer 的实现类 有一个是在 CountDownLatch 中实现。
而且 CountDownLatch 的功能不是那么多,所以 CountDownLatch 是研究锁的突破口。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
使用代码:
设置一个倒计时,总是为2,
开启两组线程, 第一组2个线程, 休眠以后,然后倒计时
第二组3个线程,全部阻塞,等待着倒计时结束。 倒计时结束后,三个线程开始后续的执行。
private static void t1()
{
int count = 2;
CountDownLatch countDownLatch = new CountDownLatch(count);
for (int i=0; i< count; ++i) {
new Thread(() -> {
//countDownLatch.getCount() 获取当前还有几个数
System.out.println("before:" + countDownLatch.getCount());
sleep((int) (Math.random() * 5000));
countDownLatch.countDown(); //会让当前倒计时减1
System.out.println(Thread.currentThread().getId() + "-结束:" + System.currentTimeMillis());
System.out.println("after:" + countDownLatch.getCount());
}).start();
}
for (int i=0; i<3; ++i) {
new Thread(()-> {
try {
//阻塞, 当倒计时为0时这里会被唤醒,再次执行
countDownLatch.await();
//还有一个限时阻塞的,超时了还没有倒计时结束,被唤醒,继续执行
//countDownLatch.await(xxx);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始执行:" + System.currentTimeMillis());
}).start();
}
System.out.println("main:" + System.currentTimeMillis());
}
输出:
before:2
before:2
main:1613032130374
12-结束:1613032132511
after:1
13-结束:1613032134281
开始执行:1613032134281
开始执行:1613032134281
after:0
开始执行:1613032134281
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CountDownLatch 暴露的接口上面基本都涉及到了。
CountDownLatch 内部有一个类 Sync, CountDownLatch 的功能都是 Sync 实现的。
Sync 的实现代码少到让我感到惊讶!
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CountDownLatch countDownLatch = new CountDownLatch(count);
结果就是 创建 Sync 对象,调用 setState(count);
AbstractQueuedSynchronizer::setState
// private volatile int state; // state 的定义
protected final void setState(int newState) {
state = newState;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
countDownLatch.await();
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
AbstractQueuedSynchronizer::acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
下面这个方法被 Sync 复写(必须复写,因为 AbstractQueuedSynchronizer 中这个方法抛异常)
参数 acquires 是 Sync 前面传入的 1, 应该没有什么用。
返回值:
-1: 负数表示 失败
0: 本次成功,后续xxx不成功
1: 正数,表示本次成功,后续的xx也可能成功
不太明白,继续吧
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
显然第一次调用 await 时 Sync::tryAcquireShared 返回了-1
然后就调用了 AbstractQueuedSynchronizer::doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED); //---------------1
boolean failed = true;
try {
for (;;) {
//获取自己加入的 node 的前一个节点。
final Node p = node.predecessor();
if (p == head) {
//自己的前节点就是 head, 再次尝试获取一下资源,
//countDownLatch 倒计时还没结束, 所以这个方法返回值小于0
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//代码在 ---3 中阻塞,如果阻塞解除,这里的if 没有进去,则继续死循环了
if (shouldParkAfterFailedAcquire(p, node) && //---------------2
parkAndCheckInterrupt()) //---------------3
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
---------------1
/**
* 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 内部有 前对象 和 后对象, 他们连接能形成双向链表
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//AbstractQueuedSynchronizer 类中定义了两个 Node 类型的变量, head 和 tail
//很明显 刚刚创建的这个 Node 要加到 head 和 tail 中。
Node pred = tail;
if (pred != null) {
//新创建的 Node 要加到后面,变成新的 tail, 而原来的tail变成了倒数第二个
//所以 新创建的 node 的前一个是 原来的tail. 但是并没着急把原 tail 的
//后一个设置为新加入的 node。 因为需要更新 tail 为新加入的 node。 如果
//更新失败,那不就把原来的 tail 污染了吗。
node.prev = pred;
if (compareAndSetTail(pred, node)) {
//把新加入的 node 设置为 tail, 多线程竞争设置,只有设置成功的
//才能把自己 新加入的 node 设置为 原tail 的后一个。到这里就把
//原tail的 和 新Node 连接在一起, 即原tail 的下一个为新的 node。
//新 加入的 node 是新的 tail
pred.next = node;
return node;
}
}
//来到这里说明: tail 可能为空(链表还不存在), 或者 tail 存在,但是
//竞争设置自己创建的 node 为新的 tail 时,竞争失败了
enq(node); //---------------1.1
return node;
}
---------------1.1:
//当前还没有tail, 或者有tail, 但是自己竞争失败了
//竞争失败,说明想把自己创建的 node 设置成 tail, 但是被别的线程捷足先登。
//这时不要生气, 把自己创建的 node 继续添加到被人抢先添加 node 的后面即可
// enq 的返回值是 添加的 node 的前一个 node, 而不是 node 本身, 但是返回值没人用
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//没有 tail, 说明链表不存在, 先创建一个 head,
//并让 tail 和 head 指向同一个对象, 而且成不成功都需要再循环一次。
//因为 新Node 才是真正的数据。这里是创建 head, head 不是真正数据,
//而是标记数据(找到head,tail就找到了链表)
if (compareAndSetHead(new Node()))
tail = head;
} else {
//被人抢先了,不要紧,这里是循环,直到把自己创建的 node 放到 tail 上
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---------------2
//参数:pred 是刚刚新创建的节点的前一个节点
//参数:node 是刚刚新创建的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//ws 是前节点状态, 如果是 SINGAL 就直接返回 true
return true;
if (ws > 0) {
//如果前节点状态大于0, 那么前节点的前节点作为自己的新的前节点
//如果这个新的前节点的状态还是大于0, 继续把它的前节点作为自己的前节点
//直到找到一个前节点不大于0的节点作为自己的前节点。
//所以这里的代码意思是 删除状态大于0的前节点
//为什么不用考虑 一直循环导致 pred 变成 null 的问题呢。因为最前面的是 head,
//而head的这个变量是 默认为0的,而且不会有大于0的情况
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//把前节点状态从 0或者非 Node.SIGNAL 的状态通通改成 Node.SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
还是没明白这到底在干什么:
补充:
waitStatus 的状态有 下面4个和一个默认值 0
CANCELLED = 1;
SIGNAL = -1;
CONDITION = -2;
PROPAGATE = -3;
所以有效的是值是小于0, 大于0是表示被取消。 0 默认值,不表示意义
SINGAL javaDoc: waitStatus value to indicate successor''s thread needs unparking
一个链表上有多个节点,一个节点的状态是 SINGAL 表明本节点的下一个节点已经在睡觉
或者即将要睡觉,而且本节点被唤醒时或者本节点被取消时有义务唤醒一下自己的下一个节点。
所以这个方法的作用就是,一个线程想要阻塞了,但是自己在阻塞前必须要预定一个叫醒服务。
这样才能安心睡觉。如果一直没预定成功,返回了false, 调用本方法的是一个循环,一定要预定。
所谓的预定叫醒服务就是给自己的前一个节点的状态设置成 SINGAL 。 如果前一个节点的状态
大于0, 表示这个节点已经取消,它不靠谱了,不能找到,继续向前找,实在不行就找到 head 了。
head 的默认值时 0, 而且不可能被设置成大于0的值
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---------------3
这里的代码想要被执行要求 ---------------2 返回 true 才可以。 ----2 返回true则
又说明 新加入的节点(本节点)的前一个节点的 状态已经是 SINGAL。
然后本节点才能来到这里阻塞。
阻塞的解除也有多个方式,这里是通过 unsafe.park 阻塞的。
可以 unsafe.unpark 解除, 也可以 Thread.interrupted() 打断。
因为 ----2 返回 true, 所以如果这里是因为 Thread.interrupted() 解除阻塞的。
这里也会返回 true, 然后执行就是抛出一个异常。
如果这里是 unsafe.unpark 正常解除阻塞, 这里返回 false,接下来 就不会抛出异常了
//看看本节点是如何被阻塞的:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
代码来到: LockSupport 类中
public static void park(Object blocker) {
Thread t = Thread.currentThread();
// Thread 类中有一个变量,表示是谁阻塞的本线程,
// 这就是通过内存地址方式,把 blocker 设置进去
setBlocker(t, blocker);
//代码在这里永久的阻塞了, UNSAFE.park 在其他笔记中讲过了
UNSAFE.park(false, 0L);
//从上面的阻塞中恢复了,把阻塞本线程的对象清除掉
setBlocker(t, null);
}
//设置线程中的一个变量的值。这个变量代表着线程是被哪个对象阻塞的。
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
到了这里:多个调用 countDownLatch.await(); 的线程,都在链表中添加了自己 Node,
而且把前置 Node 的状态变成 SINGAL, 并且自己阻塞在 UNSAFE.park(false, 0L); 上。
如何解除阻塞,还要看 countDownLatch.countDown(); 的调用
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~