【JavaEE初阶】线程 和 thread

news/2024/12/22 21:42:15/
本节⽬标
  • 认识多线程
  • 掌握多线程程序的编写
  • 掌握多线程的状态

一. 认识线程(Thread)

1概念

1) 线程是什么

       

         ⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 "同时" 执⾏着多份代码.

        还是回到我们之前的银⾏的例⼦中。之前我们主要描述的是个⼈业务,即⼀个⼈完全处理⾃⼰的业务。我们进⼀步设想如下场景:

        ⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。
        如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情,分别申请⼀个号码进⾏排队,⾃此就有了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务。
        此时,我们就把这种情况称为多线程,将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队 执⾏。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(Main Thread)。

2) 为啥要有线程

⾸先, "并发编程" 成为 "刚需".

  • 单核 CPU 的发展遇到了瓶颈. 要想提⾼算⼒, 就需要多核 CPU. ⽽并发编程能更充分利⽤多核 CPU资源.
  • 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程.

其次, 虽然多进程也能实现 并发编程, 但是线程⽐进程更轻量.(线程就是轻量级进程)

  • 创建线程⽐创建进程更快.
  • 销毁线程⽐销毁进程更快.
  • 调度线程⽐调度进程更快.

最后, 线程虽然⽐进程轻量, 但是⼈们还不满⾜, 于是⼜有了 "线程池"(ThreadPool) 和 "协程"(Coroutine)

3) 进程和线程的区别

  • 进程是包含线程的. 每个进程⾄少有⼀个线程存在,即主线程。
  • 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.
⽐如之前的多进程例⼦中,每个客⼾来银⾏办理各⾃的业务,但他们之间的票据肯定是不想让别⼈知道的,否则钱不就被其他⼈取⾛了么。⽽上⾯我们的公司业务中,张三、李四、王五虽然是不同的执⾏流,但因为办理的都是⼀家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最⼤区别。
  • 进程是系统分配资源的最⼩单位,线程是系统调度的最⼩单位。
  • ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带⾛(整个进程崩溃).

4) Java 的线程 和 操作系统线程 的关系

        线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对⽤⼾层提供了⼀些 API 供用户使⽤(例如 Linux 的 pthread 库).
api:application programming interface(应用程序编程接口)
  1. 操作系统提供的原生api是c写的
  2. 不同操作系统的线程api不相同
Java 标准库中 Thread 类 可以视为是对操作系统提供的 API 进⾏了进⼀步的抽象和封装.(Thread类就在Java默认导入的java.lang包里面)

2 第⼀个多线程程序

感受多线程程序和普通程序的区别:
  • 每个线程都是⼀个独⽴的执⾏流
  • 多个线程之间是 "并发" 执⾏的.
  使⽤ jconsole 命令观察线程

3 创建线程

⽅法1 继承 Thread 类

(1)继承 Thread 来创建⼀个线程类.
java">class MyThread extends Thread{@Override//run相当于线程的入口函数public void run() {System.out.println("hello world");}
}

(2)创建 MyThread 类的实例

java">Thread t=new MyThread();

(3)调⽤ start ⽅法启动线程

java"> //真正在系统中创建出一个线程t.start();

(4)休眠

java"> try {Thread.sleep(1000);//sleep是静态方法,表示休眠,休息一会再执行,用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}

(5)run

        在执行线程时,不需要显示运行run方法,JVM自动调用

Tip:

在自己写的MyThread类里面不允许用throws,只能try-catch,因为其父类Thread里面没有实现该功能,但main函数中可以

实际开发中,异常的处理方式
1.记录异常信息作为日志.后续程序员根据日志调查问题
        程序仍然正常往后执行逻辑,不会因为这个异常就终止(对于服务器非常关键的)

2.进行重试,有的异常是概率性的(网络通讯)

3.特别严重的问题,必须立即马上处理的问题

        通过短信/邮件/微信/电话 通知程序员 (报警机制)


服务器和客户端指的是两个程序

服务器(server):被动接受请求,返回响应的一方

客户端(client):主动发起请求的一方

  • 客户端给服务器发送的数据,叫做“request"请求
  • 服务器给客户端返回的叫做“response”响应
  • 通常一个服务器可以给多个客户端提供服务
  • 服务器基本7*24待命

根据输出可以知道:多个线程的调度是随机的(“抢占式执行”)


Q:可以控制输出顺序吗?

A:输出顺序是操作系统内核的调度器控制的,没法在应用应用程序中编写代码控制 (调度器没有提供 api 的)
唯一能做的就是给线程设置优先级(但是优先级,对于操作系统来说,也是仅供参考,不会严格的定量的遵守)


如果直接使用run方法,而没有start,那么MyTread实质上没有创建出进程,只有main进程,遇到run中的死循环之后无法退出。

java">package Thread;
class MyThread extends Thread{@Override//run相当于线程的入口函数public void run() {while(true){System.out.println("hello run");try {Thread.sleep(1000);//sleep是静态方法,表示休眠,休息一会再执行,用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class demo1 {public static void main(String[] args) throws InterruptedException {Thread t=new MyThread();//真正在系统中创建出一个线程//t.start();t.run();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

⽅法2 实现 Runnable 接⼝

1. 实现 Runnable 接⼝
java">class MyRunnable implements Runnable{@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}

2. 创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为 target 参数.

java">Runnable runnable=new MyRunnable();
Thread t=new Thread(runnable);

3. 调⽤ start ⽅法

java"> t.start();

总代码:

java">package Thread;
class MyRunnable implements Runnable{@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}public class demo2 {public static void main(String[] args) throws InterruptedException {Runnable runnable=new MyRunnable();Thread t=new Thread(runnable);t.start();while (true){System.out.println("hello main");Thread.sleep(1000);}}
}

Runnable最终还是要通过 Thread,真正创建线程

线程里要干啥, 通过 Runnable 来表示(而不是通过直接重写 Thread的run 来表示了)

Q:如何判断是用哪种?

A:根据线程要执行的任务的定义,是放到 Thread 里面,还是放到外面(Runnable 中)

Q:使用Runnable有什么好处吗?

A:解耦合。要执行的任务本身,和 线程这个概念,能够解耦合,从而后续如果变更代码.(比如不通过线程执行这个任务,通过其他方式.….)

采用 Runnable 这样的方案,代码的修改就会更简单.

对⽐上⾯两种⽅法:

  • 继承 Thread 类, 直接使⽤ this 就表⽰当前线程对象的引⽤.
  • 实现 Runnable 接⼝, this 表⽰的是 MyRunnable 的引⽤. 需要使⽤Thread.currentThread()

⽅法3 实现Tread的匿名内部类

java">package Thread;public class demo3 {public static void main(String[] args) throws InterruptedException {Thread thread=new Thread(){while(true){System.out.println("hello run");try {Thread.sleep(1000);//sleep是静态方法,表示休眠,休息一会再执行,用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}}};thread.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

⽅法4 匿名内部类创建 Runnable ⼦类对象

 
java">package Thread;public class demo4 {public static void main(String[] args) throws InterruptedException {Runnable runnable = new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello run");try {Thread.sleep(1000);//sleep是静态方法,表示休眠,休息一会再执行,用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}}}};Thread thread=new Thread(runnable);thread.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

⽅法5  lambda 表达式创建 Runnable ⼦类对象

java">package Thread;public class demo5 {public static void main(String[] args) throws InterruptedException {Thread thread=new Thread(()->{while (true) {System.out.println("hello run");try {Thread.sleep(1000);//sleep是静态方法,表示休眠,休息一会再执行,用ms为单位} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

4 多线程的优势-增加运⾏速度

可以观察多线程在⼀些场合下是可以提⾼程序的整体运⾏效率的。
  • 使⽤ System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
  • serial 串⾏的完成⼀系列运算.
  • concurrency 使⽤两个线程并⾏的完成同样的运算.
 

二. Thread 类及常⻅⽅法

      

        Thread 类是 JVM ⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。

        ⽤我们上⾯的例⼦来看,每个执⾏流,也需要有⼀个对象来描述,类似下图所⽰,⽽ Thread 类的对象就是⽤来描述⼀个线程执⾏流的,JVM 会将这些 Thread 对象组织起来,⽤于线程调度,线程管理。

1 Thread 的常⻅构造⽅法

java">Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

main方法结束了,主线程就结束了

以前认知里main方法结束,程序就执行完毕是针对单线程程序的

2 Thread 的⼏个常⻅属性

  • ID 是线程的唯⼀标识,不同线程不会重复
  • 名称是各种调试⼯具⽤到
  • 状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明
  • 优先级⾼的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
  • 是否存活,即简单的理解,为 run ⽅法是否运⾏结束了
  • 线程的中断问题,下⾯我们进⼀步说明
 
java">public class ThreadDemo {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还在");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(Thread.currentThread().getName()+ ": ID: " + thread.getId());System.out.println(Thread.currentThread().getName()+ ": 名称: " + thread.getName());System.out.println(Thread.currentThread().getName()+ ": 状态: " + thread.getState());System.out.println(Thread.currentThread().getName()+ ": 优先级: " + thread.getPriority());System.out.println(Thread.currentThread().getName()+ ": 后台线程: " + thread.isDaemon());System.out.println(Thread.currentThread().getName()+ ": 活着: " + thread.isAlive());System.out.println(Thread.currentThread().getName()+ ": 被中断: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(Thread.currentThread().getName()+ ": 状态: " + thread.getState());}
}

3 启动⼀个线程 - start()

之前我们已经看到了如何通过覆写 run ⽅法创建⼀个线程对象,但线程对象被创建出来并不意味着线程就开始运⾏了。

  • 覆写 run ⽅法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • ⽽调⽤ start() ⽅法,就是喊⼀声:”⾏动起来!“,线程才真正独⽴去执⾏了。

调⽤ start ⽅法, 才真的在操作系统的底层创建出⼀个线程

4 中断⼀个线程

        李四⼀旦进到⼯作状态,他就会按照⾏动指南上的步骤去进⾏⼯作,不完成是不会结束的。但有时我们需要增加⼀些机制,例如⽼板突然来电话了,说转账的对⽅是个骗⼦,需要赶紧停⽌转账,那张三该如何通知李四停⽌呢?这就涉及到我们的停⽌线程的⽅式了。

⽬前常⻅的有以下两种⽅式:

  • 通过共享的标记来进⾏沟通
  • 调⽤ interrupt() ⽅法来通知

⽰例1: 使⽤⾃定义的变量来作为标志位.

java">//需要给标志位上加 volatile 关键字(这个关键字的功能后⾯介绍).
public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了⼤事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");target.isQuit = true;}
}

⽰例-2: 使⽤ Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位.

Thread 内部包含了⼀个 boolean 类型的变量作为线程是否被中断的标记.

使⽤ thread 对象的 interrupted() ⽅法通知线程结束.
java">public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {// 两种⽅法均可以while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName() + ": 有内⻤,终⽌交易!");// 注意此处的 breakbreak;}}System.out.println(Thread.currentThread().getName() + ": 啊!险些误了⼤事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName() + ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName() + ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");thread.interrupt();}
}

thread 收到通知的⽅式有两种:

1. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志

        ◦ 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.

          ◦ Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

2. 否则,只是内部的⼀个中断标志被设置,thread 可以通过这种⽅式通知收到的更及时,即使线程正在 sleep 也可以⻢上收到。

      

5 等待⼀个线程 - join()

有时,我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要⼀个⽅法明确等待线程的结束。

java">package Thread;public class ThreadDemo1 {public static void main(String[] args) throws InterruptedException {Runnable target =() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName()+ ": 我还在⼯作!");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我结束了!");};Thread thread1 = new Thread(target, "李四");Thread thread2 = new Thread(target, "王五");System.out.println("先让李四开始⼯作");thread1.start();thread1.join();System.out.println("李四⼯作结束了,让王五开始⼯作");thread2.start();thread2.join();System.out.println("王五⼯作结束了");}
}

⼤家可以试试如果把两个 join 注释掉,现象会是怎么样的呢?

附录

6 获取当前线程引⽤

这个⽅法我们已经⾮常熟悉了

java">public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}

7 休眠当前线程

也是我们⽐较熟悉⼀组⽅法,有⼀点要记得,因为线程的调度是不可控的,所以,这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的。
 
java">public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}

三、 线程的状态

1 观察线程的所有状态

线程的状态是⼀个枚举类型
 
java">Thread.State
public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}

  • NEW: 安排了⼯作, 还未开始⾏动
  • RUNNABLE: 可⼯作的. ⼜可以分成正在⼯作中和即将开始⼯作.
  • BLOCKED: 这⼏个都表⽰排队等着其他事情
  • WAITING: 这⼏个都表⽰排队等着其他事情
  • TIMED_WAITING: 这⼏个都表⽰排队等着其他事情
  • TERMINATED: ⼯作完成了.

2. 线程状态和状态转移的意义

⼤家不要被这个状态转移图吓到,我们重点是要理解状态的意义以及各个状态的具体意思。

还是我们之前的例⼦:

刚把李四、王五找来,还是给他们在安排任务,没让他们⾏动起来,就是 NEW 状态;

当李四、王五开始去窗⼝排队,等待服务,就进⼊到 RUNNABLE 状态。该状态并不表⽰已经被银⾏⼯作⼈员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;

当李四、王五因为⼀些事情需要去忙,例如需要填写信息、回家取证件、发呆⼀会等等时,进⼊BLOCKED 、 WATING 、TIMED_WAITING 状态,⾄于这些状态的细分,我们以后再详解;

如果李四、王五已经忙完,为 TERMINATED 状态。

所以,之前我们学过的 isAlive() ⽅法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的

3 观察线程的状态和转移

观察 1: 关注 NEW RUNNABLE TERMINATED 状态的转换  
java">package Thread;public class ThreadStateTransfer {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 1000_0000; i++) {}}, "李四");System.out.println(t.getName() + ": " + t.getState());;t.start();while (t.isAlive()) {System.out.println(t.getName() + ": " + t.getState());;}System.out.println(t.getName() + ": " + t.getState());;}
}


观察 2: 关注 WAITING BLOCKED TIMED_WAITING 状态的转换
java"> public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();}
}

使⽤ jconsole 可以看到 t1 的状态是 TIMED_WAITING , t2 的状态是 BLOCKED

修改上⾯的代码, 把 t1 中的 sleep 换成 wait
 
java">    public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {// [修改这⾥就可以了!!!!!]// Thread.sleep(1000);object.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}, "t1");
...}

使⽤ jconsole 可以看到 t1 的状态是 WAITING

结论:

BLOCKED 表⽰等待获取锁, WAITING 和 TIMED_WAITING 表⽰等待其他线程发来通知.

TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在⽆限等待唤醒


http://www.ppmy.cn/news/1557298.html

相关文章

仓鼠身长能长到多少厘米?

仓鼠&#xff0c;作为颇受欢迎的宠物&#xff0c;其小巧玲珑的身形是吸引众多饲主的重要原因之一。那么&#xff0c;仓鼠的身长究竟能长到多少厘米呢&#xff1f;这背后其实蕴含着不少有趣的知识。 一般而言&#xff0c;常见的仓鼠品种如三线仓鼠、紫仓仓鼠等&#xff0c;成年…

十九、IPD在国内的现状与成功案例分析

IPD在国内的现状与成功案例分析 随着市场竞争的加剧和消费者需求的日益多样化&#xff0c;企业在产品开发过程中面临着更高的挑战。如何在有限的时间内高效开发出符合市场需求的高质量产品&#xff0c;成为了各大企业亟待解决的问题。在此背景下&#xff0c;集成产品开发&…

使用Chat-LangChain模块创建一个与用户交流的机器人

当然&#xff01;要使用Chat-LangChain模块创建一个与用户交流的机器人&#xff0c;你需要安装并配置一些Python库。以下是一个基本的步骤指南和示例代码&#xff0c;帮助你快速上手。 安装依赖库 首先&#xff0c;你需要安装langchain库&#xff0c;它是一个高级框架&#x…

android、flutter离线推送插件,支持oppo、vivo、小米、华为

项目说明 项目地址&#xff1a;https://github.com/haomiao33/ym_flutter_push 起因 目前github上面搜索发现没有合适的flutter和android 推送原生插件&#xff0c;所以自己参考和借鉴了(https://github.com/taoweiji/MixPush)项目&#xff0c;这个mixpush太老了&#xff0c…

使用Python实现天文数据分析:探索宇宙的奥秘

天文学是一门通过观测和分析天体来研究宇宙结构和演化规律的科学。随着观测技术的进步&#xff0c;天文学家们积累了大量的天文数据。通过对这些数据的分析&#xff0c;我们可以揭示宇宙中的诸多奥秘。Python作为一种功能强大且易用的编程语言&#xff0c;为天文数据分析提供了…

flutter 快速实现侧边栏

首先我们写一个侧边栏工具类&#xff0c;示例如下&#xff1a; import package:flutter/material.dart;class Sidebar extends StatelessWidget {overrideWidget build(BuildContext context) {return Drawer(child: ListView(padding: EdgeInsets.zero,children: <Widget&…

力扣-图论-19【算法学习day.69】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

电商新品发布自动化:RPA 确保信息一致性与及时性【rap.top】

一、教学目标 让学员了解电商新品发布过程中的挑战以及 RPA 的概念和优势。掌握 RPA 在电商新品发布中确保信息一致性与及时性的方法和流程。培养学员运用 RPA 解决实际问题的能力。 二、教学重难点 重点 RPA 在电商新品发布中的应用场景。实现信息一致性与及时性的具体策略…