并发工具类(二):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成为办公自动化、报告生成…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...