首页 > 编程语言 >Java并发基础:Phaser全面解析!

Java并发基础:Phaser全面解析!

时间:2024-02-01 14:32:38浏览次数:34  
标签:Phaser Java 并发 任务 屏障 线程 阶段 完成

Java并发基础:Phaser全面解析! - 程序员古德

内容概要

Phaser是Java中一个灵活的同步工具,其优点在于支持多阶段的任务拆分与同步,并且能够动态地注册与注销参与者,它提供了丰富的等待与推进机制,使得开发者能够更细粒度地控制线程的协调行为,实现复杂的并行任务处理,相比于其他同步工具,Phaser更加灵活且易于扩展,适用于多种并发场景。

核心概念

在Java中,Phaser是一个灵活的同步工具类,它允许多个线程在一个或多个屏障(barrier points)上进行协调,可以把Phaser想象成一个多线程聚会的组织者,它负责确保所有参与的线程都到达某个阶段后再一起进行下一步。

举一个实际生活中的场景:假设正在开发一个在线多人游戏,比如,团队解谜游戏,在这个游戏中,有几个玩家(线程)需要合作完成一系列任务来通关,每个任务都被划分为几个阶段,而每个阶段都需要所有玩家共同完成某些操作后才能进入下一阶段。

这个场景中,Phaser就可以发挥它的作用,可以把每个阶段看作是一个屏障点,每个玩家线程在完成自己当前阶段的任务后会向Phaser报告,然后等待其他玩家完成,一旦所有玩家都完成了当前阶段的任务,Phaser就会像一个响铃一样,通知所有玩家可以进入下一阶段了。

比如,在解谜游戏的关卡中,四个玩家需要分别找到四个不同的线索,并将这些线索组合起来才能打开通往下一关的大门,每个玩家在找到线索后,都会告知Phaser自己已经完成任务,Phaser会等待所有四个玩家都找到线索后,再通知他们可以将线索组合起来打开大门进入下一关了。

Phaser主要用于解决多个线程分阶段共同完成任务的同步问题,它可以确保一组线程在达到某个屏障点(phase)之前都保持同步,即所有线程都完成了某个阶段的任务后,才能一起进入下一个阶段,这种同步机制尤其适用于需要多个线程协作完成复杂任务的情况,比如在线多人游戏、分布式系统、并行计算等场景。

Phaser在内部维护了一个状态机,用来跟踪和管理每个线程的执行状态以及各个阶段的完成情况,当一个线程完成任务并达到屏障点时,会调用Phaser的相应方法来通知其他线程,然后等待其他线程一起进入下一阶段,在这个过程中,Phaser会管理线程的同步和协作,确保所有线程都能按照预定的顺序完成各自的任务。

此外,Phaser还提供了灵活的注册和注销线程的功能,可以动态地添加或删除参与同步的线程,它还支持中断和超时机制,可以在等待其他线程的过程中被中断或设置超时,增强了对多线程同步的灵活性。

官方文档:https://docx.iamqiang.com/jdk11/api/java.base/java/util/concurrent/Phaser.html

代码案例

下面是一个简单的Java示例代码,演示了如何使用Phaser来同步多个线程,以确保它们分阶段完成任务,如下代码:

import java.util.concurrent.Phaser;  
  
public class PhaserExample {  
  
    public static void main(String[] args) throws InterruptedException {  
        // 创建一个Phaser实例,初始时注册3个线程(不包括主线程)  
        Phaser phaser = new Phaser(3);  
  
        // 创建并启动3个线程  
        for (int i = 0; i < 3; i++) {  
            int threadNum = i + 1; // 为了在输出中区分线程  
            new Thread(() -> {  
                System.out.println("线程" + threadNum + ":已经准备好,等待其他线程。");  
                  
                // 线程在此等待,直到所有线程都到达这个屏障点  
                phaser.arriveAndAwaitAdvance();  
                  
                System.out.println("线程" + threadNum + ":第一阶段任务完成。");  
  
                // 模拟第二阶段的任务  
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                    return;  
                }  
  
                // 再次到达屏障点,等待其他线程  
                phaser.arriveAndAwaitAdvance();  
  
                System.out.println("线程" + threadNum + ":第二阶段任务完成。");  
  
                // 模拟第三阶段的任务  
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                    return;  
                }  
  
                // 最后一次到达屏障点,所有线程都完成后Phaser将自动进入终止状态  
                phaser.arriveAndAwaitAdvance();  
  
                System.out.println("线程" + threadNum + ":第三阶段任务完成,Phaser任务结束。");  
            }).start();  
        }  
  
        // 等待所有线程完成任务  
        // 注意:在实际应用中,可能不希望主线程在这里阻塞,而是去做其他工作  
        // 但为了演示目的,让主线程等待所有工作线程完成  
        phaser.awaitAdvance(phaser.getPhase());  
        System.out.println("所有线程的第一阶段任务完成。");  
  
        phaser.awaitAdvance(phaser.getPhase() + 1);  
        System.out.println("所有线程的第二阶段任务完成。");  
  
        phaser.awaitAdvance(phaser.getPhase() + 1);  
        System.out.println("所有线程的第三阶段任务完成,整个任务结束。");  
    }  
}

在上面代码中,创建了一个Phaser实例并初始注册了3个线程以及主线程,每个线程都执行三个阶段的任务,每个阶段任务之间都通过phaser.arriveAndAwaitAdvance()方法进行同步,每个线程在完成当前阶段的任务后,都会在这个方法上阻塞,直到所有其他线程也完成了它们当前阶段的任务,在所有线程都完成最后一个阶段的任务后,Phaser会自动进入终止状态,此时不会再有线程被阻塞。

上述代码输出如下结果:

主线程也已经准备好,等待其他线程。  
线程x已经准备好,等待其他线程。  
线程y已经准备好,等待其他线程。  
线程z已经准备好,等待其他线程。  
(这里所有线程和主线程都会等待,直到所有参与者都调用了arriveAndAwaitAdvance)  
主线程第一阶段任务完成(实际上主线程可能只是监控或协调其他线程)。  
线程x第一阶段任务完成。  
线程y第一阶段任务完成。  
线程z第一阶段任务完成。  
(所有线程和主线程继续执行,直到它们再次调用arriveAndAwaitAdvance)  
主线程第二阶段任务完成(实际上可能是等待其他线程完成某些任务)。  
线程x第二阶段任务完成。  
线程y第二阶段任务完成。  
线程z第二阶段任务完成。  
(所有线程和主线程继续执行第三阶段任务)  
主线程第三阶段任务完成(实际上可能是进行一些清理工作或者汇总结果)。  
线程x第三阶段任务完成,Phaser任务结束。  
线程y第三阶段任务完成,Phaser任务结束。  
线程z第三阶段任务完成,Phaser任务结束。

核心API

Phaser它允许一组线程互相等待,直到所有线程都到达某个屏障(barrier)点,Phaser非常适合用于多阶段的任务拆分和同步,以下是Phaser中一些重要方法的简要说明:

  1. Phaser(int parties): 构造函数,创建一个新的Phaser实例,并设置注册的线程数(parties),这个数字表示在继续到下一个阶段之前,必须到达屏障的线程数。
  2. Phaser(): 构造函数,创建一个新的Phaser实例,但不设置注册的线程数,这通常用于层次结构的Phaser,其中子Phaser会继承父Phaser的注册线程数。
  3. register(): 增加一个到达屏障所需的线程数,如果调用此方法的线程尚未注册,它也会将自己注册为未到达的线程。
  4. arrive(): 表示当前线程已经到达屏障,并减少未到达的线程数,如果这是最后一个到达的线程,并且已经设置了下一个阶段的屏障,那么这个方法将返回true,否则返回false
  5. arriveAndAwaitAdvance(): 当前线程到达屏障,并等待其他线程也到达,当所有线程都到达后,屏障会自动推进到下一个阶段,然后该方法返回,如果当前Phaser被终止,这个方法会抛出IllegalStateException
  6. awaitAdvance(int phase): 等待直到屏障推进到给定的阶段,如果当前阶段大于或等于给定的阶段,那么此方法将立即返回。
  7. isTerminated(): 检查Phaser是否已经终止,当注册的线程数减少到零,且没有新的线程注册时,Phaser将被终止。
  8. getPhase(): 获取当前屏障的阶段号,每个屏障都有一个唯一的阶段号,初始阶段号为0。
  9. getRegisteredParties(): 获取当前注册的线程数。
  10. getArrivedParties(): 获取已经到达当前屏障的线程数。
  11. getUnarrivedParties(): 获取尚未到达当前屏障的线程数,这实际上是getRegisteredParties()getArrivedParties()之间的差值。
  12. forceTermination(): 强制终止Phaser,即使还有未到达的线程,这会导致所有等待在arriveAndAwaitAdvance()awaitAdvance(int)方法上的线程抛出IllegalStateException
  13. onAdvance(int phase, int registeredParties): 这是一个受保护的方法,可以在子类中覆盖,以便在每个屏障阶段推进时执行自定义操作。
  14. bulkRegister(int parties): 一次性注册多个线程,这通常用于静态已知的线程数,或者当多个任务由同一个线程代表时。

核心总结

Java并发基础:Phaser全面解析! - 程序员古德

Phaser类的目的是允许在并发编程中同步多个线程之间的执行,它具有如下优点,如下:

  1. 更好的可扩展性:Phaser类相对于其他同步工具类(如CyclicBarrier和CountDownLatch)具有更好的可扩展性,因为它支持更多的参与者(即线程)同时进行同步。
  2. 自动注销和清理:当所有参与者都完成执行后,Phaser会自动注销并释放相关资源,这有助于避免内存泄漏和资源浪费。
  3. 灵活的执行模式:Phaser类提供了多种执行模式,如并行、串行和混合模式,这使得在处理并发任务时更加灵活。

它也有不少缺点,如:1、与其他的同步工具类相比,Phaser类的实现相对复杂,因此在某些场景下可能会引入额外的性能开销,并且Phaser类具有一定的使用门槛,使用时深入理解并发编程和Java并发API,这可能会增加学习成本。

关注我,每天学习互联网编程技术 - 程序员古德

END!

标签:Phaser,Java,并发,任务,屏障,线程,阶段,完成
From: https://blog.51cto.com/bytegood/9527070

相关文章

  • 韩顺平Java自学编程误区,评论区的总结
    韩顺平Java自学编程误区,评论区的总结———韩顺平听了两遍,觉得很有收获。总结韩老师的视频内容。总结了10条。需要的大家可以参考。总结(我只总结10个)韩老师自学编程的13个误区:1.不注重基础(例如:oop,网络、操作系统、基本数据结构、算法、常用的设计模式、多线程、高并发基础知识......
  • Java 中的集合
    集合纲要Collection和IteratorListSetMapCollections工具类Comparable与Comparator数组其实也是一个集合。集合实际上就是一个容器,可以容纳其他类型的数据集合是一个容器一个载体,可以一次容纳多个对象在实际开发中,假设连接数据库,数据库中有10条记录,那么假设把......
  • JAVA应用CPU跳点自动DUMP工具 | 京东物流技术团队
    背景在做系统监控时,CPU的使用率是一个关键的指标,它反映了系统的性能稳定性以及是否存在异常情况,能帮助我们了解系统的负载情况。通过监控CPU使用率,可以判断系统是否正常运行或者是否存在性能问题。如果CPU使用率过高,可能表示系统存在资源瓶颈,需要进行优化或升级。CPU监控的难点现有......
  • JAVA应用CPU跳点自动DUMP工具 | 京东物流技术团队
    背景在做系统监控时,CPU的使用率是一个关键的指标,它反映了系统的性能稳定性以及是否存在异常情况,能帮助我们了解系统的负载情况。通过监控CPU使用率,可以判断系统是否正常运行或者是否存在性能问题。如果CPU使用率过高,可能表示系统存在资源瓶颈,需要进行优化或升级。CPU监控的难......
  • 探索五款全球知名的JavaScript混淆加密工具
    ​现在市场上有很多好用的JavaScript混淆加密工具,其中一些比较流行且受欢迎的工具包括:1、UglifyJS(罗马尼亚):UglifyJS是一个非常流行的JavaScript工具库,它可以压缩、混淆、美化和格式化JavaScript代码。使用UglifyJS时,您可以通过调整参数来控制压缩级别并设置混淆选项。网站......
  • java直连mysql操作数据
    连接器importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;/***@author:chenKeFeng*@date:2024/1/3010:21*/publicclassMySQLConnector{p......
  • BigInt:JavaScript 中的任意精度整数
    BigInts 是JavaScript中的一个新的数字基本(primitive)类型,可以用任意精度表示整数。使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。umber 在JavaScript中被表示为双精度浮点数。这意味着它们的精度有限。......
  • 经验之谈——Java包装类
    目录应用场景总结wisdomJava基本类型的包装类使用都很简单,查一下就懂。我这里主要想讨论一下,为什么要用包装类?基本类型就如同人的裸手一样,直接让他去干一些危险的家务是不行的,比如进烤箱拿出盘子。但是手包上了一个隔热手套,那么就可以很轻松的去做这个事了。同理,在Java的编......
  • 每日一道Java面试题:说一说Java中的泛型?
    写在开头今天的每日一道Java面试题聊的是Java中的泛型,泛型在面试的时候偶尔会被提及,频率不是特别高,但在日后的开发工作中,却是是个高频词汇,因此,我们有必要去认真的学习它。泛型的定义什么是泛型?什么是泛型?这是个好问题,JDK5更新时带来了一个新特性-泛型,所谓“泛型”就是类型参......
  • Java中比较两个字符串==和.equals()区别
    ​在Java中,==和.equals()都是用于比较两个字符串是否相等的运算符,==比较的是两个字符串的引用地址,而.equals()比较的是两个字符串的内容。只有当两个字符串变量指向同一个字符串对象时,==和.equals()才会返回相同的结果 参考文档:Java中比较两个字符串==和.equals()区......