JDK中“常用的”三种同步辅助工具

CountDownLatch、CyclicBarrier和Semaphore说我常用的三种同步工具,其实是我们大部分人常学却不常用的。
本文默认大家都会AQS

CountDownLatch

官方解释:
一种同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
CountDownLatch使用给定的计数进行初始化。wait方法会阻塞,直到当前计数由于countDown方法的调用而达到零,之后所有等待线程都会被释放,任何后续的wait调用都会立即返回。这是一种一次性现象——计数无法重置。如果您需要重置计数的版本,请考虑使用CyclicBarrier。
CountDownLatch是一种通用的同步工具,可用于多种目的。用计数1初始化的CountDownLatch用作简单的开/关锁存器或门:所有调用的线程都在门处等待,直到调用countDown的线程打开它。初始化为N的CountDownLatch可用于使一个线程等待,直到N个线程完成某个操作,或者某个操作已完成N次。
CountDownLatch的一个有用特性是,它不要求调用countDown的线程在继续之前等待计数达到零,它只是防止任何线程继续经过等待,直到所有线程都可以通过为止。

翻译的虽然比较生涩,但是意思都到了。

一、主要方法

1. await方法 => 重写的tryAcquireShared

protected int tryAcquireShared(int acquires) {
  	// 注意 !!当state = 0的时候才能够获取到锁,也就是才能执行
    return (getState() == 0) ? 1 : -1;
}

2. countDown方法 => 重写的tryReleaseShared方法

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
      	// 当state减到0的时候,那么直接return false不会继续减少
        if (c == 0)
            return false;
      	// 注意!!  release时  state是减1,正常在release中state是会加的
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

使用场景

1. 主线程等待多个工作线程完成,然后再继续执行

CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(()->{
    try {
        System.out.println("thread1......");
    } finally {
        countDownLatch.countDown();
    }
}).start();
new Thread(()->{
    try {
        System.out.println("thread2......");
        TimeUnit.SECONDS.sleep(2);
        System.out.println("sleep end...");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        countDownLatch.countDown();
    }
}).start();
countDownLatch.await();
System.out.println("end");

2. 主线程控制多个工作线程一起执行

// 注意这里是1
CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(()->{
    try {
        countDownLatch.await();
        System.out.println("thread1......");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}).start();
new Thread(()->{
    try {
        countDownLatch.await();
        System.out.println("thread2......");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}).start();
TimeUnit.SECONDS.sleep(1);
countDownLatch.countDown();

CyclicBarrier

官方解释:
一种同步辅助工具,允许一组线程全部等待对方到达一个共同的障碍点。CyclicBarrier在涉及固定大小的线程组的程序中很有用,这些线程组偶尔必须相互等待。屏障被称为循环的,因为它可以在等待线程释放后重新使用。
CyclicBarrier支持一个可选的Runnable命令,该命令在参与方中的最后一个线程到达之后,但在任何线程释放之前,每个屏障点运行一次。这种屏障操作有助于在任何一方继续之前更新共享状态。

一、主要方法

1. await方法

// count是可申请总量
// lock是锁
// trip是锁的条件
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    // 获取锁   下面的trip是lock的条件(trip = lock.newCondition())
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;
        if (g.broken)
            throw new BrokenBarrierException();
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
        int index = --count;
      	// 当count为0时
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                  	// 唤醒trip的条件队列
                    breakBarrier();
            }
        }
        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                if (!timed)
                  	// 获取到锁   那么等待
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    // We're about to finish waiting even if we had not
                    // been interrupted, so this interrupt is deemed to
                    // "belong" to subsequent execution.
                    Thread.currentThread().interrupt();
                }
            }
            if (g.broken)
                throw new BrokenBarrierException();
            if (g != generation)
                return index;
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

二、使用场景

当多个线程都达到某一点再开始往下执行

CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
new Thread(()->{
    try {
        cyclicBarrier.await();
        System.out.println("thread1...");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } catch (BrokenBarrierException e) {
        throw new RuntimeException(e);
    }
}).start();
new Thread(()->{
    try {
        cyclicBarrier.await();
        System.out.println("thread2...");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } catch (BrokenBarrierException e) {
        throw new RuntimeException(e);
    }
}).start();

Semaphore

官方解释:
计数信号灯。从概念上讲,信号量维护一组许可。如有必要,每次收购都会阻止,直到有许可证可用,然后再获得许可证。每次发行都会增加一个许可证,可能会释放一个阻止收购的人。然而,没有使用实际的许可对象;Semaphore只是保持对可用数字的计数并相应地进行操作。

一、主要方法

1. acquire => tryAquireShared

// 公平锁的acquire
protected int tryAcquireShared(int acquires) {
    for (;;) {
      	// 已经有等待队列,直接去排队
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

// 非公平锁   直接参与竞争,这里竞争也是和等待队列的头节点进行竞争!!!
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

2. release=>tryReleaseShared

没啥可说的

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;
    }
}

二、使用场景

主要⽤于那些资源有明确访问数量限制的场景,常⽤于限流。

额外: 取令牌一般取1比较合适,如果取多个,可能会发生剩余的共享资源足够某个等待的线程的需求,但是由于不是在等待队列的第一个不能被唤醒,不能执行的情况。
例子:

Semaphore semaphore = new Semaphore(6);
new Thread(()->{
    try {
        semaphore.acquire(3);
        System.out.println("thread1...");
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        semaphore.release(3);
    }
}).start();
new Thread(()->{
    try {
        System.out.println(Thread.currentThread().getName());
        semaphore.acquire(7);
        System.out.println("thread2...");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        semaphore.release(4);
    }
}).start();
TimeUnit.SECONDS.sleep(1);
// thread2给挡住了,即使总资源有6个,但是它永远无法执行。
new Thread(()->{
    try {
        System.out.println(Thread.currentThread().getName());
        semaphore.acquire(5);
        System.out.println("thread3...");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        semaphore.release(4);
    }
}).start();
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倜傥村的少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值