抽象同步队列

121 阅读5分钟

简介

java除了提供synchronized(跟object header相关)实现以外,还创造了同步队列来实现相同的功用。并且演化出了juc并发相关的工具,包括了ReentrantLock,Semaphore,CountDownLatch,CyclicBarrier。

数据结构说明

队列的数据涉及到两个指针,以方便数据的遍历与修改 Node(prev,next,others) ,同时需要指定两个特殊节点(head,tail)来方便数据的位置界限

  1. 队列的数据规定有 先进先出 意味着
    1. 遍历的时候,只能从头(第一个节点 head)开始
    2. 写数据的时候,只能从尾(最后一个节点 tail)进行修改。
    3. 当初始化的时候,head和tail将是同一个节点,同时当head==tail的时候,意味着当前队列为空。同时自然还有一个约定,head没有前一个节点即head.prev=null,tail的下一个节点不存在,tail.next=null
    4. 当队列存在数据的时候,一定是这样的。 head---first_node---second_node---other_nodes---tail 。所以又衍生出一个条件,queue中只有 head.prev才能是null,其他节点都必须有prev.否则这个节点是非法存在的,需要进行其他处理
    5. 整理如下表
数据约束说明
headhead.prev=null,只有head节点的prev才能是null,其他节点若出现,则是非法错误的节点,其形式是Node(null,next,null)
tailtail.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说明
exclusivestate ,0表示当前没有线程在使用queue,被阻塞的其他线程现在可以使用queue了。大于0的时候表示,当前线程正在使用queue,其他线程会被阻塞,必须等待当前线程的退出。语义类似 synchronized(object)。这里是支持重入的。所以state>=1,是可能的。state需要看子类的tryAcquire/tryRelease方法具体实现
share需要看子类的tryAcquireShared/tryReleaseShared 的具体实现

下面的方法可以帮助我们更加的理解 子类的作用

acquire的时机

  1. exclusive mode 的 aquire 方法
public final void acquire(int arg) {
    if (!tryAcquire(arg)) 
        acquire(null, arg, false, false, false, 0L);
}

tryAcquire方法返回false的时候,再去调用acquire方法.

  1. 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.parkacquire失败的时候
解除阻塞LockSupport.unparksignal发出的时候

signal的几种情况

  1. 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;
}
  1. release或者cleanQueue的时候
  2. exclusice mode时,唤起waiters时调用的doSignal方法

okay,到这里我们算是了解了基础的背景知识。下面就需要对aquire方法做更深的了解

acquire方法

该方法是分成了几个步骤的大抵如下,是由几个if条件来确定的

  1. 首先它是一个死循环,只有在满足条件下,才能退出。
for (;;) {
// method body
}
  1. 从指定的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有种优先权。
  1. 开始去修改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; // 如果获取成功 这里就会退出整个循环
    }
}
  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;
    }
}
  1. 处理queue中被标记为取消的node
return cancelAcquire(node, interrupted, interruptible);

Condition

此处就不赘述,其实就是另一queue. 需要注意的是两个关键方法

await方法

关键是调用了acquire. 调用的前提是

while (!canReacquire(node)) {
// 这里循环的跳出条件 node在同步队列中 而这正是signal所在的核心   
}

在这之后 回去参与竞争acquire

image.png

signal方法

关键是调用了euqueue方法 ,将condition queue中的节点,入队到同步队列

image.png

image.png