并发协调神器CountDownLatch和CyclicBarrier

开发 后端
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)有序性: 重排序引起

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

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来实现多个线程之间的协同工作,确保它们按照预期顺序执行。

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了。

这段代码演示了如何使用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更适用于多个线程在特定位置同步后继续执行。两者都是非常有用的并发控制工具,能够帮助程序员更加灵活地管理多线程任务的执行顺序。

责任编辑:姜华 来源: 今日头条
相关推荐

2014-03-14 10:34:28

JavaJava并发

2019-09-16 09:23:34

高并发编程CountDownLaCyclicBarri

2020-12-03 11:15:21

CyclicBarri

2021-08-26 08:03:30

大数据Zookeeper选举

2021-03-18 00:14:29

JavaCyclicBarri高并发

2021-03-11 00:05:55

Java高并发编程

2020-12-04 19:28:53

CountDownLaPhaserCyclicBarri

2022-09-07 18:32:57

并发编程线程

2021-02-26 13:08:27

Java高并发AQS

2020-12-21 07:54:46

CountDownLa用法源码

2023-11-28 08:01:48

互斥锁共享资源

2022-11-27 08:12:11

RocketMQ源码工具类

2021-07-07 07:09:49

Redisson分布式锁源码

2021-07-30 11:21:39

物联网网络安全IoT

2015-05-28 11:02:55

TI ZigbeePANID通信

2021-04-21 07:53:12

Java限流器管理

2021-09-08 06:51:53

CountDownLa闭锁源码

2021-08-30 19:00:46

静态CompletableCountDownLa

2014-03-17 09:48:23

大数据

2012-08-21 10:54:54

点赞
收藏

51CTO技术栈公众号