12.jdk源码阅读之CyclicBarrier

1.写在前面

CyclicBarrier 是 Java 并发编程中的一个重要工具,它允许一组线程相互等待,直到所有线程都到达某个公共屏障点。在深入阅读CyclicBarrier的代码之前,我先抛出几个问题看看大家有没有思考过:

  1. 什么是 CyclicBarrier?
  2. CyclicBarrier 和 CountDownLatch 的区别是什么?
  3. 如何使用 CyclicBarrier?
  4. CyclicBarrier 的构造方法有哪些?
  5. await() 方法的作用是什么?
  6. CyclicBarrier 的屏障可以被重用吗?
  7. CyclicBarrier 的 BrokenBarrierException 是什么?
  8. 如何处理 CyclicBarrier 中的异常?
  9. CyclicBarrier 的应用场景有哪些?
  10. CyclicBarrier 的 reset() 方法的作用是什么?

2. 从使用说起

以下是一个简单的示例,展示了如何使用 CyclicBarrier 让一组线程在某个点上相互等待。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int numberOfThreads = 3;
        CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
            System.out.println("All threads have reached the barrier. Let's proceed.");
        });

        for (int i = 0; i < numberOfThreads; i++) {
            new Thread(new Task(barrier)).start();
        }
    }

    static class Task implements Runnable {
        private CyclicBarrier barrier;

        public Task(CyclicBarrier barrier) {
            this.barrier = barrier;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
                barrier.await();
                System.out.println(Thread.currentThread().getName() + " has crossed the barrier.");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}


2.1 代码解释

  1. 设置线程数和创建 CyclicBarrier
int numberOfThreads = 3;
CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
    System.out.println("All threads have reached the barrier. Let's proceed.");
});

  • numberOfThreads 定义了参与的线程数量(3个)。
  • 创建一个 CyclicBarrier 实例,指定线程数为 numberOfThreads,并传入一个 Runnable 作为屏障动作,当所有线程到达屏障点时执行该动作。
  1. 启动线程
for (int i = 0; i < numberOfThreads; i++) {
    new Thread(new Task(barrier)).start();
}

使用一个循环启动指定数量的线程,每个线程执行 Task 类的实例。

  1. Task 类
static class Task implements Runnable {
    private CyclicBarrier barrier;

    public Task(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
            barrier.await();
            System.out.println(Thread.currentThread().getName() + " has crossed the barrier.");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

  • Task 实现了 Runnable 接口,表示一个可以在线程中执行的任务。
  • Task 类中有一个 CyclicBarrier 实例,通过构造方法传入。
  • run 方法中:
    • 打印当前线程正在等待屏障。
    • 调用 barrier.await(),使当前线程在屏障点等待,直到所有线程都到达屏障。
    • 打印当前线程已经跨过屏障。
    • 捕获并处理 InterruptedException 和 BrokenBarrierException 异常。
  1. 运行结果
    当代码运行时,输出结果可能类似于以下内容(线程顺序可能不同):
Thread-0 is waiting at the barrier.
Thread-1 is waiting at the barrier.
Thread-2 is waiting at the barrier.
All threads have reached the barrier. Let's proceed.
Thread-2 has crossed the barrier.
Thread-1 has crossed the barrier.
Thread-0 has crossed the barrier.

2.2 关键点

  1. CyclicBarrier 的创建
  • 指定需要等待的线程数量。
  • 提供一个可选的 Runnable 屏障动作,当所有线程到达屏障时执行。
  1. await 方法
  • 使线程在屏障点等待,直到所有线程都调用了 await 方法
  • 当所有线程都到达屏障时,屏障动作执行,所有线程继续执行。
  1. 异常处理
  • InterruptedException:线程在等待时被中断
  • BrokenBarrierException:屏障被破坏,无法继续等待

3. CyclicBarrier 和 CountDownLatch 的区别是什么?

3.1 用途

  • CyclicBarrier:允许一组线程相互等待,直到所有线程都到达某个公共屏障点。它是可重用的。
  • CountDownLatch:用于让一个或多个线程等待,直到其他线程完成某些操作。它是一次性的,计数器在达到零后无法重置。

3.2 重用性

  • CyclicBarrier:可以在所有线程到达屏障点后重置,允许再次使用。
  • CountDownLatch:一旦计数器到达零,就不能重置,必须重新创建一个新的实例。

4. CyclicBarrier 的构造方法有哪些?

CyclicBarrier 有两个主要的构造方法

4.1 CyclicBarrier(int parties)

创建一个新的 CyclicBarrier,当指定数量的线程(parties)都调用 await() 方法时,屏障被触发。

4.2 CyclicBarrier(int parties, Runnable barrierAction)

创建一个新的 CyclicBarrier,当指定数量的线程(parties)都调用 await() 方法时,屏障被触发,并且执行指定的 barrierAction。

5. await() 方法的作用是什么?

await() 方法是 CyclicBarrier 的核心方法,调用该方法的线程会在此等待,直到所有参与的线程都调用了 await() 方法。当所有线程都到达屏障点时,屏障被触发,所有等待的线程继续执行。源码如下:

public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

上面这段代码用于处理线程在屏障点的等待逻辑。它支持可选的超时等待,并处理各种异常情况,如线程中断和超时。

5.1 方法签名

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException, TimeoutException {
  • timed:一个布尔值,表示是否使用超时等待。
  • nanos:超时时间,单位为纳秒。
  • 抛出三种异常:InterruptedException、BrokenBarrierException 和 TimeoutException。

5.2 获取锁

final ReentrantLock lock = this.lock;
lock.lock();

使用 ReentrantLock 进行线程同步,确保对共享资源的访问是线程安全的。

5.3 检查屏障状态

try {
    final Generation g = generation;

    if (g.broken)
        throw new BrokenBarrierException();

    if (Thread.interrupted()) {
        breakBarrier();
        throw new InterruptedException();
    }
  • 获取当前屏障的生成代 generation
  • 如果屏障已经破坏,抛出 BrokenBarrierException
  • 如果当前线程被中断,调用 breakBarrier() 方法破坏屏障,并抛出 InterruptedException

5.4 减少计数并检查是否所有线程已到达屏障

int index = --count;
if (index == 0) {  // tripped
    boolean ranAction = false;
    try {
        final Runnable command = barrierCommand;
        if (command != null)
            command.run();
        ranAction = true;
        nextGeneration();
        return 0;
    } finally {
        if (!ranAction)
            breakBarrier();
    }
}
  • 减少等待线程的计数 count
  • 如果计数为0,表示所有线程都已到达屏障点:
    • 执行屏障动作 barrierCommand(如果有)
    • 调用 nextGeneration() 方法重置屏障以供下一次使用。
    • 返回0,表示当前线程是最后一个到达屏障的线程。

5.5 等待屏障被触发

// loop until tripped, broken, interrupted, or timed out
for (;;) {
    try {
        if (!timed)
            trip.await();
        else if (nanos > 0L)
            nanos = trip.awaitNanos(nanos);
    } catch (InterruptedException ie) {
        if (g == generation && !g.broken) {
            breakBarrier();
            throw ie;
        } else {
            Thread.currentThread().interrupt();
        }
    }

    if (g.broken)
        throw new BrokenBarrierException();

    if (g != generation)
        return index;

    if (timed && nanos <= 0L) {
        breakBarrier();
        throw new TimeoutException();
    }
}

  • 进入一个无限循环,直到屏障被触发、破坏、中断或超时。
  • 如果 timed 为 false,调用 trip.await() 使线程等待。
  • 如果 timed 为 true 且 nanos 大于0,调用 trip.awaitNanos(nanos) 使线程等待指定的纳秒时间。
  • 捕获 InterruptedException 异常
    • 如果当前屏障未破坏且仍在当前代,调用 breakBarrier() 破坏屏障,并重新抛出异常。
    • 否则,将当前线程标记为中断状态。
  • 如果屏障被破坏,抛出 BrokenBarrierException。
  • 如果屏障代已更新,返回当前线程的索引。
  • 如果超时且 nanos 小于等于0,调用 breakBarrier() 破坏屏障,并抛出 TimeoutException。

5.6 释放锁

} finally {
    lock.unlock();
}

在 try 块的 finally 部分释放锁,确保锁总是被释放以避免死锁。

6.CyclicBarrier 的 BrokenBarrierException 是什么?

BrokenBarrierException 是一个异常,当等待在 CyclicBarrier 的线程被中断或屏障被重置时,会抛出这个异常。它表示屏障已经被破坏,无法继续等待。

7. 如何处理 CyclicBarrier 中的异常?

在使用 CyclicBarrier 时,需要处理 InterruptedException 和 BrokenBarrierException。通常在 await() 方法的调用处进行异常处理。

try {
    barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
    e.printStackTrace();
}

8. CyclicBarrier 的应用场景有哪些?

  • 并行计算:将一个大任务拆分成多个子任务,并行执行,所有子任务完成后再汇总结果。
  • 模拟多线程并发:在测试中,模拟多个线程在同一时刻开始执行某些操作。
  • 多阶段任务:将任务分为多个阶段,每个阶段的所有线程完成后再进入下一阶段。

9. CyclicBarrier 的 reset() 方法的作用是什么?

reset() 方法用于将 CyclicBarrier 重置为初始状态。如果有任何线程正在等待屏障,它们会抛出 BrokenBarrierException。这个方法可以在需要重新使用屏障时调用。

系列文章

1.JDK源码阅读之环境搭建

2.JDK源码阅读之目录介绍

3.jdk源码阅读之ArrayList(上)

4.jdk源码阅读之ArrayList(下)

5.jdk源码阅读之HashMap

6.jdk源码阅读之HashMap(下)

7.jdk源码阅读之ConcurrentHashMap(上)

8.jdk源码阅读之ConcurrentHashMap(下)

9.jdk源码阅读之ThreadLocal

10.jdk源码阅读之ReentrantLock

11.jdk源码阅读之CountDownLatch

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

至真源

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值