开发基础
JAVA
JUC
多线程
Lock锁
并发容器类
JUC辅助类
JUC底层知识
JVM
本文档使用 MrDoc 发布
-
+
首页
JUC辅助类
## CompletableFuture CompletableFuture 是 Java 8 引入的异步编程工具,用于简化多线程任务的编排和管理。它的核心功能包括: 1. 异步执行:将任务提交到线程池异步执行,避免阻塞主线程。 2. 链式调用:通过链式方法(如 thenApply、thenAccept)串联多个异步任务。 3. 结果组合:合并多个异步任务的结果(如 thenCombine、allOf)。 4. 异常处理:提供统一的异常处理机制(如 exceptionally、handle)。 5. 回调机制:支持任务完成后的回调操作(如 whenComplete)。 ### 1. 创建异步任务 | 方法名 | 参数说明 | 作用 | 示例 | |---------------|-------------|----------------|---------------------------------------------| | runAsync() | Runnable | 异步执行一个没有返回值的任务 | runAsync(() -> System.out.println("Hello")) | | supplyAsync() | Supplier<T> | 异步执行一个有返回值的任务 | supplyAsync(() -> "Hello") | ``` import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureExample { public static void main(String[] args) { // supplyAsync 示例:有返回值的异步任务 CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> { System.out.println("Running supplyAsync task"); return "Hello"; }); // runAsync 示例:无返回值的异步任务 CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> { System.out.println("Running runAsync task"); }); try { System.out.println("Result from supplyAsync: " + future1.get()); System.out.println("Result from runAsync: " + future2.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } } ``` ### 2. 链式处理(非阻塞) | 方法名 | 参数说明 | 作用 | 示例 | |-------------------|----------------------------------|------------------------------|------------------------------------------------| | thenApply() | Function<? super T, ? extends U> | 对上一个任务的结果进行处理,并返回一个新的结果 | thenApply(s -> s + " World") | | thenApplyAsync() | Function<? super T, ? extends U> | 与 thenApply() 类似,但在不同的线程上执行 | thenApplyAsync(s -> s + " World") | | thenAccept() | Consumer<? super T> | 消费上一个任务的结果,没有返回值 | thenAccept(System.out::println) | | thenAcceptAsync() | Consumer<? super T> | 与 thenAccept() 类似,但在不同的线程上执行 | thenAcceptAsync(System.out::println) | | thenRun() | Runnable | 执行一个与上一个任务结果无关的操作 | thenRun(() -> System.out.println("Done")) | | thenRunAsync() | Runnable | 与 thenRun() 类似,但在不同的线程上执行 | thenRunAsync(() -> System.out.println("Done")) | ``` import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // thenApply 示例:对结果进行转换 CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello") .thenApply(s -> s + " World"); // thenAccept 示例:消费结果 future1.thenAccept(System.out::println); // thenRun 示例:执行后续操作 future1.thenRun(() -> System.out.println("Task completed")); // thenApplyAsync 示例:在不同线程上对结果进行转换 CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Hello") .thenApplyAsync(s -> s + " World"); // thenAcceptAsync 示例:在不同线程上消费结果 future2.thenAcceptAsync(System.out::println); // thenRunAsync 示例:在不同线程上执行后续操作 future2.thenRunAsync(() -> System.out.println("Task completed")); } } ``` ### 3. 组合多个任务 | 方法名 | 参数说明 | 作用 | 示例 | |---------------|--------------------------------------------------------------------------------------|-----------------------|--------------------------------------------------------------------------------------| | thenCombine() | CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn | 合并两个异步任务的结果,并返回一个新的结果 | thenCombine(CompletableFuture.supplyAsync(() -> "World"), (s1, s2) -> s1 + " " + s2) | | thenCompose() | Function<? super T, ? extends CompletionStage<U>> fn | 将上一个任务的结果作为新任务的输入 | thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World")) | | allOf() | CompletableFuture<?>... cfs | 等待所有异步任务完成 | allOf(future1, future2).thenRun(() -> System.out.println("All done")) | | anyOf() | CompletableFuture<?>... cfs | 等待任意一个异步任务完成 | anyOf(future1, future2).thenAccept(System.out::println) | ``` import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // thenCombine 示例:合并两个任务的结果 CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World"); future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2) .thenAccept(System.out::println); // thenCompose 示例:将前序结果作为新任务的输入 CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Hello") .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World")); // allOf 示例:等待所有任务完成 CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2); allFutures.thenRun(() -> System.out.println("All tasks completed")); // anyOf 示例:等待任意一个任务完成 CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2); anyFuture.thenAccept(System.out::println); } } ``` ### 4. 异常处理 | 方法名 | 参数说明 | 作用 | 示例 | |---------------------|------------------------------------------|--------------------------------|-------------------------------------------------------------------------| | whenComplete() | BiConsumer<? super T, ? super Throwable> | 无论上一个任务是否成功完成,都执行指定的操作 | whenComplete((result, ex) -> System.out.println("Task completed")) | | whenCompleteAsync() | BiConsumer<? super T, ? super Throwable> | 与 whenComplete() 类似,但在不同的线程上执行 | whenCompleteAsync((result, ex) -> System.out.println("Task completed")) | | exceptionally() | Function<Throwable, ? extends T> | 当上一个任务出现异常时,提供一个备用结果 | exceptionally(ex -> "Fallback") | ``` import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // exceptionally 示例:捕获异常并返回默认值 CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Error occurred"); }).exceptionally(ex -> "Fallback"); // handle 示例:无论成功/失败都会执行 CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Error occurred"); }).handle((result, ex) -> ex != null ? "Error occurred" : result); future1.thenAccept(System.out::println); future2.thenAccept(System.out::println); } } ``` ### 5. 获取结果 | 方法名 | 参数说明 | 作用 | 示例 | |--------|---------------------------------|----------------|-------------------------------| | get() | 无 或 long timeout, TimeUnit unit | 阻塞获取结果(可能抛异常) | String result = future.get() | | join() | 无 | 阻塞获取结果(不抛受检异常) | String result = future.join() | ``` import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureExample { public static void main(String[] args) { // get 示例:阻塞获取结果(可能抛异常) CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello"); try { System.out.println("Result from get: " + future1.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } // join 示例:阻塞获取结果(不抛受检异常) CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Hello"); System.out.println("Result from join: " + future2.join()); } } ``` ### 6. 其他实用方法 | 方法名 | 参数说明 | 作用 | 示例 | |-------------------|---------|-------------------------------|----------------------------------| | completedFuture() | T value | 创建一个已完成的 CompletableFuture 对象 | completedFuture("Hello") | | complete() | T value | 设置 CompletableFuture 的结果为指定值 | future.complete("Hello") | | isDone() | 无 | 检查 CompletableFuture 是否已完成 | boolean isDone = future.isDone() | ``` import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // completedFuture 示例:创建一个已完成的 CompletableFuture 对象 CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); future1.thenAccept(System.out::println); // complete 示例:设置 CompletableFuture 的结果为指定值 CompletableFuture<String> future2 = new CompletableFuture<>(); future2.complete("Hello"); future2.thenAccept(System.out::println); // isDone 示例:检查 CompletableFuture 是否已完成 CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Hello"); System.out.println("Is future3 done? " + future3.isDone()); } } ``` ## CountDownLatch(倒计数器) CountDownLatch 是 Java 中的一个多线程控制工具类,位于 java.util.concurrent 包中。它的主要作用是允许一个或多个线程等待其他线程完成某些操作后再继续执行。它的名字可以形象地理解为“倒计数门闩”。 * 倒计数(Count Down):通过调用 countDown() 方法,计数器的值会递减。 * 门闩(Latch):通过调用 await() 方法,线程会被阻塞,直到计数器的值减到 0,门闩“打开”,阻塞的线程才会继续执行。 ### 常用方法 * CountDownLatch(int count) 构造方法,用于初始化一个倒计数器,count 表示初始计数值。这个值必须大于等于 0。 * countDown() 每调用一次,计数器的值减 1。当计数器的值减到 0 时,所有等待在 await() 方法上的线程会被唤醒。 * await() 当前线程等待,直到计数器的值减到 0。如果计数器的值已经为 0,则当前线程会直接继续执行 ### 使用场景 * 等待多个线程完成任务后再继续执行。 * 主线程需要等待所有子线程完成初始化后再继续执行。 * 多个线程需要协同完成某个任务。 ``` import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) { // 定义一个计数器,初始值为 3 CountDownLatch countDownLatch = new CountDownLatch(3); // 创建 3 个线程,模拟任务 for (int i = 1; i <= 3; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 开始执行任务"); try { // 模拟任务执行时间 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 完成任务"); // 每完成一个任务,计数器减 1 countDownLatch.countDown(); }, "线程 " + i).start(); } try { // 主线程等待,直到计数器减到 0 System.out.println("主线程等待所有子线程完成任务..."); countDownLatch.await(); System.out.println("所有子线程任务完成,主线程继续执行"); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` >s **输出结果** > > 主线程等待所有子线程完成任务... 线程 1 开始执行任务 线程 2 开始执行任务 线程 3 开始执行任务 线程 1 完成任务 线程 2 完成任务 线程 3 完成任务 所有子线程任务完成,主线程继续执行 ### 注意事项 * 线程安全:CountDownLatch 内部是线程安全的,多个线程可以安全地调用 countDown() 和 await() 方法。 * 不可重用:一旦计数器的值减到 0,CountDownLatch 就不可再被重用。如果需要重新设置计数器的值,需要使用 CyclicBarrier(另一个同步工具类)。 * 异常处理:如果在 await() 方法中捕获到 InterruptedException,计数器的值不会受到影响。 ## CyclicBarrier(循环栅栏) CyclicBarrier 是 Java 中的一个多线程控制工具类,位于 java.util.concurrent 包中。它的主要作用是允许一组线程相互等待,直到所有线程都到达一个公共的屏障点(barrier point)。一旦所有线程都到达屏障点,它们会被同时唤醒,继续执行后续任务。CyclicBarrier 的名字可以形象地理解为“循环的栅栏”,因为它可以在多次迭代中重复使用。 ### 常用方法 * CyclicBarrier(int parties, Runnable barrierAction) 创建一个 CyclicBarrier 实例,parties 指定参与相互等待的线程数,barrierAction 是一个可选的 Runnable 命令。该命令只在每个屏障点运行一次,可以在执行后续业务之前共享状态。该操作由最后一个进入屏障点的线程执行。 * CyclicBarrier(int parties) 创建一个 CyclicBarrier 实例,parties 指定参与相互等待的线程数。 * await() 该方法被调用时表示当前线程已经到达屏障点,当前线程阻塞进入休眠状态,直到所有线程都到达屏障点,当前线程才会被唤醒。 ### 使用场景 * 多个线程需要分阶段完成任务,每个阶段都必须等待所有线程完成当前阶段后才能进入下一个阶段。 * 多个线程需要在每个阶段共享某些状态或资源。 ``` import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { public static void main(String[] args) { // 定义一个循环栅栏,指定参与的线程数为 3 CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> { // 这个 Runnable 命令会在所有线程到达屏障点后执行 System.out.println("所有线程都到达屏障点,开始下一阶段..."); }); // 创建 3 个线程,模拟任务 for (int i = 1; i <= 3; i++) { new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + " 开始执行任务"); // 模拟任务执行时间 Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成任务第一阶段"); // 到达屏障点,等待其他线程 cyclicBarrier.await(); // 模拟第二阶段任务 System.out.println(Thread.currentThread().getName() + " 开始执行第二阶段任务"); Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成第二阶段任务"); // 再次到达屏障点,等待其他线程 cyclicBarrier.await(); // 模拟第三阶段任务 System.out.println(Thread.currentThread().getName() + " 开始执行第三阶段任务"); Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成第三阶段任务"); } catch (Exception e) { e.printStackTrace(); } }, "线程 " + i).start(); } } } ``` >s **输出结果** > > 线程 1 开始执行任务 线程 2 开始执行任务 线程 3 开始执行任务 线程 2 完成任务第一阶段 线程 1 完成任务第一阶段 线程 3 完成任务第一阶段 所有线程都到达屏障点,开始下一阶段... 线程 3 开始执行第二阶段任务 线程 1 开始执行第二阶段任务 线程 2 开始执行第二阶段任务 线程 1 完成第二阶段任务 线程 2 完成第二阶段任务 线程 3 完成第二阶段任务 所有线程都到达屏障点,开始下一阶段... 线程 1 开始执行第三阶段任务 线程 3 开始执行第三阶段任务 线程 2 开始执行第三阶段任务 线程 2 完成第三阶段任务 线程 3 完成第三阶段任务 线程 1 完成第三阶段任务 ### 注意事项 * 线程安全:CyclicBarrier 内部是线程安全的,多个线程可以安全地调用 await() 方法。 * 异常处理:如果在 await() 方法中捕获到 InterruptedException 或 BrokenBarrierException,需要进行适当的异常处理。BrokenBarrierException 表示某个线程在等待过程中被中断或超时。 * 重用性:CyclicBarrier 是可重用的,可以在多次迭代中重复使用。 ### CyclicBarrier和CountDownLatch的区别? CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;CountDownLatch允许一个或多个线程**等待一组事件的产生**,而CyclicBarrier用于等待其他线程**运行到栅栏位置**。 ## Semaphore(信号量) Semaphore 是 Java 中的一个多线程控制工具类,位于 java.util.concurrent 包中。它的主要作用是控制对共享资源的访问权限,通过限制同时访问资源的线程数量来实现资源的互斥使用或并发线程数的控制。 ### 常用方法 * public Semaphore(int permits) 构造方法,permits 指定资源数目(信号量)。这个值表示最多允许多少个线程同时访问共享资源。 * public void acquire() throws InterruptedException 占用资源。当一个线程调用 acquire() 方法时,它要么通过成功获取信号量(信号量减 1),要么一直等下去,直到有线程释放信号量,或超时。 * public void release() 释放资源。实际上会将信号量的值加 1,然后唤醒等待的线程。 ### 信号量的主要用途 * 多个共享资源的互斥使用 当有多个共享资源时,Semaphore 可以用来控制这些资源的互斥访问。例如,一个系统中有多个打印机,Semaphore 可以确保每个打印机在同一时间只被一个线程使用。 * 用于并发线程数的控制 保护一个关键部分不要一次进入超过 N 个线程。例如,一个系统中有一个关键部分,最多只能有 5 个线程同时访问,Semaphore 可以用来限制并发线程数。 ``` import java.util.concurrent.Semaphore; public class SemaphoreExample { public static void main(String[] args) { // 定义一个信号量,最多允许 3 个线程同时访问共享资源 Semaphore semaphore = new Semaphore(3); // 创建 5 个线程,模拟任务 for (int i = 1; i <= 5; i++) { new Thread(() -> { try { // 获取信号量 semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 获取信号量,开始执行任务"); // 模拟任务执行时间 Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成任务"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 释放信号量 semaphore.release(); System.out.println(Thread.currentThread().getName() + " 释放信号量"); } }, "线程 " + i).start(); } } } ``` >s **输出结果** > > 线程 1 获取信号量,开始执行任务 线程 2 获取信号量,开始执行任务 线程 3 获取信号量,开始执行任务 线程 4 等待信号量 线程 5 等待信号量 线程 1 完成任务 线程 1 释放信号量 线程 4 获取信号量,开始执行任务 线程 2 完成任务 线程 2 释放信号量 线程 5 获取信号量,开始执行任务 线程 3 完成任务 线程 3 释放信号量 线程 4 完成任务 线程 4 释放信号量 线程 5 完成任务 线程 5 释放信号量 Avatar 已完成推理 介绍Java Semaphore及代码案例 Semaphore(信号量) Semaphore 是 Java 中的一个多线程控制工具类,位于 java.util.concurrent 包中。它的主要作用是控制对共享资源的访问权限,通过限制同时访问资源的线程数量来实现资源的互斥使用或并发线程数的控制。 常用方法 public Semaphore(int permits) 构造方法,permits 指定资源数目(信号量)。这个值表示最多允许多少个线程同时访问共享资源。 public void acquire() throws InterruptedException 占用资源。当一个线程调用 acquire() 方法时,它要么通过成功获取信号量(信号量减 1),要么一直等下去,直到有线程释放信号量,或超时。 public void release() 释放资源。实际上会将信号量的值加 1,然后唤醒等待的线程。 信号量的主要用途 多个共享资源的互斥使用 当有多个共享资源时,Semaphore 可以用来控制这些资源的互斥访问。例如,一个系统中有多个打印机,Semaphore 可以确保每个打印机在同一时间只被一个线程使用。 用于并发线程数的控制 保护一个关键部分不要一次进入超过 N 个线程。例如,一个系统中有一个关键部分,最多只能有 5 个线程同时访问,Semaphore 可以用来限制并发线程数。 示例代码 以下是一个简单的代码案例,展示如何使用 Semaphore: java 复制 import java.util.concurrent.Semaphore; public class SemaphoreExample { public static void main(String[] args) { // 定义一个信号量,最多允许 3 个线程同时访问共享资源 Semaphore semaphore = new Semaphore(3); // 创建 5 个线程,模拟任务 for (int i = 1; i <= 5; i++) { new Thread(() -> { try { // 获取信号量 semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 获取信号量,开始执行任务"); // 模拟任务执行时间 Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成任务"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 释放信号量 semaphore.release(); System.out.println(Thread.currentThread().getName() + " 释放信号量"); } }, "线程 " + i).start(); } } } >s **输出示例** > > 线程 1 获取信号量,开始执行任务 线程 2 获取信号量,开始执行任务 线程 3 获取信号量,开始执行任务 线程 4 等待信号量 线程 5 等待信号量 线程 1 完成任务 线程 1 释放信号量 线程 4 获取信号量,开始执行任务 线程 2 完成任务 线程 2 释放信号量 线程 5 获取信号量,开始执行任务 线程 3 完成任务 线程 3 释放信号量 线程 4 完成任务 线程 4 释放信号量 线程 5 完成任务 线程 5 释放信号量 ### 注意事项 * 线程安全:Semaphore 内部是线程安全的,多个线程可以安全地调用 acquire() 和 release() 方法。 * 异常处理:如果在 acquire() 方法中捕获到 InterruptedException,需要进行适当的异常处理。 * 资源释放:在使用 Semaphore 时,必须确保在任务完成后释放信号量,否则会导致其他线程无法获取信号量。 ## Exchanger Exchanger 是一个用于两个线程之间交换数据的工具类。它允许两个线程在某个点上互相等待,直到双方都到达该点,然后交换数据。 ### 常用方法: Exchanger(V>:构造方法。 exchange(V x):交换数据,当前线程等待另一个线程到达交换点,然后交换数据。 ### 使用场景: 两个线程需要在某个点上交换数据,例如生产者和消费者模型中的数据交换。 ``` import java.util.concurrent.Exchanger; public class ExchangerExample { public static void main(String[] args) { Exchanger<String> exchanger = new Exchanger<>(); new Thread(() -> { try { String data = "数据来自线程 1"; System.out.println("线程 1 准备交换数据: " + data); String received = exchanger.exchange(data); System.out.println("线程 1 收到数据: " + received); } catch (InterruptedException e) { e.printStackTrace(); } }, "线程 1").start(); new Thread(() -> { try { String data = "数据来自线程 2"; System.out.println("线程 2 准备交换数据: " + data); String received = exchanger.exchange(data); System.out.println("线程 2 收到数据: " + received); } catch (InterruptedException e) { e.printStackTrace(); } }, "线程 2").start(); } } ``` ## Phaser Phaser 是 CyclicBarrier 的增强版,支持动态调整参与线程数和多阶段同步。它允许一组线程在多个屏障点上互相等待,并且可以在每个屏障点上执行一些操作。 ### 常用方法: * Phaser(int parties):构造方法,指定初始参与线程数。 * Phaser(int parties, Runnable onAdvanceAction):构造方法,指定初始参与线程数和每个屏障点执行的操作。 * register():注册一个线程。 * arriveAndAwaitAdvance():当前线程到达屏障点并等待其他线程。 * arriveAndDeregister():当前线程到达屏障点并注销自己。 ### 使用场景: 多个线程需要在多个屏障点上互相等待,并且参与线程数可能会动态变化。 ``` import java.util.concurrent.Phaser; public class PhaserExample { public static void main(String[] args) { Phaser phaser = new Phaser(3); for (int i = 1; i <= 3; i++) { new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + " 开始执行任务"); // 模拟任务执行时间 Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成任务第一阶段"); phaser.arriveAndAwaitAdvance(); System.out.println(Thread.currentThread().getName() + " 开始执行第二阶段任务"); Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成第二阶段任务"); phaser.arriveAndAwaitAdvance(); System.out.println(Thread.currentThread().getName() + " 开始执行第三阶段任务"); Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成第三阶段任务"); phaser.arriveAndDeregister(); } catch (InterruptedException e) { e.printStackTrace(); } }, "线程 " + i).start(); } } } ```
admin
2025年3月6日 12:06
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Rancher
Jenkins
ADMIN-UI
VBEN-ADMIN-UI
RUST-FS
MinIO
mindoc
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码