Semaphore和countDownLatch完全解析

Semaphore和countDownLatch差不多,都涉及到共享锁,也涉及到PROPAGATE,所以一起讲,至于ReentrantReadWriteLock以及CyclicBarrier,这个之后再说,如果不了解aqs请查看之前的博文

Semaphore

Semaphore semaphore = new Semaphore(2); //设置state值
semaphore.acquire();
semaphore.release();

初始化的时候设置一个值,如果线程acquire数量超过了这个值,再acquire的线程会阻塞,直到有线程调用release释放,注意,不一定是先前acquire的线程才release,和锁有点不同。

semaphore.acquire()
public void acquire() throws InterruptedException {
  sync.acquireSharedInterruptibly(1);
}
//其中,sync是
public Semaphore(int permits) {
  sync = new NonfairSync(permits);
}

sync.acquireSharedInterruptibly(1)中

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
  if (Thread.interrupted())
    throw new InterruptedException();
  //还是老方法,先try尝试一下,如果没有申请上再说,返回的是remaining,也就是减去现在申请1个还剩下多少,小于0的时候,也就是没有申请上
  if (tryAcquireShared(arg) < 0)
    doAcquireSharedInterruptibly(arg);
}

protected int tryAcquireShared(int acquires) {
  return nonfairTryAcquireShared(acquires);
}
//具体的很简单,这个还看不懂直接自杀
final int nonfairTryAcquireShared(int acquires) {
  for (;;) {
    int available = getState();
    int remaining = available - acquires;
    if (remaining < 0 ||
        compareAndSetState(available, remaining))
      return remaining;
  }
}

如果尝试失败,那么进入doAcquireSharedInterruptibly方法,开始第二次申请或者阻塞

private void doAcquireSharedInterruptibly(int arg)
  throws InterruptedException {
  //放入队列,注意如果队列不存在的话就新建一个,注意如果是新建的话,这个队列的头是个空的,然后指向这个node
  final Node node = addWaiter(Node.SHARED);
  boolean failed = true;
  try {
    for (;;) {
      //再次尝试能不能搞到,不过这一次必须安装链表的顺序来,也就是clh队列那个机制
      final Node p = node.predecessor();
      if (p == head) {
        int r = tryAcquireShared(arg);
        //r>=0说明搞到了资源,此时将此节点设为新头,注意注意	
        //doAcquireSharedInterruptibly方法和独占模式的acquireQueued方法类似,但区别是共享模式在一个节点获取锁后,会通知后续的节点也来一起尝试获取
        //这里还要注意,如果在这里申请成功了,那么必说明有线程调用了semaphore.release()释放了线程
        if (r >= 0) {
          setHeadAndPropagate(node, r);
          p.next = null; // help GC
          failed = false;
          return;
        }
      }
      //这个之前有,在阻塞之前要把前驱节点waitStatus设为-1
      if (shouldParkAfterFailedAcquire(p, node) &&
          parkAndCheckInterrupt())
        throw new InterruptedException();
    }
  } finally {
    if (failed)
      cancelAcquire(node);
  }
}

setHeadAndPropagate

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 either before
         *     or after setHead) 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.
         */
  //注意这里,propagate是在tryAcquireShared(arg)那一瞬间的剩余值,如果大于0,那么有理由相信还有剩余的资源可以通知后面的线程。
  //h为null说明根本没有,这个只是为了防止空指针异常,因为接下来要判断h.waitStatus < 0,
  //第一个h.waitStatus这里涉及到了PROPAGATE的使用(因为之所以会调用setHeadAndPropagate方法,是第一次尝试申请资源的时候没有申请好,在第二次或者第n次申请的时候才会进入这个,那么必有其他线程调用了semaphore.release()修改了state,而release()涉及到对链表后续节点的释放,会修改头节点的waitStatus(也不一定),所以这里第一个h.waitStatus有可能等于0,而如果小于0,则涉及到PROPAGATE,之后再讲)。
  //现在的头如果为空也是防止空指针异常,第二个h.waitStatus < 0说明后续有节点
  if (propagate > 0 || h == null || h.waitStatus < 0 ||
      (h = head) == null || h.waitStatus < 0) {
    Node s = node.next;
    //注意,这里要判断下一个节点为共享锁的节点才会进行释放(在Semaphore中因为都是共享锁的节点所以可以忽略),判断s是否为null一方面也是为了后面判断s是否是共享节点时不会抛出空指针异常;但更重要的原因是因为如果node是CLH队列中的最后一个节点的话,这个时候虽然拿到的s是null,但如果此时有其他的线程在CLH队列中新添加了一个节点后,此处并不能及时感知到这个变化。于是此时也会走进doReleaseShared方法中去处理这种情况(当然,如果没有发生多线程插入节点的时候,多调用一次doReleaseShared方法也是无妨的,在该方法里面会过滤掉这种情况)。同时这里会特殊判断共享节点是因为CLH队列中可能会存在独占节点和共享节点共存的场景出现,也就是ReentrantReadWriteLock读写锁的场景。这里会一直传播唤醒共享节点直到遇到一个独占节点为止,后面的节点不管是独占或共享状态都不会再被唤醒了
    if (s == null || s.isShared())
      doReleaseShared();
  }
}

shouldParkAfterFailedAcquire中会将头前驱节点的waitStatus设置成-1.

未申请上,则挂起。

semaphore.release()
public void release() {
  sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
  //注意,这个虽然是try,但是必返回的是true(或者抛出异常)
  if (tryReleaseShared(arg)) {
    //尝试唤醒挂起的线程
    doReleaseShared();
    return true;
  }
  return false;
}
//没什么好说的
protected final boolean tryReleaseShared(int releases) {
  for (;;) {
    int current = getState();
    int next = current + releases;
    if (next < current) // overflow
      throw new Error("Maximum permit count exceeded");
    if (compareAndSetState(current, next))
      return true;
  }
}

doReleaseShared

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) {
        //将头节点设置为0,不然的话同一时间以后很多线程都执行release方法,都会执行到这,执行unparkSuccessor(h),效率不高。
        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
          continue;            // loop to recheck cases
        //和前面相同
        unparkSuccessor(h);
      }
      //同一时间以后很多线程都执行release方法,只有一个线程成功的执行了unparkSuccessor(h),之后第二快的线程将head节点的WaitStatus改成了Node.PROPAGATE,是为了消除高并发下乐观锁有节点被不唤醒的问题
      else if (ws == 0 &&
               !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
        continue;                // loop on failed CAS
    }
    if (h == head)                   // loop if head changed
      break;
  }
}

PROPAGATE状态

那么,为什么要改呢?举一个例子:

假设现在使用Semaphore semaphore = new Semaphore(2)

https://blog.csdn.net/tomakemyself/article/details/109499230

但是这个也只是对比修改之前和修改之后,没有解释

private void setHeadAndPropagate(Node node, int propagate) {
  Node h = head; // Record old head for check below
  setHead(node);
  if (propagate > 0 || h == null || h.waitStatus < 0 ||
      (h = head) == null || h.waitStatus < 0) {
    Node s = node.next;
    if (s == null || s.isShared())
      doReleaseShared();
  }
}

(h = head) == null || h.waitStatus < 0这一行中拿到现在的head,并且判断 h.waitStatus < 0,这样的话即使不设置Node.PROPAGATE,也会最终刷新。

CountDownLatch

基本用法,注意可以多个线程同时调用latch.await()一同阻塞。

public static void main(String[] args) throws InterruptedException {
  CountDownLatch latch = new CountDownLatch(10);
  ExecutorService exec = Executors.newFixedThreadPool(10);
  for (int i = 0; i < 10; i++) {
    exec.execute(() -> {
      try {
        int millis = new Random().nextInt(10000);
        System.out.println("等待游客上船,耗时:" + millis + "(millis)");
        Thread.sleep(millis);
      } catch (Exception ignore) {
      } finally {
        latch.countDown(); // 完事一个扣减一个名额 }
      }
      ;
    });
    // 等待游客
    latch.await();
    System.out.println("船长急躁了,开船!"); // 关闭线程池
    exec.shutdown();
  }
}
先看countDownLatch.await()吧
public void await() throws InterruptedException {
  sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
  throws InterruptedException {
  if (Thread.interrupted())
    throw new InterruptedException();
  //先尝试一下是否可以获得资源(也就是到0的时候)
  if (tryAcquireShared(arg) < 0)
    //尝试不行,开始之后的再尝试或者阻塞
    doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
  //如果等于0就返回1,不然返回-1
  return (getState() == 0) ? 1 : -1;
}

doAcquireSharedInterruptibly

private void doAcquireSharedInterruptibly(int arg)
  throws InterruptedException {
  //放入队列中,注意可以多个线程同时调用latch.await()一同挂起,所以要放入队列排队
  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);
  }
}

protected int tryAcquireShared(int acquires) {
  return (getState() == 0) ? 1 : -1;
}
再看countDownLatch.countDown()
public void countDown() {
  sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
  //将state减1
  if (tryReleaseShared(arg)) {
    //看看能不能释放锁
    doReleaseShared();
    return true;
  }
  return false;
}
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;
  }
}
//释放链表后面的线程,让他们再去尝试申请资源
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;
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值