多线程相关高频面试题

news2025/1/21 13:06:14

一、线程的基础知识

1、线程和进程的区别?

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务。
  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间。
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)。

2、并行和并发有什么区别?

  • 并发(concurrent)是同一时间应对(dealing with)多件事情的能力

  • 并行(parallel)是同一时间动手做(doing)多件事情的能力

3、创建线程的四种方式

  • 继承Thread类。
  • 实现runnable接口。
  • 实现Callable接口。
  • 线程池创建线程。

4、runnable 和 callable 有什么区别?

  1. Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
  2. Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
  3. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。

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

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

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

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

  • 新建
    • 当一个线程对象被创建,但还未调用 start 方法时处于新建状态。
    • 此时未与操作系统底层线程关联。
  • 可运行
    • 调用了 start 方法,就会由新建进入可运行
    • 此时与底层线程关联,由操作系统调度执行。
  • 终结
    • 线程内代码已经执行完毕,由可运行进入终结
    • 此时会取消与底层线程关联。
  • 阻塞
    • 当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间。
    • 当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态。
  • 等待
    • 当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间。
    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态。
  • 有时限等待
    • 当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间。
    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁。
    • 如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁。
    • 还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态。

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

使用线程类的join()方法在一个线程中启动另一个线程。

T3调用T2,T2调用T1,这样T1就会先完成而T3最后完成。

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

  • notifyAll:唤醒所有wait的线程。
  • notify:只随机唤醒一个 wait 线程。

9、在 java 中 wait 和 sleep 方法的不同?

共同点

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

不同点

  • 方法归属不同

    • sleep(long) 是 Thread 的静态方法。
    • 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有。
  • 醒来时机不同

    • 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来。
    • wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
    • 它们都可以被打断唤醒。
  • 锁特性不同(重点)

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

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

有三种方式可以停止线程

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  • 使用stop方法强行终止(不推荐,方法已作废)。
  • 使用interrupt方法中断线程。

二、线程中并发安全

1、讲一下synchronized关键字的底层原理?

  • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】。
  • 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor。
  • 在monitor内部有三个属性,分别是owner、entrylist、waitset。
  • 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程。

2、Monitor实现的锁属于重量级锁,你了解过锁升级吗?

  • 重量级锁:
    底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
  • 轻量级锁:
    线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性。
  • 偏向锁:
    一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令。

3、你谈谈 JMM(Java 内存模型)

  • JMM(Java Memory Model)Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性。
  • JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)。
  • 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存。

4、CAS你知道吗?

  • CAS的全称是: Compare And Swap(比较再交换);它体现的一种乐观锁的思想,在无锁状态下保证线程操作数据的原子性。
  • CAS使用到的地方很多:AQS框架、AtomicXXX类。
  • 在操作共享变量的时候使用的自旋锁,效率上更高一些。
  • CAS的底层是调用的Unsafe类中的方法,都是操作系统提供的,其他语言实现。

5、乐观锁和悲观锁

  • CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

6、请谈谈你对 volatile 的理解

  • ①保证线程间的可见性
    用 volatile 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见。
  • ② 禁止进行指令重排序
    指令重排:用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果。

7、什么是AQS?

  • 是多线程中的队列同步器。是一种锁机制,它是做为一个基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的。
  • AQS内部维护了一个先进先出的双向队列,队列中存储的排队的线程。
  • 在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中的有一个线程修改成功了state为1,则当前线程就相等于获取了资源。
  • 在对state修改的时候使用的cas操作,保证多个线程修改的情况下原子性。

8、ReentrantLock的实现原理

  • ReentrantLock表示支持重新进入的锁,调用 lock 方 法获取了锁之后,再次调用 lock,是不会再阻塞。
  • ReentrantLock主要利用CAS+AQS队列来实现。
  • 支持公平锁和非公平锁,在提供的构造器的中无参默认是非公平锁,也可以传参设置为公平锁。

9、synchronized和Lock有什么区别 ?

  • 语法层面
    • synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现。
    • Lock 是接口,源码由 jdk 提供,用 java 语言实现。
    • 使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁。
  • 功能层面
    • 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能。
    • Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量。
    • Lock 有适合不同场景的实现,如 ReentrantLock, ReentrantReadWriteLock。
  • 性能层面
    • 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖。
    • 在竞争激烈时,Lock 的实现通常会提供更好的性能。

10、死锁产生的条件是什么?

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

11、如何进行死锁诊断?

  • 当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack。
    • jps:输出JVM中运行的进程状态信息
    • jstack:查看java进程内线程的堆栈信息,查看日志,检查是否有死锁;
      如果有死锁现象,需要查看具体代码分析后,可修复。
  • 可视化工具jconsole、VisualVM也可以检查死锁问题。

12、聊一下ConcurrentHashMap?

  1. 底层数据结构:
    JDK1.7底层采用分段的数组+链表实现。
    JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。
  2. 加锁的方式
    JDK1.7采用Segment分段锁,底层使用的是ReentrantLock。
    JDK1.8采用CAS添加新节点,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段锁粒度更细,性能更好。

13、导致并发程序出现问题的根本原因是什么?

  • 1.原子性:
    解决:synchronized、lock。
  • 2.内存可见性:
    解决:volatile、synchronized、lock。
  • 3.有序性:
    解决:volatile。

三、线程池

1、说一下线程池的核心参数(线程池的执行原理知道嘛)

核心参数

  • corePoolSize 核心线程数目。
  • maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)。
  • keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放。
  • unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等。
  • workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务。
  • threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等。
  • handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略。

执行原理

  • 1、任务在提交的时候,首先判断核心线程数是否已满,如果没有满则直接添加到工作线程执行。
  • 2,如果核心线程数满了,则判断阻塞队列是否已满,如果没有满,当前任务存入阻塞队列。
  • 3,如果阻塞队列也满了,则判断线程数是否小于最大线程数,如果满足条件,则使用临时线程执行任务。
    如果核心或临时线程执行完成任务后会检查阻塞队列中是否有需要执行的线程,如果有,则使用非核心线程执行任务。
  • 4,如果所有线程都在忙着(核心线程+临时线程),则走拒绝策略。
    在这里插入图片描述

2、拒绝策略:

  • 1.AbortPolicy:直接抛出异常,默认策略;
  • 2.CallerRunsPolicy:用调用者所在的线程来执行任务;
  • 3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • 4.DiscardPolicy:直接丢弃任务;

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

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。
  • LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。
  • DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的。
  • SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

4、如何确定核心线程数

  • 高并发、任务执行时间短 -->( CPU核数+1 ),减少线程上下文的切换
  • 并发不高、任务执行时间长
    • IO密集型的任务 --> (CPU核数 * 2 + 1)。
    • 计算密集型任务 --> ( CPU核数+1 )。
  • 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,参考(2)。

5、线程池的种类有哪些?

  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行。
  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行。

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

参考阿里开发手册《Java开发手册-嵩山版》。

主要原因是如果使用Executors创建线程池的话,它允许的请求队列默认长度是Integer.MAX_VALUE,这样的话,有可能导致堆积大量的请求,从而导致OOM(内存溢出)。

所以,我们一般推荐使用ThreadPoolExecutor来创建线程池,这样可以明确规定线程池的参数,避免资源的耗尽。

四、使用场景

1、你们项目哪里用到了多线程?

参考场景一:

es数据批量导入

在我们项目上线之前,我们需要把数据量的数据一次性的同步到es索引库中,但是当时的数据好像是1000万左右,一次性读取数据肯定不行(oom异常),如果分批执行的话,耗时也太久了。所以,当时我就想到可以使用线程池的方式导入,利用CountDownLatch+Future来控制,就能大大提升导入的时间。

参考场景二:

在我做那个xx电商网站的时候,里面有一个数据汇总的功能,在用户下单之后需要查询订单信息,也需要获得订单中的商品详细信息(可能是多个),还需要查看物流发货信息。因为它们三个对应的分别三个微服务,如果一个一个的操作的话,互相等待的时间比较长。所以,我当时就想到可以使用线程池,让多个线程同时处理,最终再汇总结果就可以了,当然里面需要用到Future来获取每个线程执行之后的结果才行。

参考场景三:

xxx项目中使用的。

我当时做了一个文章搜索的功能,用户输入关键字要搜索文章,同时需要保存用户的搜索记录(搜索历史),这块我设计的时候,为了不影响用户的正常搜索,我们采用的异步的方式进行保存的,为了提升性能,我们加入了线程池,也就说在调用异步方法的时候,直接从线程池中获取线程使用。

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

在多线程中提供了一个工具类Semaphore,信号量。在并发的情况下,可以控制方法的访问量

  • 创建Semaphore对象,可以给一个容量。
  • acquire()可以请求一个信号量,这时候的信号量个数-1。
  • release()释放一个信号量,此时信号量个数+1。

3、谈谈你对ThreadLocal的理解?

ThreadLocal 主要功能有两个

  • 第一个是可以实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题。
  • 第二个是实现了线程内的资源共享。

4、你知道ThreadLocal的底层原理实现吗?

在ThreadLocal内部维护了一个一个 ThreadLocalMap 类型的成员变量,用来存储资源对象。

  • 当我们调用 set 方法,就是以 ThreadLocal 自己作为 key,资源对象作为 value,放入当前线程的 ThreadLocalMap 集合中。
  • 当调用 get 方法,就是以 ThreadLocal 自己作为 key,到当前线程中查找关联的资源值。
  • 当调用 remove 方法,就是以 ThreadLocal 自己作为 key,移除当前线程关联的资源值。

5、关于ThreadLocal会导致内存溢出这个事情,了解吗?

ThreadLocalMap 中的 key 被设计为弱引用,它是被动的被GC调用释放key,不过关键的是只有key可以得到内存释放,而value不会,因为value是一个强引用。

在使用ThreadLocal 时都把它作为静态变量(即强引用),因此无法被动依靠 GC 回收,建议主动的remove 释放 key,这样就能避免内存溢出。

结束!!!!!!


			个人能否有成就,只看他是否具备自尊心与自信心两个条件。---苏格拉底

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GPIO口输出与输入模式的理解

问题?看GPIO的结构图,发现: 上拉输入电流是从引脚流入外部 下来输入电流是从引脚流进芯片内部 推挽输出推模式电流是从引脚流入外部 推挽输出挽模式电流是从外部流入内部 输入输出模式都有电流流入流出,为什么还要分为输入输出模…

K8s排错之浏览器打不开K8s Dashboard

一、问题 10.0.0.10 通常会使用加密技术来保护您的信息。Chrome 此次尝试连接到 10.0.0.10 时,该网站发回了异常的错误凭据。这可能是因为有攻击者在试图冒充 10.0.0.10,或者 Wi-Fi 登录屏幕中断了此次连接。请放心,您的信息仍然是安全的&am…

【饿了么UI】elementUI密码框图标实现睁眼和闭眼效果(阿里巴巴iconfront图标库vue项目本地引用)

elementUI中输入框的密码框属性, 默认是一个始终睁眼的图标,测试今天提bug要有闭眼效果(无大语)… 因为elementUI中的icon没有闭眼的,所以还要去iconfront下载引入 效果图: 点击后 一、下载图标 http…

【LeetCode】138. 复制带随机指针的链表

题目链接:https://leetcode.cn/problems/copy-list-with-random-pointer/description/ 📕题目要求: 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。…

基于springboot+mybatis-puls+mysql+html实现大学生创新创业管理系统

基于springbootmybatis-pulsmysqlhtml实现大学生创新创业管理系统 一、系统介绍1、系统主要功能:2.涉及技术框架:3.本项目所用环境: 二、功能展示三、其它系统四、获取源码 一、系统介绍 1、系统主要功能: 学生:申报…

自更新参数web接口预热工具

痛点 日常上线流程中经常需要对接口进行预热,因为服务器每次启动后都有一定次数访问失败,如果不处理将此请求直接抛出,会降低用户体验。当服务器数量较少时,我们可以在发布机器后,待机器启动使用本地hosts更改IP&…

20230510MTCNN3

MTCNN数据制作 - 1 多任务 分类任务 回归任务 模型增加任务,其实就是增加输出 级联即减少了 数据量,又增加了 模型的精度 级联可以让网络变得越快 越好 单独来看这三个网络,它们的效果不会好,因为网络太浅了 但,当…

【嵌入式烧录刷写文件】-1.3-删除/修改Motorola S-record(S19/SREC/mot/SX)文件中指定地址范围内的数据

案例背景(共6页精讲): 有如下一段S19文件,如何“自动”地完成地址范围0x9110-0x9113数据的删除或修改。 S0110000486578766965772056312E30352EA6 S123910058595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576775B…

t检验前世今生

1、背景 t检验是科研中非常常用的一种方法和手段,但是理解到位的人并不多,虽然这也不影响其使用。本文主要目的在于将与t检验有关的前前后后都讲明白。 2、补充知识 理解t检验,我们需要补充一些统计学有关的先验知识。 2.1 正态分布 概率…

【文本三剑客】SED

SED 一、sed编辑器1.2sed简介1.3sed工作流程1.4sed命令格式1.5常用选项1.6常用操作 二、sed实验2.1打印内容2.2删除行2.3替换2.4插入 一、sed编辑器 1.2sed简介 sed是一种流编辑器,流编辑器会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。sed编辑器可…

链表详解 - C语言描述

目录 认识链表 链表的分类 链表的实现 单链表的增删查改 增操作 删操作 查操作 改操作 带头双向循环链表 认识链表 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。链表由一系列节点(结点)组…

最火爆ChatGPT知识星球分享,开启与GPT的神奇之旅

今天给大家介绍的是一个讲解ChatGPt的知识星球,这个星球是专门为ChatGPT爱好者和AI绘画感兴趣的朋友们打造的。这个知识星球主题是关于ChatGPT的,旨在提供一个交流、学习和探索GPT的平台。 这里有一个强大的阵容,汇集了许多对人工智能和自然…

.Net平台下OpenGL绘制图形(2)(VS2019,Winform,C#)

本节主要讲诉图形绘制的原理,使用介绍和代码演示。 原理介绍 我们先来讲讲OpenGL的图形绘制。其实,所有的图形都是由许多个小图形连接而成的。你可以理解为是图片的像素,一张彩图是由很多个色彩不一的像素点组合而成。要实现色彩绚丽的图形设…

安装旧版本chrome 浏览器方法

1、下载 国内推荐下载网址:https://www.slimjet.com/chrome/google-chrome-old-version.php 我自己目前再用的103.0.5060.53版本,也可以到我网盘下载[若失效可联系我更新]。 2、安装之前,请先卸载干净当前版本 打开控制面板,找打…

挣值管理专题

挣值管理 基本思路 该方法的基本思想是应用统计学的原理,通过引进一个中间变量即“挣值”来帮助项目管理者分析项目成本的变动情况,并给出项目成本与工期相关变化的信息及对项目成本发展趋势作出预测与决策。 挣值的定义 挣值是一个表示项目“已完成作业…

Linux多路复用机制原理分析--select/poll

前言 Linux访问设备的IO模型主要有五种,分别是非阻塞IO模型、阻塞IO模型、IO多路复用模型、信号驱动模型以及异步IO模型。本文主要分析IO多路复用模型,Linux下的IO多路复用模型主要有select/poll/epoll等机制实现。 IO多路复用模型可以实现以非阻塞的方…

Dijkstra算法图解,C++实现Dijkstra算法

目录 Dijkstra算法简介数据结构抽象初始化开始计算第一轮计算第二轮计算第三轮计算第四轮计算算法总结 C实现Dijkstra算法 Dijkstra算法简介 Dijkstra算法计算是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起…

1929-2022年全球气象站点的逐日平均能见度

气象数据是我们在各项研究中都非常常用的数据,之前我们分享过全球范围的1929-2022年的具体到气象站点的逐日气象数据,包括平均气温、最高气温、最低气温、平均风速(可查看之前的文章获取)。 本次我们带来的是全球范围的1929-2022…

单链表的成环问题

前言:链表成环问题不仅考察双指针的用法,该问题还需要一定的数学推理和分析能力,看似简单的题目实则细思缜密,值得斟酌~ 目录 1.问题背景引入-判断链表是否成环: 1.1.正解:快慢指针 1.2 STL的集合判重 …

kubespray部署k8s 1.26集群安装指南

Kubespray 是一个自由开源的工具,它提供了 Ansible 剧本(playbook) 来部署和管理 Kubernetes 集群。它旨在简化跨多个节点的 Kubernetes 集群的安装过程,允许用户快速轻松地部署和管理生产就绪的 Kubernetes 集群。 它支持一系列操作系统,包…