并发工具类(二):CyclicBarrier
1、CyclicBarrier 介绍
从字面上看 CyclicBarrier 就是 一个循环屏障,它也是一个同步助手工具,它允许多个线程
在执行完相应的操作后彼此等待共同到达一个屏障点。
CyclicBarrier可以被循环使用,当屏障点值变为0之后,可以在接下来的的使用中重置屏障点
值,而无需重新定义一个CyclicBarrier。
Cyclic循环:所有线程释放后,屏障点的数值可以被重置
Barrier屏障:让一个或多个线程到达一个屏障点,会被阻塞;屏障点会有一个数值,当每有一
个线程到达屏障点时,屏障点数值就会减1操作,并且线程阻塞在屏障点,当屏
障点数值变为0时,屏障就会打开,唤醒所有阻塞在屏障点的线程。
在释放屏障点之后,可以先执行一个任务,然后让唤醒阻塞的线程继续执行后
续任务。
CyclicBarrier是一种同步机制,允许一组线程之间互相等待,现成达到屏障点其实是基于
await方法在屏障点阻塞;等待所有线程到达屏障点后再统一唤醒
CyclicBarrier 并不是基于AQS来实现的,其是基于ReentrantLock锁的机制来实现对屏障点的
“减减” 操作以及线程的挂起。
2、CyclicBarrier核心属性&构造方法
public class CyclicBarrier {/*** 内部类*/private static class Generation {//该类用来标记是否被中断过//用来表示阻塞时当前party有没有被强制中断boolean broken = false;//某个线程由于执行了await()方法进入了阻塞状态,若该线程被执行了中断操作,那么 broken 得值就会变为true}/** 保证操作屏障值原子性的锁 */private final ReentrantLock lock = new ReentrantLock();/*** 用于阻塞线程的条件变量:若有未到party的线程,那么等待该条件变量上* 基于当前的Condition 实现线程的挂起和唤醒* */private final Condition trip = lock.newCondition();/*** 屏障数值,与count初始值一致* todo 注意:不会对 parties 进行操作,因为 parties 是final修饰,初始化后不能修改* */private final int parties;//计数器得值/** 当屏障数值count到达0时,优先执行当前任务,然后再会唤醒所有等待的线程执行后续任务* */private final Runnable barrierCommand;/*** 表示当前party是否被中断过* */private Generation generation = new Generation();/**** 屏障数值,初始值与 parties 相等,当每有一个线程到达屏障点时,就会执行count--操作*/private int count;//构造方法/*** * @param parties 屏障点数值* * @param barrierAction 当屏障点数值达到0时,优先执行该 barrierAction 任务* 若barrierAction 为null,则直接执行唤醒的线程* */public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;this.count = parties;//到达屏障点后优先执行的任务this.barrierCommand = barrierAction;}public CyclicBarrier(int parties) {this(parties, null);}
}
3、CyclicBarrier应用场景及示例代码
3.1、将一个任务分成若干个并行的子任务,当所有的子任务全部执行结束后,再继续执行后边的
工作。
从这一点上看,CyclicBarrier 功能与CountDownLatch 的功能差不多,但他们运行方式上却
有很大区别;在 CyclicBarrier 中,每个子任务完成后,子线程调用 CyclicBarrier的await方法
使当前子线程进入阻塞状态,直到其他所有子线都完成了任务后,他们才能退出阻塞;
注意:这里CyclicBarrier并没有干预主线程的运行,所以主线程的 “运行/阻塞” 需要我们来
手动干预。所以 CyclicBarrier 更像是把“任务分片”而不是计数器,当每个分片任务
完成后都会阻塞在“屏障点”,
把前边CountDwonLatch 的示例使用 CyclicBarrier 来实现,比较 CountDwonLatch 与
CyclicBarrier 在相同场景下使用的不同,示例代码如下:
public class CylicBarrierExample1 {public static void main(String[] args) {//先获取商品编号列表int[] products = getProductsByCategoryID();//使用Stream 流,将商品编号列表中的每个商品转换为 ProductPriceList<ProductPrice> list = Arrays.stream(products).mapToObj(ProductPrice::new).collect(Collectors.toList());//定义 CyclicBarrier ,并设置子任务数CyclicBarrier barrier = new CyclicBarrier(list.size());//存放线程任务的集合final List<Thread> threads = new ArrayList<>();list.forEach(pp -> {//对每个商品都创建一个子任务来计算Thread thread = new Thread(() -> {System.out.println(pp.getProdID()+" -> start calculate price.");try {//模拟业务逻辑耗时TimeUnit.SECONDS.sleep(current().nextInt(10));if(pp.prodID %2 == 0){pp.setPrice(pp.prodID*0.9D);}else {pp.setPrice(pp.prodID*0.71D);}System.out.println(pp.getProdID()+" -> price calculate");} catch (InterruptedException e) {e.printStackTrace();}finally {try {//当前子任务线程进入阻塞状态,在这里等待所有的子任务线程都执行到共同的屏障点 barrier pointbarrier.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}});threads.add(thread);thread.start();});//遍历所有的子任务线程,让主线程等待所有的子任务线程结束threads.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println("**************************************");System.out.println("All of price calculate finished!");list.forEach(System.out::println);}//获取商品编号列表private static int[] getProductsByCategoryID(){//商品列表编号为从1,10的数字return IntStream.rangeClosed(1,10).toArray();}//定义商品类,有2个成员变量:商品编号和商品价格private static class ProductPrice{private final int prodID;//商品编号private double price;//商品价格public ProductPrice(int prodID){this(prodID,-1);}public ProductPrice(int prodID,double price){this.prodID = prodID;this.price = price;}public int getProdID(){return this.prodID;}public void setPrice(double price){this.price = price;}@Overridepublic String toString() {return "ProductPrice{" +"prodID=" + prodID +", price=" + price +'}';}}
}
上边这段代码,有个需要优化的地方,即:既然 CyclicBarrier 中所有线程都会阻塞在屏
障点,所有任务都达到屏障点时才会往下执行,那么我们可以把主线程也作为一个任务线程
,即在定义 CyclicBarrier 屏障点数值时,在原有的数值上加1,然后在主线程中执行
CyclicBarrier的await方法,这样就不用让主线程等待每个子线程执行完成了
优化代码如下:
public static void main(String[] args) {//先获取商品编号列表int[] products = getProductsByCategoryID();//使用Stream 流,将商品编号列表中的每个商品转换为 ProductPriceList<ProductPrice> list = Arrays.stream(products).mapToObj(ProductPrice::new).collect(Collectors.toList());//定义 CyclicBarrier ,并设置子任务数CyclicBarrier barrier = new CyclicBarrier(list.size()+1);//存放线程任务的集合final List<Thread> threads = new ArrayList<>();list.forEach(pp -> {//对每个商品都创建一个子任务来计算Thread thread = new Thread(() -> {System.out.println(pp.getProdID()+" -> start calculate price.");try {//模拟业务逻辑耗时TimeUnit.SECONDS.sleep(current().nextInt(10));if(pp.prodID %2 == 0){pp.setPrice(pp.prodID*0.9D);}else {pp.setPrice(pp.prodID*0.71D);}System.out.println(pp.getProdID()+" -> price calculate");} catch (InterruptedException e) {e.printStackTrace();}finally {try {//当前子任务线程进入阻塞状态,在这里等待所有的子任务线程都执行到共同的屏障点 barrier pointbarrier.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}});threads.add(thread);thread.start();});//主线程也阻塞在屏障点barrier.await();System.out.println("**************************************");System.out.println("All of price calculate finished!");list.forEach(System.out::println);}
3.2、CyclicBarrier 循环使用
使用 CyclicBarrier 模拟旅游时导游清点人数的场景
大家报团旅游时,为了安全和避免掉队的,每次登上大巴,大巴启动前,导游都会清点人数
;在到达一个景点后,游客下车后,导游也会重复清点人数,保证所有的人都下来了后,才
会通知大巴师傅去停车场停车,下边写个demo简单模拟下这个场景,
/*** CylicBarrier 的循环使用*/
public class CylicBarrierExample2 {public static void main(String[] args) throws BrokenBarrierException, InterruptedException {//定义 CyclicBarrierfinal CyclicBarrier barrier = new CyclicBarrier(11);//创建10个线程for(int i=0;i<10;i++){//定义游客子线程,传入游客编号和 barriernew Thread(new Tourist(i,barrier)).start();}//主线程也进入阻塞,等待所有游客都上车barrier.await();System.out.println("Tour Guilder: all of Tourist get on the bus");//主线程进入阻塞,所有游客都下车barrier.await();System.out.println("Tour Guilder: all of Tourist get OFF the bus");}//定义游客线程private static class Tourist implements Runnable{private final int touristID;private final CyclicBarrier barrier;private Tourist(int touristID,CyclicBarrier barrier){this.touristID = touristID;this.barrier = barrier;}@Overridepublic void run() {System.out.printf("Tourist: %d by bus\n",touristID);//上车耗时this.spendSeveralSeconds();//上车后等待其他同伴this.waitAndPrint("Tourist: %d Get on the bus, and wait other people");//todo 注意:所有线程到达屏障点后,最后一个到达屏障点的线程会重置CyclicBarrier// 所以这里不需要手动调用reset()重置方法//下车耗时this.spendSeveralSeconds();//下车后等待其他同步全部下车this.waitAndPrint("Tourist: %d Get OFF the bus, and wait other people OFF");}//模拟乘客上车耗时private void spendSeveralSeconds(){try {TimeUnit.SECONDS.sleep(current().nextInt(10));} catch (InterruptedException e) {e.printStackTrace();}}//模拟上车后等待其他同伴private void waitAndPrint(String msg){System.out.printf(msg,touristID);System.out.println();try {//所有线程到达屏障点后,最后一个到达屏障点的线程会重置CyclicBarrierbarrier.await();} catch (InterruptedException |BrokenBarrierException e) {e.printStackTrace();}}}
}
4、CyclicBarrier 常用方法解析
在 CyclicBarrier 中常用方法就2个,即:await 和带超时时间的await ,但真正执行业务
的方法其实只有 doawait 一个方法,如下图所示:
4.1、dowait(boolean timed, long nanos) 方法
dowait 方法是CyclicBarrier 的核心方法,该方法功能是先将 CyclicBarrier 计数器count减1,
然后判断减1后的count是否等于0,若等于0,则唤醒所有阻塞在屏障点的线程,并重置
CyclicBarrier;若减1后的count不等于0,则当前线程被阻塞,直到被其他线程唤醒或过了
超时时间(有超时时间的情况)
dowait 代码如下:
private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException,TimeoutException {final ReentrantLock lock = this.lock;lock.lock();try {//获取 Generation 对象的引用final Generation g = generation;//判断是否有现成中断if (g.broken) //表示当前party已经被中断throw new BrokenBarrierException();//有中断的线程混入其中,则干掉其他所有的线程重新开始if (Thread.interrupted()) {//判断当前执行线程是否被中断,若被中断则先调用 breakBarrier()方法,再抛出异常breakBarrier();throw new InterruptedException();}int index = --count;//计数器减1,对屏障点数据做--操作if (index == 0) { // tripped 当 count 为0 得时候,表示是最后一个线程,负责唤醒所有阻塞在条件变量上的线程,然后回调barrierCommandboolean ranAction = false;try {final Runnable command = barrierCommand;//当前任务线程//优先执行 barrierCommand 任务if (command != null)command.run();ranAction = true;//生成新的 Generation,并且直接返回nextGeneration();//进入下一个party,这时屏障值被重置了,等价与调用了reset()方法return 0;} finally {//如果 barrierCommand 方法发生了异常,则设置 broKen标志位if (!ranAction)//中断当前任务线程breakBarrier();}}// loop until tripped, broken, interrupted, or timed outfor (;;) {//循环等待最后一个参与party的线程,或者被中断、等待超时try {if (!timed) //表示调用的是非超时时间的await方法,则这里也是调用Condition的不带超时时间的awaittrip.await();else if (nanos > 0L)//表示调用的是带超时时间的await方法nanos = trip.awaitNanos(nanos);} catch (InterruptedException ie) {//执行到这,说明线程被中断了//g == generation:查看 generation 是否被重置//若 generation 没有被重置,且没有现成被中断,则调用 breakBarrier 方法执行线程中断后的操作if (g == generation && ! g.broken) {breakBarrier();throw ie;} else {//表示 generation 已经被重置或者 有线程已经被中断,则表示本次CyclicBarrier已经作废,则中断当前线程// 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();//查看当前线程是否是因为 generation 重置而被唤醒(即被reset),若是则直接返回index 数值//或者任务正常完成也会被重置if (g != generation)return index;//判断是否是因为到达超时时间被唤醒,若是则中断当前任务if (timed && nanos <= 0L) {//中断当前任务breakBarrier();throw new TimeoutException();}}} finally {lock.unlock();}}private void nextGeneration() {// signal completion of last generation 唤醒屏障点阻塞中的所有线程trip.signalAll();// set up next generation 修改 count 的值使其等于构造 CyclicBarrier 时传入的parties 值count = parties;generation = new Generation();//创建新的Generation,即生成下一代party}private void breakBarrier() {generation.broken = true; //设置为中断状态count = parties;//将计数器设置为构建 CyclicBarrer 时传入得值,即重置屏障点数值counttrip.signalAll();//唤醒其他所有等待的线程}
4.2、reset() 方法
reset() 方法功能是重置CyclicBarrier
public void reset() {final ReentrantLock lock = this.lock;lock.lock();try {//干掉当前所有的线程breakBarrier(); // break the current generation//生成下一代partynextGeneration(); // start a new generation} finally {lock.unlock();}}
4.3、getNumberWaiting() 方法
该方法功能是返回正在阻塞在屏障点的线程数
public int getNumberWaiting() {final ReentrantLock lock = this.lock;lock.lock();try {return parties - count;} finally {lock.unlock();}}
相关文章:

并发工具类(二):CyclicBarrier
1、CyclicBarrier 介绍 从字面上看 CyclicBarrier 就是 一个循环屏障,它也是一个同步助手工具,它允许多个线程 在执行完相应的操作后彼此等待共同到达一个屏障点。 CyclicBarrier可以被循环使用,当屏障点值变为0之后,可以在接下来…...
Spring Cloud全解析:负载均衡之Ribbon简介
Ribbon简介 Ribbon是一种客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起,提供了一系列完善的配置如连接超时、重试等,Ribbon会自动的帮助基于某种规则(如简单轮询、随机连接等)去连接那些机器,也可以自定义的负载均衡…...
Kettle安装与使用指南
1. 介绍 什么是Kettle? Kettle,全称Pentaho Data Integration (PDI),是Pentaho BI套件的一部分。它提供了一个可视化的ETL工具,允许用户通过图形界面设计复杂的数据集成流程。Kettle支持多种数据源,包括关系型数据库…...

教育行业解决方案:智能PPT在教育行业的创新应用
在信息化时代,教育行业面临着巨大的变革。随着人工智能技术的不断发展,传统教学方式正在被重新定义。彩漩科技作为 AI 技术的先行者,推出了歌者 PPT &彩漩 PPT,为教师、学生和家长提供了一种全新的教育体验,实现了…...

Matlab程序练习
Part1 1.求 [100,999] 之间能被 21整除的数的个数。 程序: 主文件:main.m clear; start_num 100; end_num 999; div_num 21; res div(start_num,end_num,div_num); fprintf("[%d,%d]之间能被%d整除的数的个数为%d个\n",start_num,end_…...

cesium可不可以改变影像底图颜色,如何给地球底图影像添加一层滤镜蒙版?
废话:你的球是不是很丑?是不是没有科技感?是不是没有好看的影像? 因果: 因:客户问,底图可不可以改变颜色,想让球更漂亮一些。 答:可以改变影像饱和度,透明度…...

MyBatis-MappedStatement什么时候生成?QueryWrapper如何做到动态生成了SQL?
通过XML配置的MappedStatement 这部分MappedStatement主要是由MybatisXMLMapperBuilder进行解析,核心逻辑如下: 通过注解配置的MappedStatement 核心逻辑就在这个里面了: 继承BaseMapper的MappedStatement 我们看看这个类,里…...
Netty系列-2 NioServerSocketChannel和NioSocketChannel介绍
背景 本文介绍Netty的通道组件NioServerSocketChannel和NioSocketChannel,从源码的角度介绍其实现原理。 1.NioServerSocketChannel Netty本质是对NIO的封装和增强,因此Netty框架中必然包含了对于ServerSocketChannel的构建、配置以及向选择器注册&am…...

智能客服的四大优势,提升企业服务效率
在这个信息化快速发展的时代,客户服务的重要性越来越凸显。传统的客服方式已经无法满足企业日益增长的服务需求,于是智能客服服务应运而生。智能客服服务不仅改变了企业与客户的互动方式,还提高了服务效率和客户满意度。本文将深入探讨智能客…...
AutoGPT开源项目解读
AutoGPT开源项目解读 (qq.com) AutoGPT旨在创建一个自动化的自我改进系统,能够自主执行和学习各种任务 项目基本信息 首先阅读项目的README.md,下述代理和智能体两个名词可互换 项目简介:一个创建和运行智能体的工具,这些智能体…...

Linux离线安装fontconfig
Linux离线下载yum包,安装字体库 一、下载安装包 以CentOS Linux release 7.9.2009下载fontconfig的rpm包的为例 http://mirror.centos.org/centos/7/按提示跳转历史库 找到对应版本的centos https://vault.centos.org/7.9.2009/os/x86_64/Packages/在Packages目…...

海山数据库(He3DB)+AI:(一)神经网络基础
文章目录 1 引言2 基本结构2.1 神经元2.2 模型结构 3 训练过程3.1 损失函数3.2 反向传播3.3 基于梯度的优化算法 4 总结 1 引言 神经网络可以被视为一个万能的拟合器,通过深层的隐藏层实现输入数据到输出结果的映射。神经网络的思想源于对大脑的模拟,在…...
CSS中选择器有哪些?(史上最全选择器)
CSS选择器是用来选择和应用样式到HTML元素上的工具。以下是所有主要的CSS选择器的详细分类和描述: 1. 基本选择器 通配符选择器 (*):选择所有元素。例如,* { color: red; } 会将所有元素的文字颜色设置为红色。元素选择器:选择指…...

本地部署 AI 智能体,Dify 搭建保姆级教程(下):知识库 RAG + API 调用,我捏了一个红楼解读大师
话接上篇: 本地部署 AI 智能体,Dify 搭建保姆级教程(上):工作流 Agent,把 AI 接入个人微信 相信大家已经在本地搭建好 Dify 了。 今日分享,继续介绍 Dify 的另外两项重要功能: 知…...
HarmonyOS应用开发者高级认证,Next版本发布后最新题库 - 答案纯享版
这篇文章是高级题库答案纯享版,只有需要选择的选项。如果需要查看所有选项,可以点击下方链接跳转。以考代学,还是推荐点击下方链接,查看完整的题库,边看边学习鸿蒙应用开发。此题库已更新完毕,笔者将不继续…...
基于PHP的文件包含介绍
引言:在实际开发过程中,经常会遇到部分模块功能需要重复使用的情况,比如数据库的增删改查,文件包含通过将需要重复使用的功能模块代码引入其他文件的内容,实现重用代码、分离配置等。然而,如果文件包含操作…...

K7系列FPGA多重启动(Multiboot)
Xilinx 家的 FPGA 支持多重启动功能(Multiboot),即可以从多个 bin 文件中进行选择性加载,从而实现对系统的动态更新,或系统功能的动态调整。 这一过程可以通过嵌入在 bit 文件里的 IPROG 命令实现上电后的自动加载。而…...

关于武汉芯景科技有限公司的RS232通信接口芯片XJ3243EEUI开发指南(兼容MAX3243EEUI)
一、芯片引脚介绍 1.芯片引脚 2.引脚描述 二、典型应用电路 三、功能描述 1.Transmitter 通过T1,T2可以将TTL电平转换为RS232电平 2.Receiver 通过R1,R2可以将RS232电平转换为TTL电平 3.工作模式控制 4.INVALID引脚...

TreeSize Free:你的免费磁盘空间管理专家
TreeSize Free是一款专为Windows用户设计的磁盘空间分析工具。它能够帮助用户快速识别并管理那些占用大量空间的文件夹和文件。 功能亮点 快速扫描:TreeSize Free能够迅速扫描整个磁盘卷,展示所有文件夹及其子文件夹的大小,甚至可以细化到单…...
python办公自动化:初识`python-docx`
1.1 什么是python-docx python-docx是一个用于在Python中创建和操作Word文档的库。它提供了一组简洁的API,让开发者可以轻松地生成、修改、和读取Microsoft Word (.docx)文件,而不需要安装Microsoft Office。这使得python-docx成为办公自动化、报告生成…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

ubuntu22.04有线网络无法连接,图标也没了
今天突然无法有线网络无法连接任何设备,并且图标都没了 错误案例 往上一顿搜索,试了很多博客都不行,比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动,重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...

图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...