服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 并发协调神器CountDownLatch和CyclicBarrier

并发协调神器CountDownLatch和CyclicBarrier

2024-04-02 14:26编码是个技术活 Java教程

从Java的最初版本开始,就可以利用Java来进行多线程编程。正因为Java从最早的版本就支持多线程编程,程序员们才能够利用Java强大的多线程机制来实现并发任务的执行。然而,多线程编程虽然强大,却也带来了一系列潜在的问题和

并发协调神器CountDownLatch和CyclicBarrier

1.引言

从Java的最初版本开始,就可以利用Java来进行多线程编程。正因为Java从最早的版本就支持多线程编程,程序员们才能够利用Java强大的多线程机制来实现并发任务的执行。然而,多线程编程虽然强大,却也带来了一系列潜在的问题和挑战。

假设有一个共享的咖啡机,多个同事在办公室中使用它来冲泡咖啡。这里就存在着典型的多线程问题:

  • 竞态条件(Race Condition):多个同事同时按下冲泡按钮,如果没有适当的控制和同步机制,可能会导致咖啡机出现异常行为,比如一次性冲出过多的咖啡或者没有冲出咖啡。
  • 死锁(Deadlock):例如,如果两个同事分别占用了添加水和放置咖啡粉的步骤,但互相等待对方完成,那么咖啡机就无法继续工作了。
  • 数据同步(Data Synchronization):确保每个同事知道何时该添加水、何时该放置咖啡粉,以及何时可以开始冲泡,从而避免出现混乱或资源争夺的情况。

2.并发引出的问题

并发引发问题的根源归咎于三个方面:

(1)可见性: CPU缓存引起

可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到。

举个简单的例子,看下面这段代码:

//线程1执行的代码
int i = 0;
i = 10;
 
//线程2执行的代码
j = i;

在多线程环境下,如果线程1对变量i进行了修改,但由于CPU缓存的存在,这个修改可能并不会立即被其他线程(比如线程2)所看到。这样,在线程2执行j = i;时,它可能读取到的是旧的i的值,而不是线程1修改后的新值。

(2)原子性: 分时复用引起

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 经典的转账问题:比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。 试想一下,如果这2个操作不具备原子性,会造成什么样的后果。假如从账户A减去1000元之后,操作突然中止。然后又从B取出了500元,取出500元之后,再执行 往账户B加上1000元 的操作。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元。 所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。

(3)有序性: 重排序引起

并发协调神器CountDownLatch和CyclicBarrier

有序性:即程序执行的顺序按照代码的先后顺序执行。举个简单的例子,看下面这段代码:

int i = 0;              
boolean flag = false;
i = 1;                //语句1  
flag = true;          //语句2

上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗? 不一定,为什么呢? 这里可能会发生指令重排序。

3.CountDownLatch和CyclicBarrier如何让多线程步调一致

在多线程编程中,有时我们需要让多个线程协同工作,以便在某个点上保持步调一致。Java中的CountDownLatch和CyclicBarrier就是用来实现这一目的的两个重要工具。

(1)CountDownLatch

实现原理:

CountDownLatch是一个计数器,它允许一个或多个线程等待一组操作完成。其基本思想是,一个线程或多个线程在执行任务时,需要等待其他线程都执行完毕后才能继续执行。CountDownLatch通过一个计数器来实现,当计数器为0时,等待的线程可以继续执行。

并发协调神器CountDownLatch和CyclicBarrier

这段代码演示了使用CountDownLatch来实现多个线程之间的协同工作,确保它们按照预期顺序执行。

import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3); // 初始化计数器为3

        Runnable task = () -> {
            // 执行一些操作
            latch.countDown(); // 操作完成后将计数器减1
        };

        for (int i = 0; i < 3; i++) {
            new Thread(task).start(); // 启动3个线程执行任务
        }

        latch.await(); // 等待计数器变为0
        System.out.println("所有操作已完成,多线程步调一致!");
    }
}

当3个线程都调用了countDown之后,主线程从await返回,并输出"所有操作已完成,多线程步调一致!

(2)CyclicBarrier

实现原理:

字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。

并发协调神器CountDownLatch和CyclicBarrier

这段代码演示了如何使用CyclicBarrier来实现多个线程在一个屏障点进行等待,并在达到该位置后同时继续执行后续操作。CyclicBarrier常用于多个线程协同工作,并且需要在某个点上实现同步,在实际场景中可以用于控制多线程分阶段执行任务的情况。

import java.util.concurrent.CyclicBarrier;

public class Main {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("所有线程已到达栅栏位置,开始执行后续操作!");
        });

        Runnable task = () -> {
            try {
                // 执行一些操作
                barrier.await(); // 等待其他线程到达栅栏位置
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        for (int i = 0; i < 3; i++) {
            new Thread(task).start(); // 启动3个线程执行任务
        }
    }
}

4.总结

CountDownLatch 和 CyclicBarrier 都是 Java 多线程编程中用于控制多个线程协同工作的工具,但它们之间有一些关键的区别,用法的区别如下:CountDownLatch 主要用来解决一个线程等待多个线程的场景,可以类比旅游团团长要等待所有的游客到齐才能去下一个景点;而CyclicBarrier 是一组线程之间互相等待,更像是几个驴友之间不离不弃。

(1)功能不同:

  • CountDownLatch 用于让一个或多个线程等待其他线程完成操作,达到某个条件后再继续执行。
  • CyclicBarrier 则用于让多个线程在指定位置处进行等待,然后同时继续执行。

(2)重用性:

  • CountDownLatch 是一次性的,一旦计数器变为0,就不能再重置使用。
  • CyclicBarrier 可以被重复使用,当所有线程都到达屏障点后,可以选择重置栅栏来进行下一轮等待。

(3)回调操作:

  • CountDownLatch 没有提供回调操作的机制。
  • CyclicBarrier 可以在所有线程到达屏障点后执行指定的合并操作。

(4)适用场景:

  • CountDownLatch 适用于一组线程需要等待另一组线程执行完毕后再执行的情况,或者等待某些条件满足后再继续执行。
  • CyclicBarrier 适用于多个线程需要在特定点同步,并且在这一点上需要同时继续执行后续操作的情况。

总的来说,CountDownLatch的适用场景更倾向于等待其他线程的任务完成,而CyclicBarrier更适用于多个线程在特定位置同步后继续执行。两者都是非常有用的并发控制工具,能够帮助程序员更加灵活地管理多线程任务的执行顺序。

原文地址:https://www.toutiao.com/article/7346037175445750307/

延伸 · 阅读

精彩推荐
  • Java教程Java自定义注解的详解

    Java自定义注解的详解

    这篇文章主要介绍了Java自定义注解的详解的相关资料,Java注解提供了关于代码的一些信息,但并不直接作用于它所注解的代码内容,需要的朋友可以参考下...

    零度anngle3442020-12-12
  • Java教程详解Sentinel 结合 GateWay 限流

    详解Sentinel 结合 GateWay 限流

    Sentinel 不仅仅可以可以作用于服务之间,还可以完美的和服务网关 GateWay 或者 Zuul 一起使用来对网关实现流控。...

    运维开发故事6802021-05-21
  • Java教程Jemalloc优化MySQL和Nginx

    Jemalloc优化MySQL和Nginx

    这篇文章主要介绍了Jemalloc优化MySQL和Nginx的相关资料,需要的朋友可以参考下...

    mrr3742019-06-17
  • Java教程基于java 线程的几种状态(详解)

    基于java 线程的几种状态(详解)

    下面小编就为大家带来一篇基于java 线程的几种状态(详解)。小编觉得挺不错的,现在就想给大家,也给大家做个参考。一起跟随小编过来看看吧...

    alternative6612020-12-31
  • Java教程java启动参数之谜的排查过程

    java启动参数之谜的排查过程

    在日常操作中,相信很多人对Java启动参数存在疑惑,下面这篇文章主要给大家介绍了关于java启动参数之谜的排查过程,文中通过实例代码介绍的非常详细,需要...

    邪笑风9002023-02-17
  • Java教程浅谈Java面向接口编程

    浅谈Java面向接口编程

    本文通过结合接口的本质、面向对象编程与面向接口编程的关系以及一些作者自身的理解,向大家介绍了面向接口编程的一些东西,需要的朋友可以了解下...

    liuyazhuang6952021-01-06
  • Java教程spring中AOP 注解开发示例详解

    spring中AOP 注解开发示例详解

    这篇文章主要介绍了spring中AOP注解开发的相关资料,文中介绍的很详细,需要的朋友可以参考借鉴,下面来一起看看吧。...

    橘子不酸2072020-08-12
  • Java教程Java之Jackson使用案例详解

    Java之Jackson使用案例详解

    这篇文章主要介绍了Java之Jackson使用案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...

    小小张自由—>张有博4252021-10-26