Java后端开发面试题——多线程

创建线程的方式有哪些?

继承Thread类

public class MyThread extends Thread {@Overridepublic void run() {System.out.println("MyThread...run...");}public static void main(String[] args) {// 创建MyThread对象MyThread t1 = new MyThread() ;MyThread t2 = new MyThread() ;// 调用start方法启动线程t1.start();t2.start();}}

实现runnable接口

public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("MyRunnable...run...");}public static void main(String[] args) {// 创建MyRunnable对象MyRunnable mr = new MyRunnable();// 创建Thread对象Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);// 调用start方法启动线程t1.start();t2.start();}
}

实现Callable接口

public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println(Thread.currentThread().getName());return "ok";}public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建MyCallable对象MyCallable mc = new MyCallable() ;// 创建FutureTaskFutureTask<String> ft = new FutureTask<String>(mc) ;// 创建Thread对象Thread t1 = new Thread(ft) ;Thread t2 = new Thread(ft) ;// 调用start方法启动线程t1.start();// 调用ft的get方法获取执行结果String result = ft.get();// 输出System.out.println(result);}
}

线程池创建线程

public class MyExecutors implements Runnable{@Overridepublic void run() {System.out.println("MyRunnable...run...");}public static void main(String[] args) {// 创建线程池对象ExecutorService threadPool = Executors.newFixedThreadPool(3);threadPool.submit(new MyExecutors()) ;// 关闭线程池threadPool.shutdown();}
}

runnable 和 callable 有什么区别?

Runnable 接口run方法没有返回值

Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果

Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

线程的 run()和 start()有什么区别?

start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。

run(): 封装了要被线程执行的代码,可以被调用多次。

线程包括哪些状态,状态之间是如何变化的

public enum State {//尚未启动的线程的线程状态NEW,//可运行线程的线程状态。RUNNABLE,//线程阻塞等待监视器锁的线程状态。BLOCKED,     //等待线程的线程状态WAITING,//具有指定等待时间的等待线程的线程状态TIMED_WAITING,//已终止线程的线程状态。线程已完成执行TERMINATED;
}

新建 T1、T2、T3 三个线程,如何保证它们按顺序执行? 

t.join() 阻塞调用此方法的线程进入timed_waiting 直到线程t执行完成后,此线程再继续执行

Thread t1 = new Thread(() -> {System.out.println("t1");
}) ;
Thread t2 = new Thread(() -> {try {// 加入线程t1,只有t1线程执行完毕以后,再次执行该线程t1.join();                          } catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2");
}) ;
Thread t3 = new Thread(() -> {try {// 加入线程t2,只有t2线程执行完毕以后,再次执行该线程t2.join();                              } catch (InterruptedException e) {e.printStackTrace();}System.out.println("t3");
}) ;
// 启动线程
t1.start();
t2.start();
t3.start();

notify()和 notifyAll()有什么区别?

notifyAll:唤醒所有wait的线程

notify:只随机唤醒一个 wait 线程

java中wait和sleep方法的不同?

共同点

wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

1.方法归属不同 sleep(long) 是 Thread 的静态方法 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有

2.醒来时机不同 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来 wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去 它们都可以被打断唤醒

3. 锁特性不同(重点) wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制 wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用) 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

如何停止一个正在运行的线程?

使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

使用stop方法强行终止(不推荐,方法已作废)

使用interrupt方法中断线程

        打断阻塞的线程( sleep,wait,join )的线程,线程会抛出InterruptedException异常

         打断正常的线程,可以根据打断状态来标记是否退出线程

synchronized关键字的底层原理

Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住

它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor

在monitor内部有三个属性,分别是owner、entrylist、waitset

其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程

请谈谈你对 volatile 的理解

①保证线程间的可见性

② 禁止进行指令重排序

        写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下

        读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上

volatile int x;
int y;
@Actor
public void actor1() {x = 1;y = 1;
}
@Actor
public void actor2(II_Result r) {r.r1 = y;r.r2 = x;
}

        写变量让volatile修饰的变量的在代码最后位置

        读变量让volatile修饰的变量的在代码最开始位置

什么是AQS?

是多线程中的队列同步器。是一种锁机制,它是做为一个基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的

AQS内部维护了一个先进先出的双向队列,队列中存储的排队的线程

在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中的有一个线程修改成功了state为1,则当前线程就相等于获取了资源

在对state修改的时候使用的cas操作,保证多个线程修改的情况下原子性

新的线程与队列中的线程共同来抢资源,是非公平锁 新的线程到队列中等待,只让队列中的head线程获取锁,是公平锁

ReentrantLock的实现原理

ReentrantLock表示支持重新进入的锁,调用 lock 方 法获取了锁之后,再次调用 lock,是不会再阻塞

ReentrantLock主要利用CAS+AQS队列来实现

支持公平锁和非公平锁,在提供的构造器的中无参默认是非公平锁,也可以传参设置为公平锁

synchronized和Lock有什么区别 ?

语法层面

synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现

Lock 是接口,源码由 jdk 提供,用 java 语言实现

使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁

功能层面

二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能

Lock 提供了许多 synchronized 不具备的功能,例如公平锁、可打断、可超时、多条件变量

Lock 有适合不同场景的实现,如 ReentrantLock, ReentrantReadWriteLock(读写锁)

性能层面

在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖

在竞争激烈时,Lock 的实现通常会提供更好的性能

死锁产生的条件是什么?

一个线程需要同时获取多把锁,这时就容易发生死锁

如何进行死锁诊断?

当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack

jps:输出JVM中运行的进程状态信息

jstack:查看java进程内线程的堆栈信息,查看日志,检查是否有死锁      如果有死锁现象,需要查看具体代码分析后,可修复

可视化工具jconsole、VisualVM也可以检查死锁问题

聊一下ConcurrentHashMap

数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表 采用 CAS + Synchronized来保证并发安全进行实现

CAS控制数组节点的添加

synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发的问题 , 效率得到提升

Java程序中怎么保证多线程的执行安全

1.原子性     synchronized、lock

2.内存可见性   volatile、synchronized、lock

3.有序性     volatile

说一下线程池的核心参数

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

corePoolSize 核心线程数目

maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)

keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放

unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等

workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等

handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略

1.AbortPolicy:直接抛出异常,默认策略;

2.CallerRunsPolicy:用调用者所在的线程来执行任务;

3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

4.DiscardPolicy:直接丢弃任务;

线程池中有哪些常见的阻塞队列

workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务

1.ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。

2.LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。

3.DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的

4.SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

如何确定核心线程数

IO密集型任务

一般来说:文件读写、DB读写、网络请求等

核心线程数大小设置为2N+1

CPU密集型任务

一般来说:计算型代码、Bitmap转换、Gson转换等

核心线程数大小设置为N+1

public static void main(String[] args) {//查看机器的CPU核数System.out.println(Runtime.getRuntime().availableProcessors());
}

参考回答:

① 高并发、任务执行时间短 ( CPU核数+1 ),减少线程上下文的切换

② 并发不高、任务执行时间长

IO密集型的任务  (CPU核数 * 2 + 1)

计算密集型任务 ( CPU核数+1 )

③ 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)

线程池的种类有哪些

 创建使用固定线程数的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

核心线程数与最大线程数一样,没有救急线程

阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE

适用于任务量已知,相对耗时的任务

单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

核心线程数和最大线程数都是1

阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE

适用于按照顺序执行的任务

可缓存线程池

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

核心线程数为0 最大线程数是Integer.MAX_VALUE 阻塞队列为SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

适合任务数比较密集,但每个任务执行时间较短的情况

提供了“延迟”和“周期执行”功能的ThreadPoolExecutor

public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,RejectedExecutionHandler handler) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory,RejectedExecutionHandler handler) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory, handler);
}

newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行

newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行

为什么不建议用Executors创建线程池

  线程池使用场景(CountDownLatch、Future)

CountDownLatch(闭锁/倒计时锁)用来进行线程同步协作,等待所有线程完成倒计时(一个或者多个线程,等待其他多个线程完成某件事情之后才能执行) 其中构造参数用来初始化等待计数值 await() 用来等待计数归零 countDown() 用来让计数减一

批量导入:使用了线程池+CountDownLatch批量把数据库中的数据导入到了ES(任意)中,避免OOM

数据汇总:调用多个接口来汇总数据,如果所有接口(或部分接口)的没有依赖关系,就可以使用线程池+future来提升性能

异步线程(线程池):为了避免下一级方法影响上一级方法(性能考虑),可使用异步线程调用下一个方法(不需要下一级方法返回值),可以提升方法响应时间

如何控制某个方法允许并发访问线程的数量

// 1. 创建 semaphore 对象
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {new Thread(() -> {try {// 3. 获取许可semaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}try {System.out.println("running...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("end...");} finally {// 4. 释放许可semaphore.release();}}).start();
}

Semaphore使用步骤

创建Semaphore对象,可以给一个容量

semaphore.acquire(): 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量) semaphore.release():释放一个信号量,此时信号量个数+1

谈谈你对ThreadLocal的理解

它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享

使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每个线程都在各自的 Connection 上进行数据库的操作,避免A线程关闭了B线程的连接

set(value) 设置值

get() 获取值

remove() 清除值

ThreadLocal内存泄漏问题

ThreadLocalMap 中的 key 是弱引用,值为强引用;

key 会被GC 释放内存,关联 value 的内存并不会释放。

建议主动 remove 释放 key,value

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/118725.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

aarch64-linux交叉编译libcurl带zlib和openssl

交叉编译libcurl需要依赖zlib和openssl 需要先用aarch64工具链编译zlib和openssl aarch64-linux环境搭建 下载工具链 gcc用于执行交叉编译 gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnusysroot是交叉版本的库文件集合 sysroot-glibc-linaro-2.25-2019.12-aarch64-lin…

iOS逆向进阶:iOS进程间通信方案深入探究与local socket介绍

在移动应用开发中&#xff0c;进程间通信&#xff08;Inter-Process Communication&#xff0c;IPC&#xff09;是一项至关重要的技术&#xff0c;用于不同应用之间的协作和数据共享。在iOS生态系统中&#xff0c;进程和线程是基本的概念&#xff0c;而进程间通信方案则为应用的…

在 Spring Boot 中集成 MinIO 对象存储

MinIO 是一个开源的对象存储服务器&#xff0c;专注于高性能、分布式和兼容S3 API的存储解决方案。本文将介绍如何在 Spring Boot 应用程序中集成 MinIO&#xff0c;以便您可以轻松地将对象存储集成到您的应用中。 安装minio 拉取 minio Docker镜像 docker pull minio/minio创…

Linux:内核解压缩过程简析

文章目录 1. 前言2. 背景3. zImage 的构建过程4. 内核引导过程5. 内核解压缩过程6. 内核加压缩过程小结7. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 背景 本文基于 ARM32架构 …

剑指 Offer 62. 圆圈中最后剩下的数字(简单)

题目&#xff1a; class Solution { public:int lastRemaining(int n, int m) {int pos 0;for(int i2;i<n;i){pos (posm)%i;}return pos;} };作者&#xff1a;想吃火锅的木易 链接&#xff1a;详细题解 来源&#xff1a;力扣&#xff08;LeetCode&#xff09;

【微服务部署】07-调用链追踪

文章目录 集成SkyWalking实现调用链追踪1. SkyWalking架构图2. 代码集成SkyWalking 集成SkyWalking实现调用链追踪 1. SkyWalking架构图 Receiver是SkyWalking的入口&#xff0c;支持gRPC和HTTP协议。 SkyWalking内部有分析和查询两个部分 存储方面SkyWalking支持Elasticsearc…

第一个实例:QT实现汽车电子仪表盘

1.实现效果 1.1.视频演示 QT 实现汽车仪表盘 1.2.实现效果截图 2.生成的安装程序 此程序是个windows下的安装程序,可以直接安装,看到汽车仪表盘的实现效果,安装程序从下面链接下载: 【免费】使用QT实现的汽车电子仪表盘,在windows下的安装程序资源-CSDN文库 3.功能概述…

(三)行为模式:6、备忘录模式(Memento Pattern)(C++示例)

目录 1、备忘录模式&#xff08;Memento Pattern&#xff09;含义 2、备忘录模式的UML图学习 3、备忘录模式的应用场景 4、备忘录模式的优缺点 &#xff08;1&#xff09;优点&#xff1a; &#xff08;2&#xff09;缺点 5、C实现备忘录模式的实例 1、备忘录模式&#…

数学建模--整数规划匈牙利算法的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 #整数规划模型--匈牙利算法求解 """ 整数规划模型及概念&#xff1a;规划问题的数学模型一般由三个因素构成 决策变量 目标函数 约束条件&#xff1b;线性规划即以线性函数为目标函数&a…

密码算法、密钥体系---安全行业基础篇1

一、密码算法 密码算法是一种数学和计算方法&#xff0c;用于保护数据的机密性和安全性。不同的密码算法使用不同的数学原理和技术来加密和解密数据。以下是一些常见的密码算法类型&#xff1a; 1. **对称密码算法&#xff1a;** 特点&#xff1a;相同的密钥用于加密和解密数…

工服穿戴检测联动门禁开关算法

工服穿戴检测联动门禁开关算法通过yolov8深度学习框架模型&#xff0c;工服穿戴检测联动门禁开关算法能够准确识别和检测作业人员是否按照规定进行工服着装&#xff0c;只有当人员合规着装时&#xff0c;算法会发送开关量信号给门禁设备&#xff0c;使门禁自动打开。YOLO的结构…

Maven入门教程(一):安装Maven环境

视频教程&#xff1a;Maven保姆级教程 Maven入门教程(一)&#xff1a;安装Maven环境 Maven入门教程(二)&#xff1a;idea/Eclipse使用Maven Maven入门教程(三)&#xff1a;Maven语法 Maven入门教程(四)&#xff1a;Nexus私服 Maven入门教程(五)&#xff1a;自定义脚手架 Maven项…

近年GDC服务器分享合集(四): 《火箭联盟》:为免费游玩而进行的扩展

如今&#xff0c;网络游戏采用免费游玩&#xff08;Free to Play&#xff09;加内购的比例要远大于买断制&#xff0c;这是因为前者能带来更低的用户门槛。甚至有游戏为了获取更多的用户&#xff0c;选择把原来的买断制改为免费游玩&#xff0c;一个典型的例子就是最近的网易的…

c++图论免费ppt,简单深度理解图论

本篇博文想分享一个ppt,是帮助大家简单深度理解c图论. 作者承诺&#xff1a;分享的东西没有病毒&#xff0c;是资料。 分享的东西一个是ppt,ppt里面是150页的&#xff0c;里面将带领大家简单深度理解c图论&#xff0c;还有一个就是里面例题的数据&#xff0c;大家可以按照数据…

Qt应用开发(基础篇)——输入对话框 QInputDialog

一、前言 QInputDialog类继承于QDialog&#xff0c;是一个简单方便的对话框&#xff0c;用于从用户获取单个值。 对话框窗口 QDialog QInputDialog输入对话框带有一个文本标签、一个输入框和标准按钮。输入内容可以字符、数字和选项&#xff0c;文本标签用来告诉用户应该要输入…

MyBatis-Plus学习笔记

1.MyBatis-Plus简介&#xff1a; MyBatis-Plus是一个MyBatis的增强工具&#xff0c;在MyBatis的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。MyBatis-Plus提供了通用的mapper和service&#xff0c;可以在不编写任何SQL语句的情况下&#xff0c;快速的实现对单…

优化爬虫请求:如何选择合适的爬虫ip轮换策略?

在进行爬虫任务时&#xff0c;使用隧道爬虫ip并采用合适的轮换策略可以提高稳定性和效率。选择合适的隧道爬虫ip轮换策略可以优化您的爬虫请求过程。 1、考量目标网站特点 不同网站对于频繁请求可能有不同限制或反爬机制。 了解目标网站是否存在IP封禁、验证码等问题&#xff…

swagger 接口测试,用 python 写自动化时该如何处理?

在使用Python进行Swagger接口测试时&#xff0c;可以使用requests库来发送HTTP请求&#xff0c;并使用json库和yaml库来处理响应数据。以下是一个简单的示例代码&#xff1a; import requests import json import yaml# Swagger API文档地址和需要测试的接口路径 swagger_url …

云原生Kubernetes:K8S概述

目录 一、理论 1.云原生 2.K8S 3.k8s集群架构与组件 二、总结 一、理论 1.云原生 &#xff08;1&#xff09;概念 云原生是一种基于容器、微服务和自动化运维的软件开发和部署方法。它可以使应用程序更加高效、可靠和可扩展&#xff0c;适用于各种不同的云平台。 如果…

2分钟搭建FastGPT训练企业知识库AI助理(Docker部署)

我们使用宝塔面板来进行搭建&#xff0c;更方便快捷灵活&#xff0c;争取操作时间只需两分钟 宝塔面板下安装Docker 在【软件商店中】安装【docker管理器】【docker模块】即可 通过Docker安装FastGPT 通过【Docker】【添加容器】【容器编排】创建里新增docker-compose.yaml以下…