JUC并发工具十-常用的并发控制工具CountDownLatch,CyclicBarrier和Semaphore

目录

1 CountDownLatch类使用

1.1 CountDownLatch用法

1.2 CountDownLatch原理

1.2.1 构造方法

1.2.2 countDown方法

1.2.3 await方法

1.3 CDL不可重复使用

2 CyclicBarrier类使用

2.1 CyclicBarrier用法

2.2 CyclicBarrier原理

2.2.1 构造方法

2.2.2 await方法

2.3 CyclicBarrier可重复使用

3 Semaphore类使用

3.1 Semaphore用法

3.2 Semaphore原理

3.2.1 构造方法

3.2.2 acquire方法

3.2.3 release方法


针对并发问题,JUC提供了一些工具类,来控制线程并发数。常用的有CountDownLatch、CyclicBarrier和Semaphore
CountDownLatch:数值减掉0开始执行某个步骤
CyclicBarrier:数值加到某个数值开始执行某个步骤
Semaphore:控制并发线程数

1 CountDownLatch类使用

1.1 CountDownLatch用法

假设教室里有5个人,最后一个人走的时候班长关门,要实现这个功能
测试代码

@Test
public void testCountDownLatch() throws InterruptedException {
    int count = 5;
    CountDownLatch countDownLatch = new CountDownLatch(count);
    for (int i = 0; i < count; i ++) {
        int finalI = i;
        new Thread(() -> {
            System.out.println(finalI + "离开");
            // 使用后把count - 1
            countDownLatch.countDown();
        }).start();
    }
    // 这里进行阻塞
    countDownLatch.await();
    System.out.println("关门 ……");
}

输出
0离开
1离开
4离开
3离开
2离开
关门 ……

1.2 CountDownLatch原理

CountDownLatch内部也是维护了一个基于AQS实现的一个线程同步工具不了解AQS的同学可以先看看笔者前面写的

1.2.1 构造方法

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
        setState(count);
    }

是不是很眼熟,count的值实际上就是设置了AQS的state属性

1.2.2 countDown方法

public void countDown() {
    sync.releaseShared(1);
}

releaseShared是AQS的一个模版方法,笔者在讲解AQS的时候已经简单提到过,这里不再赘述,直接看tryReleaseShared

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        // 获取当前state
        int c = getState();
        if (c == 0)
            return false;
        // 把state - 1 并CAS修改
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

阅读源码可知countDown方法实际上就是把资源状态属性state值进行减一

1.2.3 await方法

如果count > 0,await方法则阻塞,直到count减成0

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}


在讲解AQS文章中笔者提到过acquireShared方法,acquireSharedInterruptibly方法类似,只是多了一个线程中断判断,这里不再cp
下面直接看tryAcquireShared方法

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

这里可以看到如果state==0,则返回值是1,>0,所以这里直接返回,不会进入doAcquireShared方法
如果state!=0,则返回值是-1,<0,进入doAcquireShared方法进行等待。
doAcquireShared方法笔者也不是很懂,只知道把当前线程添加到队列里等待运行,有兴趣的同学可以阅读以下源码

1.3 CDL不可重复使用

CDL是一次性的,使用完后就失效了,不能再做并发控制
测试代码

@Test
public void testCountDownLatch() throws InterruptedException {
    int count = 5;
    CountDownLatch countDownLatch = new CountDownLatch(count);
    for (int i = 0; i < count; i ++) {
        int finalI = i;
        new Thread(() -> {
            System.out.println(finalI + "离开");
            // 使用后把count - 1
            countDownLatch.countDown();
        }).start();
    }
    // 这里进行阻塞
    countDownLatch.await();
    System.out.println("关门 ……");
    TimeUnit.SECONDS.sleep(1);
    for (int i = 0; i < count; i ++) {
        int finalI = i;
        new Thread(() -> {
            System.out.println(finalI + "离开");
            // 使用后把count - 1
            countDownLatch.countDown();
        }).start();
    }
    // 这里进行阻塞
    countDownLatch.await();
    System.out.println("关门 ……");
}

输出
0离开
1离开
3离开
2离开
4离开
关门 ……
0离开
1离开
关门 ……
2离开
3离开
4离开

2 CyclicBarrier类使用

2.1 CyclicBarrier用法

CyclicBarrier和CDL类使用差不多,只不过CDL的count是递减的,CyclicBarrier的count是递增的
这里简单实现一个小功能,集齐七颗龙珠召唤神龙

@Test
public void testCyclicBarrier() throws InterruptedException {
    int count = 7;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(count, () -> System.out.println("召唤神龙 ……"));
    for (int i = 0; i < count; i ++) {
        int finalI = i;
        new Thread(() -> {
            System.out.println(finalI + "龙珠");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

输出:
0龙珠
2龙珠
1龙珠
4龙珠
3龙珠
5龙珠
6龙珠
召唤神龙 ……

2.2 CyclicBarrier原理

2.2.1 构造方法

// 第一个参数是屏障大小,第二个参数是达到parties后的执行方法

public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}

2.2.2 await方法

await方法就是一次次判断count是否等于0,如果等于0则执行目标方法,否则循环等待。

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 {
    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();
        }

        // count - 1
        int index = --count;
        if (index == 0) {  // tripped
            // 如果-1后=0则运行目标方法barrierCommand
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                // 执行完目标方法后重置屏障,也是因为有这个方法,CyclicBarrier是可以复用的
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    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();
    }
}

2.3 CyclicBarrier可重复使用

前面分析到执行完目标方法后调用nextGeneration方法重置屏障,所以是可以复用的
测试代码

@Test
public void testCyclicBarrier() throws InterruptedException {
    int count = 7;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(count, () -> System.out.println("召唤神龙 ……"));
    for (int i = 0; i < count; i ++) {
        int finalI = i;
        new Thread(() -> {
            System.out.println(finalI + "龙珠");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
    }

    TimeUnit.SECONDS.sleep(1);
    System.out.println("休息一年");

    for (int i = 0; i < count; i ++) {
        int finalI = i;
        new Thread(() -> {
            System.out.println(finalI + "龙珠");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

输出
1龙珠
0龙珠
3龙珠
2龙珠
4龙珠
5龙珠
6龙珠
召唤神龙 ……
休息一年
0龙珠
1龙珠
2龙珠
3龙珠
4龙珠
5龙珠
6龙珠
召唤神龙 ……

3 Semaphore类使用

Semaphore可以保证获取某一个资源的最大并发数不能超过某个值,否则阻塞等待
7辆车,3个车位,只有车位中的车离开了,其他车才能继续使用。那么我们就可以通过Semaphore来实现

3.1 Semaphore用法

@Test
public void testSemaphore() throws InterruptedException {
    int count = 3;
    Semaphore semaphore = new Semaphore(count);
    for (int i = 0; i < 7; i ++) {
        new Thread(() -> {
            try {
                semaphore.acquire();
                System.out.println(DateUtil.datetimeToString(new Date()) + ":" + Thread.currentThread().getName() + "抢到车位");
                TimeUnit.SECONDS.sleep(3);
                System.out.println(DateUtil.datetimeToString(new Date()) + ":" + Thread.currentThread().getName() + "释放车位");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                semaphore.release();
            }
        }).start();
    }
    TimeUnit.MINUTES.sleep(1);
}


输出
2020-06-25 18:46:48:Thread-1抢到车位
2020-06-25 18:46:48:Thread-2抢到车位
2020-06-25 18:46:48:Thread-0抢到车位
2020-06-25 18:46:51:Thread-1释放车位
2020-06-25 18:46:51:Thread-0释放车位
2020-06-25 18:46:51:Thread-2释放车位
2020-06-25 18:46:51:Thread-3抢到车位
2020-06-25 18:46:51:Thread-5抢到车位
2020-06-25 18:46:51:Thread-4抢到车位
2020-06-25 18:46:54:Thread-3释放车位
2020-06-25 18:46:54:Thread-5释放车位
2020-06-25 18:46:54:Thread-6抢到车位
2020-06-25 18:46:54:Thread-4释放车位
2020-06-25 18:46:57:Thread-6释放车位

3.2 Semaphore原理

3.2.1 构造方法

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

看到这里我想不用我继续往下追代码大家也能猜出来构造方法干啥的了,没错,就是设置AQS的state的值

NonfairSync(int permits) {
    super(permits);
}
Sync(int permits) {
    setState(permits);
}

3.2.2 acquire方法

acquire方法很简单,就是把可用资源数(state变量)-1。如果剩余资源数>=0则直接运行,否则进入线程等待队列

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

不废话,老知识,直接看tryAcquireShared方法在Semaphore中的实现

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

3.2.3 release方法

release方法很简单,就是把可用资源数(state变量)+1

public void release() {
    sync.releaseShared(1);
}

tryReleaseShared在Semaphore的实现

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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值