CountDownLatch和Semaphore经典协作示例

109 阅读3分钟

CountDownLatch: CountDownLatch (Java Platform SE 8 )

  • 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。CountDownLatch (Java Platform SE 8 )

  • A CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。 这是一个一次性的现象 - 计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier

    A CountDownLatch是一种通用的同步工具,可用于多种用途。 一个CountDownLatch为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用countDown()的线程打开。 一个CountDownLatch初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。

    CountDownLatch一个有用的属性是,它不要求调用countDown线程等待计数到达零之前继续,它只是阻止任何线程通过await ,直到所有线程可以通过。

Semaphore:Semaphore (Java Platform SE 8 )

  • 一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。

    信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。 例如,这是一个使用信号量来控制对一个项目池的访问的类:

       class Pool { private static final int MAX_AVAILABLE = 100; private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); public Object getItem() throws InterruptedException { available.acquire(); return getNextAvailableItem(); } public void putItem(Object x) { if (markAsUnused(x)) available.release(); } // Not a particularly efficient data structure; just for demo protected Object[] items = ... whatever kinds of items being managed protected boolean[] used = new boolean[MAX_AVAILABLE]; protected synchronized Object getNextAvailableItem() { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (!used[i]) { used[i] = true; return items[i]; } } return null; // not reached } protected synchronized boolean markAsUnused(Object item) { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (item == items[i]) { if (used[i]) { used[i] = false; return true; } else return false; } } return false; } } 
    

    在获得项目之前,每个线程必须从信号量获取许可证,以确保某个项目可用。 当线程完成该项目后,它将返回到池中,并将许可证返回到信号量,允许另一个线程获取该项目。 请注意,当调用acquire()时,不会保持同步锁定,因为这将阻止某个项目返回到池中。 信号量封装了限制对池的访问所需的同步,与保持池本身一致性所需的任何同步分开。

    信号量被初始化为一个,并且被使用,使得它只有至多一个允许可用,可以用作互斥锁。 这通常被称为二进制信号量 ,因为它只有两个状态:一个许可证可用,或零个许可证可用。 当以这种方式使用时,二进制信号量具有属性(与许多Lock实现不同),“锁”可以由除所有者之外的线程释放(因为信号量没有所有权概念)。 这在某些专门的上下文中是有用的,例如死锁恢复。

    此类的构造函数可选择接受公平参数。 当设置为false时,此类不会保证线程获取许可的顺序。 特别是, 闯入是允许的,也就是说,一个线程调用acquire()可以提前已经等待线程分配的许可证-在等待线程队列的头部逻辑新的线程将自己。 当公平设置为真时,信号量保证调用acquire方法的线程被选择以按照它们调用这些方法的顺序获得许可(先进先出; FIFO)。 请注意,FIFO排序必须适用于这些方法中的特定内部执行点。 因此,一个线程可以在另一个线程之前调用acquire ,但是在另一个线程之后到达排序点,并且类似地从方法返回。 另请注意, 未定义的tryAcquire方法不符合公平性设置,但将采取任何可用的许可证。

    通常,用于控制资源访问的信号量应该被公平地初始化,以确保线程没有被访问资源。 当使用信号量进行其他类型的同步控制时,非正常排序的吞吐量优势往往超过公平性。

    本课程还提供了方便的方法, 一次acquirerelease多个许可证。 当没有公平地使用这些方法时,请注意增加无限期延期的风险。

代码示例

public class CountdownLatchTest2 {

    // 一次允许通过的次数
    private static Semaphore carDoorSize = new Semaphore(10);

    private static Semaphore downCarDoorSize = new Semaphore(1);

    // 最大载荷
    private static CountDownLatch carCapacity = new CountDownLatch(50);

    private static CountDownLatch downCar = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("============================等待发车============================");
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 50; i++) {
            executorService.execute(() -> {
                getOn();
            });
        }
        carCapacity.await();
        System.out.println("============================车辆已坐满发车!============================");
        long lineTime = ThreadLocalRandom.current().nextLong(5L, 9L);
        Timer timer = goLink();
        TimeUnit.SECONDS.sleep(lineTime);
        timer.cancel();
        System.out.println("============================车辆行驶了" + lineTime + "s到站!============================");
        downCar.countDown();
        executorService.shutdown();
    }

    private static void getOn() {
        try {
            carDoorSize.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "乘客上车了");
        long searchTime = ThreadLocalRandom.current().nextLong(1L, 3L);
        try {
            TimeUnit.SECONDS.sleep(searchTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "用了" + searchTime + "s,找到座位了");
        carDoorSize.release();
        carCapacity.countDown();
        // 下车
        try {
            downCarDoorSize.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            downCar.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long downTime = 0L;
        try {
            downTime = ThreadLocalRandom.current().nextLong(1L, 3L);
            TimeUnit.SECONDS.sleep(downTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "下车,耗时:" + downTime + "s");
        downCarDoorSize.release();
    }

    public static Timer goLink() {
        Timer timer = new Timer();
        timer.schedule(
                new TimerTask() {
                    @Override
                    public void run() {
                        System.out.println("=======================》》》》》》》》》》》》》》》》》》========================");
                    }
                }
                , 100, 100);
        return timer;
    }

}

运行结果

============================等待发车============================
pool-1-thread-1乘客上车了
pool-1-thread-5乘客上车了
pool-1-thread-3乘客上车了
pool-1-thread-2乘客上车了
pool-1-thread-46乘客上车了
pool-1-thread-48乘客上车了
pool-1-thread-6乘客上车了
pool-1-thread-4乘客上车了
pool-1-thread-7乘客上车了
pool-1-thread-10乘客上车了
pool-1-thread-4用了1s,找到座位了
pool-1-thread-1用了1s,找到座位了
pool-1-thread-10用了1s,找到座位了
pool-1-thread-6用了1s,找到座位了
pool-1-thread-2用了1s,找到座位了
pool-1-thread-11乘客上车了
pool-1-thread-12乘客上车了
pool-1-thread-13乘客上车了
pool-1-thread-15乘客上车了
pool-1-thread-14乘客上车了
pool-1-thread-48用了2s,找到座位了
pool-1-thread-3用了2s,找到座位了
.........
.........

============================车辆已坐满发车!============================
=======================》》》》》》》》》》》》》》》》》》========================
=======================》》》》》》》》》》》》》》》》》》========================
=======================》》》》》》》》》》》》》》》》》》========================
=======================》》》》》》》》》》》》》》》》》》========================
=======================》》》》》》》》》》》》》》》》》》========================
=======================》》》》》》》》》》》》》》》》》》========================
============================车辆行驶了7s到站!============================
pool-1-thread-10下车,耗时:2s
pool-1-thread-1下车,耗时:2s
pool-1-thread-6下车,耗时:2s
.......
......
`