Java 并发 CountLatchDown
来源:互联网 发布:linux 新建文件 编辑:程序博客网 时间:2024/06/06 01:16
一个同步工具,使得当前线程等待,直到其他多个线程完成一系列的操作后,继续执行。
其中的当前线程也可以是多个线程,这个地方的线程的数量是任意的。
也就是await() 可以在多个线程中调用。
这个方法可以使得当前线程等待,也就是当前线程在未获得同步状态时,进入等待状态。因此这个这个也是获取同步状态的方法。这个方法获取同步状态,是以共享式的获取同步状态,因此await() 可以在多个线程中调用。
CountLatchDown的实现也是基于AbstractQueuedSynchronizer 同步器。
1 状态 state
state在CountDownLatch 起到一个计数器的作用,可以表示需要等待线程的数量。
当state==0时,await() 返回,然后继续执行后面的代码。当创建CountDownLatch时,初始化state的数量。
2 countDown 方法
public void countDown() { sync.releaseShared(1); }
每调用一次,同步器就会共享式的释放一个拥有同步状态的线程。state也相应的减一。
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
这个地方就使用了模板设计模式。上面这个方法是在同步器(父类)中定义的。
tryReleaseShared在不同的同步组件中有不同的实现,在CountDownLatch的实现如下:
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; } }
逻辑就是无限循环的尝试将 状态以线程安全的方式减一。
3 await方法
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
共享式的获取同步状态。
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
这个方法也是定义在同步器(父类)中。
tryAcquireShared 共享式的获取同步状态,大于0时,表示获取成功。
if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg);
表示未成功获取同步状态时,进入方法doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { //创建共享节点 并加入队列 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) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
//创建共享节点 并加入队列
final Node node = addWaiter(Node.SHARED); private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
先看下 Node的构造器:
Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; }
也就是Node.SHARED 赋值给了 this.nextWaiter。
介绍下addWaiter的实现逻辑:
当队尾节点不为null时,尝试设置为队尾节点,有可能失败。如果成功直接返回。
失败进入enq方法:
private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
当同步对列中没有任何结点时,
首先看下这个compareAndSetHead(new Node())这个方法,
这个方法一开始看的时候,没有看仔细,把 参数里面的new Node() 当成了前面传过来的node,导致一直不理解这个“操作”。后来突然发现这个是 新建的空节点 。
/** * CAS head field. Used only by enq. */ private final boolean compareAndSetHead(Node update) { return unsafe.compareAndSwapObject(this, headOffset, null, update); }
设置head节点为空节点,tail=head。
后面这个方法compareAndSetTail 就没有问题了,就是以原子的方式追加到同步队列的末尾。
至此 final Node node = addWaiter(Node.SHARED);
执行完毕。
继续看下面的代码,下面是尝试获取同步状态的代码。 先看下,同步状态获取不成功的情况(一开始时,一般情况下,是不会马上就会获得同步状态的。)
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException();初始情况下, waitStatus 为0;private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; 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. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
所以执行的是 compareAndSetWaitStatus
把节点设置为SIGNAL状态,然后这个状态的节点随后会被设置,阻塞
也就是这个方法parkAndCheckInterrupt。
LockSupport.park(this);
然后我们看获取到同步状态的情况:
这种情况,是在countdown 方法进行了足够的执行之后,state==0时。
final Node p = node.predecessor();返回的是前序节点。final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; }如果前序节点为 head节点。尝试获取同步状态。if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) {//获取成功 setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } }
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node); /* * Try to signal next queued node if: * Propagation was indicated by caller, * or was recorded (as h.waitStatus) by a previous operation * (note: this uses sign-check of waitStatus because * PROPAGATE status may transition to SIGNAL.) * and * The next node is waiting in shared mode, * or we don't know, because it appears null * * The conservatism in both of these checks may cause * unnecessary wake-ups, but only when there are multiple * racing acquires/releases, so most need signals now or soon * anyway. */ if (propagate > 0 || h == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); } }/** * Release action for shared mode -- signal successor and ensure * propagation. (Note: For exclusive mode, release just amounts * to calling unparkSuccessor of head if it needs signal.) */ private void doReleaseShared() { /* * Ensure that a release propagates, even if there are other * in-progress acquires/releases. This proceeds in the usual * way of trying to unparkSuccessor of head if it needs * signal. But if it does not, status is set to PROPAGATE to * ensure that upon release, propagation continues. * Additionally, we must loop in case a new node is added * while we are doing this. Also, unlike other uses of * unparkSuccessor, we need to know if CAS to reset status * fails, if so rechecking. */ for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
因为节点的状态是是signal,然后将这个状态设置为0 ,如果成功了,执行unparkSuccessor,: LockSupport.unpark(s.thread); 这个就是唤醒节点的方法,和前面的方法LockSupport.park(this); 是相对应的。
- Java 并发 CountLatchDown
- java并发--并发集合
- Java 并发:并发背景
- Java 并发:并发背景
- java 并发
- Java并发
- Java并发
- Java并发
- java 并发
- Java 并发
- Java 并发
- Java并发
- java 并发
- Java 并发
- java并发
- Java 并发
- java并发
- [Java]并发
- python学习笔记(二)列表(List)操作方法详解
- 构造函数和析构函数
- 虚拟机的基本操作
- 线段树参考资料
- 如何定义一个只能在栈上(对上)建立对象的类
- Java 并发 CountLatchDown
- 如果你不断地去连服务器某个端口
- hibernate 无法将NULL值插入列'id'
- 堆
- Git分支介绍
- 无缘无故,Oralce使用normal模式登录用户失败
- 保证分布式系统数据一致性的6种方案
- 一个数组内有两个相同的数,通过编程找到这两个数
- 4、 LwIP协议栈规范翻译——流程模型