Java多线程之CountDownLatch、CyclicBarrier和Semaphore

简介:

转自:http://www.liubey.org/countdownlatch_vs_cyclicbarrier/


概述

CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。

CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。

Semaphore:可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。



举例

1:CountDownLatch

使用场景举例:

体育课时老师拿着秒表测试同学的800米成绩,那需求就是很简单了,老师在起跑处组织大家一起跑的瞬间按下秒表计时开始,然后再终点处等待最后一个学生通过终点后开始汇集学生成绩。

API相关:

1)await(),阻塞等待,直到计数器清零
2)await(int timeout, TimeUnit unit),使线程阻塞,除非被中断或者超过等待的最大时间
如果达到计数器清零,则await返回true,如果等待超过了最大的等待时间,则返回false
3)countDown(),计数器减一,当计数器清零时,await的线程被唤醒,线程继续执行
4)getCount (),获取当前计数器的大小


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TeacherWithStopwatch {
    public static final int NUMBER_OF_STUDENT = 10;
    public static final int NUMBER_OF_TEACHER = 1;
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        CountDownLatch studentSignal = new CountDownLatch(NUMBER_OF_STUDENT);
        CountDownLatch teacherSignal = new CountDownLatch(NUMBER_OF_TEACHER);
        for (int i = 0; i < NUMBER_OF_STUDENT; i++) {
            executor.execute(new Student(i, studentSignal, teacherSignal));
        }
        try {
            System.out.println("各就各位!开跑!");
            teacherSignal.countDown();
            studentSignal.await();
            System.out.println("结果发送到汇报成绩的系统");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}


    import java.util.concurrent.CountDownLatch;
public class Student implements Runnable {
    private int id;
    private CountDownLatch studentSignal;
    private CountDownLatch teacherSignal;
    public Student(int id, CountDownLatch studentSignal,
        CountDownLatch teacherSignal) {
        this.id = id;
        this.studentSignal = studentSignal;
        this.teacherSignal = teacherSignal;
    }
    @Override
    public void run() {
        try {
            teacherSignal.await();
            System.out.println("学生" + id + "起跑...");
            System.out.println("学生" + id + "到达终点。");
            studentSignal.countDown();
            System.out.println("学生" + id + "继续干其他事情");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


2:CyclicBarrier

使用场景举例:

有四个游戏玩家玩游戏,游戏有三个关卡,每个关卡必须要所有玩家都到达后才能允许通关。
其实这个场景里的玩家中如果有玩家A先到了关卡1,他必须等待其他所有玩家都到达关卡1时才能通过。
也就是说线程之间需要互相等待,这和CountDownLatch的应用场景有区别,
CountDownLatch里的线程是到了运行的目标后继续干自己的其他事情,而这里的线程需要等待其他线程后才能继续完成下面的工作。

API相关:

public CyclicBarrier(int parties) 创建一个新的 CyclicBarrier,
它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
public CyclicBarrier(int parties, Runnable barrierAction) 创建一个新的 CyclicBarrier,
它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,
该操作由最后一个进入 barrier 的线程执行。
public int await() throws InterruptedException, BrokenBarrierException 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
public int await(long timeout,TimeUnit unit) throws InterruptedException, BrokenBarrierException,TimeoutException
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
public int getNumberWaiting() 返回当前在屏障处等待的参与者数目。此方法主要用于调试和断言。
public int getParties() 返回要求启动此 barrier 的参与者数目。
public boolean isBroken() 查询此屏障是否处于损坏状态。
public void reset() 将屏障重置为其初始状态。


public class GameBarrier {
    public static final int NUMBER_OF_PLAYERS = 4;
    public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(NUMBER_OF_PLAYERS);
    CyclicBarrier barrier = new CyclicBarrier(NUMBER_OF_PLAYERS, new Runnable() {
        @Override
        public void run() {
            System.out.println("所有玩家通过第一关!");
        }
    });
    for (int i = 0; i < NUMBER_OF_PLAYERS; i++) {
        executor.execute(new Player(i, barrier));
    }
    executor.shutdown();
    }
}

public class Player implements Runnable {
    private CyclicBarrier cyclicBarrier;
    private int id;
    public Player(int id, CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
        this.id = id;
    }
    @Override
    public void run() {
        try {
            System.out.println("玩家" + id + "通过第一关...");
            cyclicBarrier.await();
            System.out.println("玩家" + id + "进入第二关...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
        e.printStackTrace();
        }
    }
}

3:Semaphore

使用场景举例:

一个资源池只能同时五个人使用,那么十个人来轮询使用的情况就是每个人都先申请资源,使用完归还于下一个人使用。

API相关:

构造器中的fairness为true时,Semaphore保证各线程以后进先出(FIFO)的方式获得信号量。如果fairness为false,则不保证这种顺序,允许各线程之间的“讨价还价”。

tryAcquire与release为主要方法


public class Person implements Runnable {
    private SemaphorePool pool;
    private int id;
    public Person(int id, SemaphorePool pool) {
        this.pool = pool;
        this.id = id;
    }
    @Override
    public void run() {
        try {
            pool.applyResource();
            System.out.println("人物" + id + "进入");
            Thread.sleep(1000);
            System.out.println("人物" + id + "离开");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            pool.releaseResource();
        }
    }
}

public class SemaphorePool {
    private Semaphore canExecuteCount = new Semaphore(5, true);
    private static final int TRY_EXECUTE_TIMEOUT = 20;
    public boolean applyResource() {
        boolean canExecute = false;
        try {
            canExecute = canExecuteCount.tryAcquire(1, TRY_EXECUTE_TIMEOUT,
                TimeUnit.SECONDS);
        } catch (InterruptedException e) {}
        return canExecute;
    }
    public void releaseResource() {
        canExecuteCount.release(1);
    }
}


public class TestSemaphore {
    public static final int NUMBER_OF_PERSONS = 10;
    public static final SemaphorePool pool = new SemaphorePool();
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(NUMBER_OF_PERSONS);
        for(int i=0;i<NUMBER_OF_PERSONS;i++) {
            executor.execute(new Person(i, pool));
        }
        executor.shutdown();
    }
}
本文转自 古道卿 51CTO博客,原文链接:http://blog.51cto.com/gudaoqing/1550156


相关文章
|
7月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
335 0
|
8月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
574 5
|
8月前
|
监控 搜索推荐 Java
Java 多线程最新实操技术与应用场景全解析:从基础到进阶
本文深入探讨了Java多线程的现代并发编程技术,涵盖Java 8+新特性,如CompletableFuture异步处理、Stream并行流操作,以及Reactive编程中的Reactor框架。通过具体代码示例,讲解了异步任务组合、并行流优化及响应式编程的核心概念(Flux与Mono)。同时对比了同步、CompletableFuture和Reactor三种实现方式的性能,并总结了最佳实践,帮助开发者构建高效、扩展性强的应用。资源地址:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
480 3
|
10月前
|
Java
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
前言 主播觉得,AQS的原理,就是通过这2个队列的协助,实现核心功能,同步队列(CLH队列)和条件队列(Condition队列)。 同步队列(CLH队列) 作用:管理需要获...
202 18
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
|
9月前
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
157 1
|
9月前
|
Java
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
213 1
|
11月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
496 23
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
10月前
|
数据采集 存储 网络协议
Java HttpClient 多线程爬虫优化方案
Java HttpClient 多线程爬虫优化方案
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
297 1
Java—多线程实现生产消费者

热门文章

最新文章