简介
java除了提供synchronized(跟object header相关)实现以外,还创造了同步队列来实现相同的功用。并且演化出了juc并发相关的工具,包括了ReentrantLock,Semaphore,CountDownLatch,CyclicBarrier。
数据结构说明
队列的数据涉及到两个指针,以方便数据的遍历与修改 Node(prev,next,others) ,同时需要指定两个特殊节点(head,tail)来方便数据的位置界限
- 队列的数据规定有 先进先出 意味着
- 遍历的时候,只能从头(第一个节点 head)开始
- 写数据的时候,只能从尾(最后一个节点 tail)进行修改。
- 当初始化的时候,head和tail将是同一个节点,同时当head==tail的时候,意味着当前队列为空。同时自然还有一个约定,head没有前一个节点即head.prev=null,tail的下一个节点不存在,tail.next=null
- 当队列存在数据的时候,一定是这样的。 head---first_node---second_node---other_nodes---tail 。所以又衍生出一个条件,queue中只有 head.prev才能是null,其他节点都必须有prev.否则这个节点是非法存在的,需要进行其他处理
- 整理如下表
| 数据约束 | 说明 |
|---|---|
| head | head.prev=null,只有head节点的prev才能是null,其他节点若出现,则是非法错误的节点,其形式是Node(null,next,null) |
| tail | tail.next=null,enqueue的时候,只需要修改tail |
| first_node | 如果一个node.prev==head,则node是first_node,first_node的判断,当first_node获取成功以后,需要自身来替代old_head,成为new_head |
抽象同步队列中的state
state的修改,都放在具体的子类中。控制着acquire/release的逻辑
| mode | 说明 |
|---|---|
| exclusive | state ,0表示当前没有线程在使用queue,被阻塞的其他线程现在可以使用queue了。大于0的时候表示,当前线程正在使用queue,其他线程会被阻塞,必须等待当前线程的退出。语义类似 synchronized(object)。这里是支持重入的。所以state>=1,是可能的。state需要看子类的tryAcquire/tryRelease方法具体实现 |
| share | 需要看子类的tryAcquireShared/tryReleaseShared 的具体实现 |
下面的方法可以帮助我们更加的理解 子类的作用
acquire的时机
- exclusive mode 的 aquire 方法
public final void acquire(int arg) {
if (!tryAcquire(arg))
acquire(null, arg, false, false, false, 0L);
}
tryAcquire方法返回false的时候,再去调用acquire方法.
- shared mode 的 acquireShared方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
acquire(null, arg, true, false, false, 0L);
}
tryAcquireShared<0 的时候 才会去acquire
两个方法的比较,说明share mode的时候,更需要关注当前state这个状态量
队列中的阻塞/与解除阻塞
基础方法
| item | 方法 | 触发时机 |
|---|---|---|
| 阻塞 | LockSupport.park | acquire失败的时候 |
| 解除阻塞 | LockSupport.unpark | signal发出的时候 |
signal的几种情况
- share mode,acquire成功的时候 触发signalNextIfShared方法
private static void signalNextIfShared(Node h) {
Node s;
if (h != null && (s = h.next) != null &&
(s instanceof SharedNode) && s.status != 0) {
s.getAndUnsetStatus(WAITING);
LockSupport.unpark(s.waiter);
}
}
acquire方法中 获取成功的时候
if (acquired) {
if (first) {
node.prev = null;
head = node;
pred.next = null;
node.waiter = null;
if (shared)
signalNextIfShared(node);
if (interrupted)
current.interrupt();
}
return 1;
}
- release或者cleanQueue的时候
- exclusice mode时,唤起waiters时调用的doSignal方法
okay,到这里我们算是了解了基础的背景知识。下面就需要对aquire方法做更深的了解
acquire方法
该方法是分成了几个步骤的大抵如下,是由几个if条件来确定的
- 首先它是一个死循环,只有在满足条件下,才能退出。
for (;;) {
// method body
}
- 从指定的node节点遍历,确保acquire的时候,node处于正确的位置.
if (!first && (pred = (node == null) ? null : node.prev) != null &&
!(first = (head == pred))) {
if (pred.status < 0) {
cleanQueue(); // predecessor cancelled
continue;
} else if (pred.prev == null) {
Thread.onSpinWait(); // ensure serialization
continue;
}
} //注意continue
跳出if的条件
| 跳出if条件 | 说明 |
|---|---|
| node为null | 表示新来的竞争者,直接跳过(不用加入队列中,只有在第一次acquire=false的时候,再入队,等待时机),去参与竞争 |
| node不为null | 表明这是一个已经入队的竞争者,然后遍历到first_node,这里主要遵循队列的"先进先出",即first_node有种优先权。 |
- 开始去修改queue的state
if (first || pred == null) { // 这里刚好可以阐释 ‘跳出if的条件’部分
boolean acquired;
try {
if (shared)
acquired = (tryAcquireShared(arg) >= 0);
else
acquired = tryAcquire(arg);
} catch (Throwable ex) {
cancelAcquire(node, interrupted, false);
throw ex;
}
if (acquired) { // 获取成功
if (first) { // first_node时更新head
node.prev = null;
head = node;
pred.next = null;
node.waiter = null;
if (shared)
signalNextIfShared(node);
if (interrupted)
current.interrupt();
}
return 1; // 如果获取成功 这里就会退出整个循环
}
}
- acquire失败后的处理
| 失败的case | 处理方法 |
|---|---|
| queue尚未被创建 | 初始化queue |
| node为null | 需要新建node |
| node.prev==null | 此时需要入队 |
| 阻塞处理 | 调用LockSupport.lock.直到在某处由于signal,才被唤醒 |
Node t;
if ((t = tail) == null) { // initialize queue
if (tryInitializeHead() == null)
return acquireOnOOME(shared, arg);
} else if (node == null) { // allocate; retry before enqueue
try {
node = (shared) ? new SharedNode() : new ExclusiveNode();
} catch (OutOfMemoryError oome) {
return acquireOnOOME(shared, arg);
}
} else if (pred == null) { // try to enqueue
node.waiter = current;
node.setPrevRelaxed(t); // avoid unnecessary fence
if (!casTail(t, node))
node.setPrevRelaxed(null); // back out
else
t.next = node;
} else if (first && spins != 0) {
--spins; // reduce unfairness on rewaits
Thread.onSpinWait();
} else if (node.status == 0) {
node.status = WAITING; // enable signal and recheck
} else {
long nanos;
spins = postSpins = (byte)((postSpins << 1) | 1);
if (!timed)
LockSupport.park(this);
else if ((nanos = time - System.nanoTime()) > 0L)
LockSupport.parkNanos(this, nanos);
else
break;
node.clearStatus();
if ((interrupted |= Thread.interrupted()) && interruptible)
break;
}
}
- 处理queue中被标记为取消的node
return cancelAcquire(node, interrupted, interruptible);
Condition
此处就不赘述,其实就是另一queue. 需要注意的是两个关键方法
await方法
关键是调用了acquire. 调用的前提是
while (!canReacquire(node)) {
// 这里循环的跳出条件 node在同步队列中 而这正是signal所在的核心
}
在这之后 回去参与竞争acquire
signal方法
关键是调用了euqueue方法 ,将condition queue中的节点,入队到同步队列