Java线程池之栅栏

 通过闭锁来启动一组相关的操作,或者等待一组相关的操作结束。闭锁是一次性对象,一旦进入终止状态,就不能重置。

 栅栏类似于闭锁,它能阻塞到一组线程到某个事件发生。栅栏于闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。栅栏用于实现一些协议,例如几个家庭决定在某个地方集合:”所有人6:00在麦当劳碰头,到了以后要等待其他人,之后再讨论下一步要做的事情。“

CyclicBarrier可以使一定数量的参与方法反复地在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列独立的子问题。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有线程将被释放,而栅栏将被重置以便下次使用。如果对await的调用超时,或者await阻塞的线程被中断,那么栅栏将被认为是打破了,所有阻塞的await调用都将终止并抛出BrokenBarrierException。如果成功通过栅栏那么将为每个线程返回一个唯一的到达索引号,我们可以利用这些索引号来“选举”产生一个领导线程,并在下一波迭代中由该领导线程执行一些特殊化的工作。CyclicBarrier还可以使你将一个栅栏操作传递给构造函数,这是一个Runnable,当成功通过栅栏时会(在一个子线程中)执行它,但在阻塞线程被释放之前是不能执行的。

public static void main(String args[]) throws InterruptedException {
        ExecutorService executors = Executors.newFixedThreadPool(10);
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
            @Override
            public void run() {//释放栅栏时调用,也就是最后一个执行完毕的线程调用
                System.out.println(Thread.currentThread().getName() + "释放所有线程");
            }
        });
        for (int i = 0; i < 10; i++) {
            executors.submit(new CycRunnable(cyclicBarrier));
        }
        System.out.println("执行完毕");
    }

    private static class CycRunnable implements Runnable {

        private CyclicBarrier cyclicBarrier;

        public CycRunnable(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "开始执行:");
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "执行完毕并且等待");
                cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName() + "等待结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

**注意:**如果线程数大于传入给CyclicBarrier 的值,则多余线程会一直等待,如果小于则所有线程会一直等待,CyclicBarrier 也无法释放。

 栅栏还有另外一种形式,那就是Exchanger,它是一种两方栅栏,各方在栅栏位置交换数据。当两方执行不对称的操作时,Exchanger会非常有用,例如当一个线程向缓冲区写入数据,而另外一个线程从缓冲区中读取数据。这些线程可以使用Exchanger来汇合,并将满的缓冲区与空的缓冲区交换。当两个线程通过Exchanger交换对象时,这种交换就把这两个对象安全的交给发布给另一方。
 数据交换的时机取决于应用程序的响应需求。最简单的方案是,当缓冲区被填满时,由填充任务进行交换,当缓冲区为空时,由清空任务进行交换。这样会把需要交换的次数降至最低,但如果新数据的到达率不可预测,那么一些数据的处理过程就会延迟。另外一个方法是,不仅当缓冲区被填满时进行交换,并且当缓冲区被填充到一定程度并保持一定时间后,也进行交换。

<think>嗯,用户问的是如何在Java中使用ThreadPoolExecutor结合CyclicBarrier或Phaser实现栅栏机制。首先,我需要回顾一下这些类的基本概念和用法。 首先,ThreadPoolExecutor是Java中用来管理线程池的核心类,用来执行异步任务。CyclicBarrier和Phaser都是同步辅助类,用于协调多个线程之间的同步点。CyclicBarrier允许一组线程互相等待,直到所有线程都到达某个屏障点,然后才能继续执行。而Phaser则更灵活,可以动态调整参与的线程数,并且支持多阶段的同步。 接下来,我需要考虑如何将这两个同步器与线程池结合使用。用户可能希望在线程池的任务中设置栅栏,确保所有任务在某个阶段同步。比如,多个任务分阶段执行,每个阶段需要所有任务完成后才能进入下一阶段。 首先,CyclicBarrier的使用示例。当使用线程池提交任务时,每个任务需要知道CyclicBarrier的存在,并在适当的地方调用await()方法。需要注意的是,CyclicBarrier的构造需要指定参与的线程数,而线程池中的任务数需要与之匹配,否则可能出现线程一直等待的问题。例如,如果线程池有5个任务,但CyclicBarrier的parties设为5,那么每个任务调用await()时,会等待其他四个任务也到达屏障点。 然后,Phaser的情况类似,但更灵活。Phaser可以动态注册参与者,这对于线程池中的任务数量不确定的情况很有用。每个任务执行时,可以注册到Phaser中,并在适当阶段调用arriveAndAwaitAdvance()方法。Phaser会自动处理阶段的推进,无需显式指定参与者数量,适合动态调整的场景。 但需要注意,线程池中的线程可能被复用,因此在任务中注册到Phaser后,需要确保在任务结束时注销,避免影响后续任务。或者,可以使用Phaser的bulkRegister方法一次性注册所有需要的参与者。 另外,需要考虑异常处理。如果某个任务在等待屏障时被中断或抛出异常,CyclicBarrier会触发BrokenBarrierException,而Phaser则可能有不同的处理方式。需要确保代码能够正确处理这些异常,避免线程一直阻塞。 此外,线程池的大小需要与同步器的参与者数量匹配。例如,使用固定大小的线程池,确保提交的任务数等于CyclicBarrier的parties数,否则可能导致部分线程无法到达屏障,从而造成死锁。 最后,可能需要比较CyclicBarrier和Phaser的适用场景。CyclicBarrier适合已知固定数量的线程同步,而Phaser适合动态调整或分阶段更复杂的同步需求。在示例代码中,需要展示这两种情况,并解释其原理和注意事项。 总结步骤: 1. 创建ThreadPoolExecutor线程池。 2. 创建CyclicBarrier或Phaser实例。 3. 提交任务到线程池,每个任务在需要同步的地方调用await或arriveAndAwaitAdvance。 4. 处理异常和资源释放。 5. 关闭线程池。 需要注意线程池的生命周期管理,确保所有任务执行完毕后正确关闭线程池,避免资源泄漏。同时,示例代码应简洁明了,展示核心用法,并附上原理说明,帮助用户理解两者的区别和适用场景。</think>### Java中ThreadPoolExecutor结合CyclicBarrier/Phaser的实现 #### 一、实现原理 1. **CyclicBarrier** - 通过构造函数指定等待线程数$N$,当$N$个线程调用`await()`时解除阻塞[^2] - 支持可选的栅栏动作(Runnable参数) - 可重复使用`reset()`方法重置计数器[^1] 2. **Phaser** - 动态注册参与者`register()`/`bulkRegister()` - 分阶段控制`arriveAndAwaitAdvance()` - 支持分层结构减少竞争[^1] 3. **ThreadPoolExecutor整合** - 通过`execute()`提交包含同步器的任务 - 确保线程池大小与同步器要求匹配 - 推荐使用`newFixedThreadPool`固定线程数 #### 二、代码示例 ```java // 示例1:CyclicBarrier实现 ExecutorService pool = Executors.newFixedThreadPool(4); CyclicBarrier barrier = new CyclicBarrier(4, () -> System.out.println("所有任务到达栅栏")); IntStream.range(0,4).forEach(i -> pool.execute(() -> { try { System.out.println("任务"+i+"执行第一阶段"); barrier.await(); // 等待所有任务到达 System.out.println("任务"+i+"执行第二阶段"); } catch (Exception e) { e.printStackTrace(); } })); ``` ```java // 示例2:Phaser实现 ExecutorService pool = Executors.newCachedThreadPool(); Phaser phaser = new Phaser(1); // 注册主线程控制阶段 for (int i=0; i<3; i++) { phaser.register(); // 动态注册参与者 pool.execute(() -> { doPhaseWork(phaser); phaser.arriveAndDeregister(); // 完成工作后注销 }); } private static void doPhaseWork(Phaser phaser) { while (!phaser.isTerminated()) { int phase = phaser.getPhase(); System.out.println("阶段"+phase+"开始"); phaser.arriveAndAwaitAdvance(); // 等待所有参与者 System.out.println("阶段"+phase+"结束"); } } ``` #### 三、关键差异 | 特性 | CyclicBarrier | Phaser | |--------------------|-----------------------|---------------------| | 参与者数量 | 固定 | 动态可调 | | 阶段控制 | 单阶段循环 | 多阶段自动推进 | | 异常处理 | 破坏栅栏状态 | 可局部处理异常 | | 适用场景 | 简单分阶段任务 | 复杂工作流任务 | #### 四、注意事项 1. **线程池大小匹配** - `CyclicBarrier`要求线程数与parties严格相等 - 使用`ThreadPoolExecutor.CallerRunsPolicy`避免任务拒绝 2. **生命周期管理** ```java phaser.forceTermination(); // 强制终止Phaser barrier.reset(); // 重置CyclicBarrier ``` 3. **性能优化** - Phaser默认使用自旋锁(SPINS=256) - 超过64个参与者建议使用分层Phaser[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值