并发容器J.U.C -- AQS同步组件(二)

CountDownLatch、Semaphore、CyclicBarrier、ReentrantLock、Condition、FutureTask

CountDownLatch

**计数器向下减的闭锁 **
同步阻塞类,完成阻塞当前线程的功能,给定了一个计数器,原子操作,计数器不能重置。

1.通过一个计数来保证线程是否需要被阻塞。实现一个或多个线程等待其他线程执行的场景。
CountDownLatch
2.程序需要等待某个条件完成后,才能进行后面的操作(如父任务等待所有子任务都完成的时候,再继续往下进行)。

我们定义一个CountDownLatch,通过给定的计数器为其初始化,该计数器是原子性操作,保证同时只有一个线程去操作该计数器。调用该类await方法的线程会一直处于阻塞状态。其他线程调用countDown方法(每次使计数器-1),当计数器变为0的时候,所有等待的线程才会继续执行。

final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
    final int threadNum = i;
    exec.execute(() -> {
        try {
            test(threadNum);  //需要被等待的线程执行的方法
        } catch (Exception e) {
            log.error("exception", e);
        } finally {
            countDownLatch.countDown();
        }
    });
}
countDownLatch.await();

3.多个线程完成一个任务,但是这个任务只想给它一个指定的时间,超过这个时间(计数器还未清零)就不继续等待了,完成多少算多少。(并不是第一时间毁掉所有线程,而是先让正在执行的线程执行完)。countDownLatch.await(等待时间长度,时间单位 );

使用场景

查询需要等待某个条件完成后才能继续执行后续操作(Ex:并行计算)拆分任务

Semaphore 信号量

监控并发数
保证同一时间的请求量(并发访问控制线程的数目),达到上限会阻塞

信号量在操作系统中是很重要的概念,Java并发库里的Semaphore就可以很轻松的完成类似操作系统信号量的控制。Semaphore可以很容易控制系统中某个资源被同时访问的线程个数。
在数据结构中我们学过链表,链表正常是可以保存无限个节点的,而Semaphore可以实现有限大小的链表

使用场景

  1. 仅能提供有限访问的资源。比如数据库的最大连接数20,而上层的并发数远远大于20,若不做限制,可导致并发异常(无法获取连接)。当Semaphore设置为1时,和单线程很相似。
  2. 并发很高,想要超过允许的并发数之后就抛弃.
/**
 * 1、普通调用
 */
try {
     semaphore.acquire(); // 获取一个许可
     test();//需要并发控制的内容
     semaphore.release(); // 释放一个许可
} catch (Exception e) {
     log.error("exception", e);
}

/**
 * 2、acquire(n),release(n)
 * 同时获取多个许可,同时释放多个许可
 */
 try {
     semaphore.acquire(2);
     test();
     semaphore.release(2);
} catch (Exception e) {
     log.error("exception", e);
}

//tryAcquire()
//tryAcquire(int permits)//permits尝试获取许可的次数
//tryAcquire(long timeout, TimeUnit unit);
//tryAcquire(int permits,long timeout, TimeUnit unit)
/*
 * 3、tryAcquire())//尝试获取一个许可
 * 尝试获取许可,获取不到不执行
 */
 try {
     if (semaphore.tryAcquire()) {
        test(threadNum);
        semaphore.release();
     }
 } catch (Exception e) {
     log.error("exception", e);
}
/*
 * 4、尝试获取许可的时候等待一段时间,获取不到不执行
 * 参数1:等待时间长度  参数2:等待时间单位
 */
try {
     if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) {
        test(threadNum);
        semaphore.release(); 
     }
} catch (Exception e) {
     log.error("exception", e);
}

CyclicBarrier

同步辅助类,运行一组线程等待到一个公共的屏障点,实现多个线程相互等待,所有线程都准备就绪后才继续执行,通过计数器实现的.

当某个线程调用了await()后,就会进入awaiting等待状态,并将计数器-1,直到所有的线程调用await()使计数器为0,线程再同时继续执行。

由于计数器释放之后可以重用(reset方法),所以称之为循环屏障
CyclicBarrier

使用场景

多线程计算数据,最后合并计算结果。
如Excel保存用户的银行流水,每页保存了一个用户近一年的每笔银行流水,现统计用户的日均银行流水,多线程处理每一页里的银行流水。都执行完以后得到每一页的日均银行流水。之后通过CyclicBarrier 的action,利用这些线程的计算结果,计算出整个Excel的日均流水。

//公共线程循环调用方法
private static CyclicBarrier barrier = new CyclicBarrier(5);

public static void main(String[] args) throws Exception {
    ExecutorService executor = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        final int threadNum = i;
        Thread.sleep(1000);
        executor.execute(() -> {
            try {
                race(threadNum);
            } catch (Exception e) {
                log.error("exception", e);
            }
        });
    }
    executor.shutdown();
}

//使用方法1:每个线程都持续等待
private static void race(int threadNum) throws Exception {
    Thread.sleep(1000);
    log.info("{} is ready", threadNum);
    barrier.await();
    log.info("{} continue", threadNum);
}

//使用方法2:每个线程只等待一段时间
private static void race(int threadNum) throws Exception {
    Thread.sleep(1000);
    try {
        barrier.await(2000, TimeUnit.MILLISECONDS);
    } catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
        log.warn("BarrierException", e);
    }
}

//使用方法3:在初始化的时候设置runnable,当线程达到屏障时优先执行runnable
private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
    log.info("callback is running");
});

CyclicBarrier与CountDownLatch的比较

CyclicBarrier CountDownLatch
可重复用 reset() 只能使用一次
多个线程相互等待(内部关系) 一个或n个线程等待其他线程的关系

CyclicBarrier提供方法获取阻塞线程的个数,知道阻塞的线程是否中断

猜你喜欢

转载自blog.csdn.net/eluanshi12/article/details/85259453