85、Java并发工具类详解

Java并发工具类详解

在Java编程中,处理并发任务时,我们常常需要使用一些工具来确保线程之间的同步和数据交换。本文将详细介绍几个重要的并发工具类,包括 CountDownLatch CyclicBarrier Exchanger Phaser

1. CountDownLatch

CountDownLatch 是一个强大且易于使用的同步对象,适用于一个线程必须等待一个或多个事件发生的场景。

下面是一个简单的示例,展示了 CountDownLatch 的基本用法:

import java.util.concurrent.CountDownLatch;

class MyThread implements Runnable {
    private CountDownLatch latch;

    MyThread(CountDownLatch cdl) {
        this.latch = cdl;
        new Thread(this).start();
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            latch.countDown();
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch cdl = new CountDownLatch(5);
        new MyThread(cdl);
        cdl.await();
        System.out.println("Main thread resumes");
    }
}

在上述代码中, main 方法中创建了一个初始计数为5的 CountDownLatch 对象 cdl 。然后创建了一个 MyThread 实例,该线程在 run 方法中循环5次,每次调用 countDown() 方法将 cdl 的计数减1。 main 线程调用 await() 方法,会暂停执行,直到 cdl 的计数减为0。

2. CyclicBarrier

在并发编程中,经常会遇到一组两个或多个线程必须在预定的执行点等待,直到该组中的所有线程都到达该点的情况。 CyclicBarrier 类可以帮助我们处理这种情况。

CyclicBarrier 有两个构造函数:
- CyclicBarrier(int numThreads)
- CyclicBarrier(int numThreads, Runnable action)

其中, numThreads 指定了在继续执行之前必须到达屏障的线程数量。在第二个构造函数中, action 指定了一个线程,当所有线程到达屏障时将执行该线程。

以下是使用 CyclicBarrier 的一般步骤:
1. 创建一个 CyclicBarrier 对象,指定要等待的线程数量。
2. 当每个线程到达屏障时,调用该对象的 await() 方法。这将暂停线程的执行,直到所有其他线程也调用 await() 方法。
3. 当指定数量的线程到达屏障时, await() 方法将返回,线程继续执行。如果指定了 action ,则执行该线程。

await() 方法有两种形式:
- int await() throws InterruptedException , BrokenBarrierException
- int await(long wait, TimeUnit tu) throws InterruptedException , BrokenBarrierException , TimeoutException

下面是一个示例代码,展示了 CyclicBarrier 的使用:

import java.util.concurrent.*;

class BarDemo {
    public static void main(String args[]) {
        CyclicBarrier cb = new CyclicBarrier(3, new BarAction());

        System.out.println("Starting");

        new MyThread(cb, "A");
        new MyThread(cb, "B");
        new MyThread(cb, "C");
    }
}

class MyThread implements Runnable {
    CyclicBarrier cbar;
    String name;

    MyThread(CyclicBarrier c, String n) {
        cbar = c;
        name = n;
        new Thread(this).start();
    }

    public void run() {
        System.out.println(name);
        try {
            cbar.await();
        } catch (BrokenBarrierException exc) {
            System.out.println(exc);
        } catch (InterruptedException exc) {
            System.out.println(exc);
        }
    }
}

class BarAction implements Runnable {
    public void run() {
        System.out.println("Barrier Reached!");
    }
}

上述代码中,创建了一个 CyclicBarrier 对象 cb ,指定需要等待3个线程到达屏障。当3个线程都到达屏障时,执行 BarAction 线程。

CyclicBarrier 可以重复使用,每次指定数量的线程调用 await() 方法时,它都会释放等待的线程。

3. Exchanger

Exchanger 是一个非常有趣的同步类,它旨在简化两个线程之间的数据交换。其操作非常简单:它会一直等待,直到两个不同的线程调用其 exchange() 方法。当这种情况发生时,它会交换两个线程提供的数据。

Exchanger 是一个泛型类,声明如下:

Exchanger<V>

其中, V 指定了要交换的数据类型。

Exchanger 只有一个方法 exchange() ,有两种形式:
- V exchange(V objRef) throws InterruptedException
- V exchange(V objRef, long wait, TimeUnit tu) throws InterruptedException , TimeoutException

下面是一个示例代码,展示了 Exchanger 的使用:

import java.util.concurrent.Exchanger;

class ExgrDemo {
    public static void main(String args[]) {
        Exchanger<String> exgr = new Exchanger<String>();

        new UseString(exgr);
        new MakeString(exgr);
    }
}

class MakeString implements Runnable {
    Exchanger<String> ex;
    String str;

    MakeString(Exchanger<String> c) {
        ex = c;
        str = new String();
        new Thread(this).start();
    }

    public void run() {
        char ch = 'A';
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 5; j++) {
                str += ch++;
            }
            try {
                str = ex.exchange(str);
            } catch (InterruptedException exc) {
                System.out.println(exc);
            }
        }
    }
}

class UseString implements Runnable {
    Exchanger<String> ex;
    String str;

    UseString(Exchanger<String> c) {
        ex = c;
        new Thread(this).start();
    }

    public void run() {
        for (int i = 0; i < 3; i++) {
            try {
                str = ex.exchange(new String());
                System.out.println("Got: " + str);
            } catch (InterruptedException exc) {
                System.out.println(exc);
            }
        }
    }
}

在上述代码中,创建了两个线程: MakeString 线程创建一个空字符串,并将其填充数据; UseString 线程创建一个空字符串,与 MakeString 线程交换数据。

4. Phaser

Phaser 是另一个同步类,其主要目的是实现一个或多个活动阶段的线程同步。它有点像 CyclicBarrier ,但支持多个阶段。

Phaser 定义了四个构造函数,这里介绍两个常用的:
- Phaser()
- Phaser(int numParties)

第一个构造函数创建一个注册计数为0的 Phaser 对象,第二个构造函数将注册计数设置为 numParties

以下是使用 Phaser 的一般步骤:
1. 创建一个新的 Phaser 实例。
2. 通过调用 register() 方法或在构造函数中指定参与方的数量,向 Phaser 注册一个或多个参与方。
3. 对于每个注册的参与方,让 Phaser 等待所有注册的参与方完成一个阶段。参与方可以通过调用 Phaser 提供的方法(如 arrive() arriveAndAwaitAdvance() )来发出信号。
4. 当所有参与方都到达后,阶段完成, Phaser 可以进入下一个阶段(如果有)或终止。

Phaser 提供了一些常用方法:
- int register() :注册一个参与方,并返回注册的阶段号。
- int arrive() :表示一个参与方完成了某个任务,返回当前阶段号。
- int arriveAndAwaitAdvance() :表示一个参与方完成了一个阶段,并等待其他参与方完成该阶段,返回下一个阶段号。
- int arriveAndDeregister() :表示一个参与方完成了一个阶段并注销,返回当前阶段号。
- final int getPhase() :获取当前阶段号。

下面是一个示例代码,展示了 Phaser 的使用:

import java.util.concurrent.*;

class PhaserDemo {
    public static void main(String args[]) {
        Phaser phsr = new Phaser(1);
        int curPhase;

        System.out.println("Starting");

        new MyThread(phsr, "A");
        new MyThread(phsr, "B");
        new MyThread(phsr, "C");

        // Wait for all threads to complete phase one
        curPhase = phsr.getPhase();
        phsr.arriveAndAwaitAdvance();
        System.out.println("Phase " + curPhase + " Complete");

        // Wait for all threads to complete phase two
        curPhase = phsr.getPhase();
        phsr.arriveAndAwaitAdvance();
        System.out.println("Phase " + curPhase + " Complete");

        curPhase = phsr.getPhase();
        phsr.arriveAndAwaitAdvance();
        System.out.println("Phase " + curPhase + " Complete");

        // Deregister the main thread
        phsr.arriveAndDeregister();

        if (phsr.isTerminated()) {
            System.out.println("The Phaser is terminated");
        }
    }
}

class MyThread implements Runnable {
    Phaser phsr;
    String name;

    MyThread(Phaser p, String n) {
        phsr = p;
        name = n;
        phsr.register();
        new Thread(this).start();
    }

    public void run() {
        System.out.println("Thread " + name + " Beginning Phase One");
        phsr.arriveAndAwaitAdvance();

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            System.out.println(e);
        }

        System.out.println("Thread " + name + " Beginning Phase Two");
        phsr.arriveAndAwaitAdvance();

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            System.out.println(e);
        }

        System.out.println("Thread " + name + " Beginning Phase Three");
        phsr.arriveAndDeregister();
    }
}

在上述代码中, main 方法创建了一个初始参与方计数为1的 Phaser 对象 phsr 。然后创建了三个 MyThread 实例,每个线程有三个阶段。 main 线程和每个 MyThread 线程通过调用 arriveAndAwaitAdvance() 方法来同步各个阶段的执行。

总结

本文介绍了Java中的几个重要并发工具类,包括 CountDownLatch CyclicBarrier Exchanger Phaser 。这些工具类可以帮助我们更好地处理并发任务,确保线程之间的同步和数据交换。在实际应用中,我们可以根据具体需求选择合适的工具类。

流程图

graph TD;
    A[开始] --> B[创建CountDownLatch];
    B --> C[创建MyThread];
    C --> D[MyThread调用countDown];
    D --> E{CountDownLatch计数是否为0};
    E -- 是 --> F[Main线程恢复执行];
    E -- 否 --> D;
    F --> G[创建CyclicBarrier];
    G --> H[创建多个MyThread];
    H --> I[MyThread调用await];
    I --> J{所有线程是否到达屏障};
    J -- 是 --> K[执行指定线程];
    J -- 否 --> I;
    K --> L[创建Exchanger];
    L --> M[创建MakeString和UseString线程];
    M --> N[线程调用exchange];
    N --> O{两个线程是否都调用exchange};
    O -- 是 --> P[交换数据];
    O -- 否 --> N;
    P --> Q[创建Phaser];
    Q --> R[注册参与方];
    R --> S[参与方执行阶段任务];
    S --> T[参与方调用arrive或arriveAndAwaitAdvance];
    T --> U{所有参与方是否完成阶段};
    U -- 是 --> V[进入下一阶段或终止];
    U -- 否 --> T;

表格

工具类 功能 构造函数 常用方法
CountDownLatch 一个线程等待一个或多个事件发生 CountDownLatch(int count) await() , countDown()
CyclicBarrier 一组线程在预定点等待所有线程到达 CyclicBarrier(int numThreads) , CyclicBarrier(int numThreads, Runnable action) await()
Exchanger 两个线程交换数据 Exchanger<V>() exchange(V objRef)
Phaser 实现一个或多个活动阶段的线程同步 Phaser() , Phaser(int numParties) register() , arrive() , arriveAndAwaitAdvance() , arriveAndDeregister() , getPhase()

Java并发工具类详解(续)

5. 深入理解Phaser的高级用法

前面我们介绍了 Phaser 的基本用法和常用方法,接下来深入探讨它的一些高级特性。

5.1 重写 onAdvance() 方法

Phaser 允许我们重写 onAdvance() 方法,以此来控制当一个阶段完成并进入下一个阶段时的具体行为。 onAdvance() 方法的签名如下:

protected boolean onAdvance(int phase, int regParties)

其中, phase 表示当前阶段的编号, regParties 表示当前注册的参与方数量。当 onAdvance() 方法返回 true 时, Phaser 将终止;返回 false 时, Phaser 将继续进入下一个阶段。

下面是一个示例,展示了如何重写 onAdvance() 方法来控制 Phaser 只执行指定数量的阶段:

import java.util.concurrent.*;

// 扩展Phaser以允许只执行特定数量的阶段
class MyPhaser extends Phaser {
    int numPhases;

    MyPhaser(int parties, int phaseCount) {
        super(parties);
        numPhases = phaseCount - 1;
    }

    // 重写onAdvance()以执行指定数量的阶段
    protected boolean onAdvance(int p, int regParties) {
        // 此println()语句仅用于说明,通常onAdvance()不会显示输出
        System.out.println("Phase " + p + " completed.\n");

        // 如果所有阶段都已完成,返回true
        if (p == numPhases || regParties == 0) return true;

        // 否则,返回false
        return false;
    }
}

class PhaserDemo2 {
    public static void main(String args[]) {
        MyPhaser phsr = new MyPhaser(1, 4);

        System.out.println("Starting\n");

        new MyThread(phsr, "A");
        new MyThread(phsr, "B");
        new MyThread(phsr, "C");

        // 等待指定数量的阶段完成
        while (!phsr.isTerminated()) {
            phsr.arriveAndAwaitAdvance();
        }

        System.out.println("The Phaser is terminated");
    }
}

// 使用Phaser的执行线程
class MyThread implements Runnable {
    Phaser phsr;
    String name;

    MyThread(Phaser p, String n) {
        phsr = p;
        name = n;
        phsr.register();
        new Thread(this).start();
    }

    public void run() {
        while (!phsr.isTerminated()) {
            System.out.println("Thread " + name + " Beginning Phase " + phsr.getPhase());

            phsr.arriveAndAwaitAdvance();

            // 稍作暂停以防止输出混乱,仅用于说明,不是Phaser正常运行所必需的
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                System.out.println(e);
            }
        }
    }
}

在上述代码中, MyPhaser 类继承自 Phaser ,并重写了 onAdvance() 方法。在 main 方法中,创建了一个 MyPhaser 对象,指定要执行4个阶段。当所有阶段完成后, onAdvance() 方法返回 true Phaser 终止。

5.2 Phaser 的其他方法

除了前面介绍的常用方法, Phaser 还提供了一些其他有用的方法:
- int awaitAdvance(int phase) :等待指定阶段的转换。如果传入的阶段编号不等于当前阶段编号,或者 Phaser 已终止,则该方法立即返回。
- int awaitAdvanceInterruptibly(int phase) :可中断的等待指定阶段的转换。
- void bulkRegister(int parties) :一次性注册多个参与方。
- int getRegisteredParties() :获取当前注册的参与方数量。
- int getArrivedParties() :获取已经到达当前阶段的参与方数量。
- int getUnarrivedParties() :获取尚未到达当前阶段的参与方数量。
- void forceTermination() :强制 Phaser 进入终止状态。

6. 并发工具类的应用场景总结

不同的并发工具类适用于不同的应用场景,下面对它们的应用场景进行总结:
- CountDownLatch :适用于一个线程需要等待其他多个线程完成某些任务后才能继续执行的场景。例如,主线程等待多个子线程完成数据加载后再进行后续处理。
- CyclicBarrier :适用于一组线程需要在某个特定点同步的场景。例如,多个线程并行计算,在计算完成后需要同时进行结果汇总。
- Exchanger :适用于两个线程之间需要交换数据的场景。例如,一个线程负责生产数据,另一个线程负责消费数据,通过 Exchanger 可以方便地进行数据交换。
- Phaser :适用于需要同步多个阶段的场景。例如,一个复杂的任务可以分为多个阶段,每个阶段需要多个线程协同完成,使用 Phaser 可以很好地控制每个阶段的同步。

流程图

graph TD;
    A[创建MyPhaser] --> B[注册参与方];
    B --> C[参与方执行阶段任务];
    C --> D[参与方调用arriveAndAwaitAdvance];
    D --> E{所有参与方是否完成阶段};
    E -- 是 --> F{是否到达指定阶段};
    F -- 是 --> G[终止Phaser];
    F -- 否 --> H[进入下一阶段];
    H --> C;
    E -- 否 --> D;

表格

工具类 应用场景 优点 缺点
CountDownLatch 一个线程等待多个线程完成任务 简单易用,实现线程等待逻辑 只能使用一次,不能重置
CyclicBarrier 一组线程在特定点同步 可以重复使用,支持在屏障点执行额外操作 线程数量固定,修改较麻烦
Exchanger 两个线程交换数据 简化数据交换逻辑 只适用于两个线程之间的交换
Phaser 同步多个阶段 支持多个阶段的同步,可灵活控制阶段转换 相对复杂,学习成本较高

总结

本文详细介绍了Java中的几个重要并发工具类,包括 CountDownLatch CyclicBarrier Exchanger Phaser 。通过对这些工具类的学习,我们可以更好地处理并发任务,确保线程之间的同步和数据交换。在实际应用中,我们需要根据具体的需求选择合适的工具类,并合理使用它们的方法和特性。同时,我们还深入探讨了 Phaser 的高级用法,包括重写 onAdvance() 方法和使用其他辅助方法,这些高级特性可以帮助我们更灵活地控制线程的同步。希望本文能对大家在并发编程方面有所帮助。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值