同事突然考我1000 个线程同时运行,怎么防止不卡?

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:掘金/C站/腾讯云/阿里云/华为云/51CTO(全网同号);欢迎大家常来逛逛,互相学习。

今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

问题描述

我同事突然问我说,假若现在有个需求场景,当我们需要同时启动1000个线程时,系统的负载和资源竞争会急剧增加,可能导致系统变得不响应甚至崩溃。为了防止系统因为线程过多而卡住,确保系统能够平稳运行,我们需要采取一些策略来优化线程的管理和资源利用。那么你们有啥好的方式能够进行优化?如果你们暂时还无头绪,没关系,看完我推荐的这几种后,你们就不能再说自己不会需要研究一下了。以下是几种有效的方法来防止1000个线程同时运行时卡顿的解决方案,仅供参考。

解决方案

1. 使用线程池(ExecutorService

直接启动1000个线程可能会导致系统资源耗尽,尤其是当线程数过多时,操作系统和JVM会为每个线程分配一定的资源。线程池(如ExecutorService)可以有效地控制线程数量,通过复用线程和任务调度来优化性能。线程池的核心概念是使用固定大小的线程池来执行任务,而不是每次都创建新的线程。

示例代码

如下是示例代码,仅供参考:

java 复制代码
import java.util.concurrent.*;

/**
 * @Author 喵手
 * @date: 2025-04-14
 */
public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);  // 线程池大小为10

        // 提交1000个任务
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> {
                try {
                    // 模拟任务处理
                    System.out.println(Thread.currentThread().getName() + " is running");
                    Thread.sleep(1000);  // 模拟耗时任务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 关闭线程池
        executor.shutdown();
    }
}

原理

线程池的大小设置为10,意味着最多同时运行10个线程,其余的任务会等待线程池中的线程空闲出来执行。这就避免了直接启动1000个线程的资源消耗。

运行截图展示

  • 因为线程池的大小是10,所以在任何时刻最多只有10个线程在运行,其它线程将等待,直到一个线程执行完成,才会有新的线程开始执行。
  • 每个线程会打印出类似于 pool-1-thread-1 is running 的消息,表示当前线程正在执行任务。

这种方式适用于当需要执行大量独立任务时,可以利用线程池来避免每个任务都重新创建线程,从而提高性能。

如下是正式环境演示截图:

代码解析

如下是我对上述案例代码的详细剖析,目的就是为了帮助大家更好的理解代码,吃透原理。如上这段案例代码演示了如何使用Java中的线程池(ExecutorService)来并行处理多个任务,具体是创建了一个固定大小的线程池来执行1000个任务。下面是代码的逐行解析:

1. 引入必要的库:

java 复制代码
import java.util.concurrent.*;

这行代码导入了Java并发编程包中的核心类。ExecutorService 是用于管理线程池的接口,Executors 是一个工厂类,提供了各种创建线程池的静态方法。

2. 主类与 main 方法:

java 复制代码
public class ThreadPoolExample {
    public static void main(String[] args) {

这段代码定义了一个名为 ThreadPoolExample 的公共类,并声明了 main 方法,这是程序执行的入口点。

3. 创建线程池:

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(10);  // 线程池大小为10
  • Executors.newFixedThreadPool(10) 创建了一个固定大小的线程池,大小为10。也就是说,最多同时运行10个线程。
  • ExecutorService 是一个接口,它提供了管理线程池的一些常用方法,如提交任务、关闭线程池等。

4. 提交任务:

java 复制代码
for (int i = 0; i < 1000; i++) {
    executor.submit(() -> {
        try {
            // 模拟任务处理
            System.out.println(Thread.currentThread().getName() + " is running");
            Thread.sleep(1000);  // 模拟耗时任务
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}
  • 这段代码创建了一个循环,从0到999提交了1000个任务到线程池。
  • executor.submit(() -> {...}) 使用Lambda表达式提交了一个任务。每个任务的内容是:
    • 打印当前线程的名称,表示哪个线程在执行任务。
    • 模拟一个耗时任务,使用 Thread.sleep(1000) 来让线程暂停1秒钟。

在这个例子中,任务只是简单的打印当前线程名称并休眠,实际应用中这些任务可以是任何长时间运行的操作,如文件处理、数据库操作等。

5. 关闭线程池:

java 复制代码
executor.shutdown();
  • executor.shutdown() 会在所有已提交的任务完成后,关闭线程池。这是一个优雅的关闭方式,它不会立即停止正在执行的任务,而是停止接受新的任务,并在所有任务完成后关闭线程池。
  • 如果你希望立即终止线程池中的任务,可以使用 shutdownNow(),但这种方法不保证所有任务会被执行。

总结:

  • 线程池大小:创建了一个固定大小为10的线程池,意味着最多可以同时执行10个任务。
  • 提交任务:通过循环提交1000个任务,每个任务模拟了一秒钟的耗时。
  • 线程池的管理 :通过 submit() 提交任务,shutdown() 关闭线程池。

2. 限制最大并发线程数

即使使用线程池,仍然需要注意限制同时运行的线程数。如果线程池配置过大,仍然会消耗大量的资源,导致卡顿。可以通过调整线程池的大小来优化并发处理能力。

在创建线程池时,可以根据系统的硬件资源(如CPU和内存)来确定适当的线程池大小。一个常见的经验法则是线程池的大小可以设置为CPU核心数的两倍(例如2 * CPU核心数),但这也取决于任务的性质和其他因素。

示例代码

如下是示例代码,仅供参考:

java 复制代码
import java.util.concurrent.*;

/**
 * @Author 喵手
 * @date: 2025-04-14
 */
public class CPUThreadPoolExample {
    public static void main(String[] args) {
        // 获取系统的CPU核心数
        int cpuCount = Runtime.getRuntime().availableProcessors();
        System.out.println("CPU核心数: " + cpuCount);

        // 设置线程池大小为2倍CPU核心数
        ExecutorService executor = Executors.newFixedThreadPool(cpuCount * 2);
        System.out.println("线程池大小: " + (cpuCount * 2));

        // 提交任务到线程池
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " is executing the task");
                    Thread.sleep(1000);  // 模拟任务耗时1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}

原理

  • 线程池大小的选择 :选择线程池大小为 cpuCount * 2,对于大多数 I/O 密集型应用是合理的,因为 I/O 操作往往会等待外部资源(如数据库、网络等),此时 CPU 可以空闲出来处理其他任务。但如果任务是 CPU 密集型的,通常线程池大小应为 CPU 核心数。

  • 并发控制:通过线程池并发执行多个任务,而不是为每个任务创建新的线程,从而避免了线程创建的开销,确保系统性能和资源的高效利用。

运行截图展示

如下是正式环境演示截图:

代码解析

如下是我对上述案例代码的详细剖析,目的就是为了帮助大家更好的理解代码,吃透原理。你的代码看起来非常好!它展示了如何根据系统的 CPU 核心数来动态设置线程池的大小,并提交多个任务到线程池进行并发执行。代码的结构清晰且符合并发编程的最佳实践。

1. 获取 CPU 核心数:

java 复制代码
int cpuCount = Runtime.getRuntime().availableProcessors();
System.out.println("CPU核心数: " + cpuCount);
  • 使用 Runtime.getRuntime().availableProcessors() 获取系统的 CPU 核心数。这个方法返回的值表示你机器上可用的处理器核心数。在多核机器上,这个值通常会大于1。

2. 设置线程池大小:

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(cpuCount * 2);
System.out.println("线程池大小: " + (cpuCount * 2));
  • 这里创建了一个固定大小的线程池,线程池的大小是 cpuCount * 2。这是因为通常在计算密集型任务中,你可以让线程池大小为 CPU 核心数的两倍,这样线程可以在等待 I/O 操作时利用 CPU 执行计算任务。
  • 比如,如果你的机器有 4 个 CPU 核心,线程池的大小就会被设置为 8。

3. 提交任务:

java 复制代码
for (int i = 0; i < 1000; i++) {
    executor.submit(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + " is executing the task");
            Thread.sleep(1000);  // 模拟任务耗时1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}
  • 这段代码提交了 1000 个任务到线程池。每个任务的内容是打印当前线程的名称并模拟一个 1 秒的耗时操作。
  • 任务通过 executor.submit() 被提交给线程池执行,而线程池会调度合适的线程来执行这些任务。
  • 由于线程池的大小为 cpuCount * 2,最多在同一时刻会有这么多任务并发执行。

4. 关闭线程池:

java 复制代码
executor.shutdown();
  • shutdown() 会优雅地关闭线程池。它会停止接收新任务,并在所有已提交的任务完成后关闭线程池。
  • 请注意,shutdown() 并不会立即终止正在执行的任务,而是等待所有任务执行完毕后才会关闭线程池。

总结:   这个程序展示了如何通过 ExecutorService 管理线程池来并发执行多个任务,并根据 CPU 核心数动态调整线程池大小,达到更高效的并行执行效果。通过合理设置线程池的大小和任务调度,你能够高效地利用系统资源,避免过度创建线程和线程管理的开销。

3. 使用CompletableFuture(异步编程)

Java 8引入了CompletableFuture类,提供了一种更优雅的方式来管理异步任务。如果任务本身是独立的,并且可以异步执行,使用CompletableFuture可以提高效率,避免不必要的线程阻塞。

示例代码

如下是示例代码,仅供参考:

java 复制代码
import java.util.concurrent.*;

/**
 * @Author 喵手
 * @date: 2025-04-14
 */
public class CompletableFutureExample {
    public static void main(String[] args) {
        int numThreads = 10;
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);

        // 提交1000个异步任务
        for (int i = 0; i < 1000; i++) {
            CompletableFuture.runAsync(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " is running");
                    Thread.sleep(1000);  // 模拟耗时任务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, executor);
        }

        // 关闭线程池
        executor.shutdown();
    }
}

原理

  • CompletableFuture.runAsync()用于异步执行任务,这些任务在后台线程池中执行,不会阻塞主线程。
  • runAsync()方法默认是无返回值的异步任务,如果任务需要返回值,可以使用CompletableFuture.supplyAsync()

运行截图展示

  1. 并发执行:因为线程池最多只有10个线程在运行,所以最多同时有10个任务在执行,其他任务会等待。
  2. 输出 :每个任务会打印类似于 pool-1-thread-1 is running 的信息,表示当前的线程在执行任务。任务间的执行顺序是不确定的,具体哪一个线程执行哪个任务取决于线程调度。
  3. 执行效率:任务是异步执行的,主线程提交任务后不会被阻塞,而是继续向下执行。这使得可以高效地提交大量任务并进行并发处理。

如下是正式环境演示截图:

代码解析

如下是我对上述案例代码的详细剖析,目的就是为了帮助大家更好的理解代码,吃透原理。如上这段案例代码展示了如何使用 CompletableFuture 来处理异步任务,并结合线程池来并发执行任务。CompletableFuture 是Java 8引入的一个强大工具,它提供了一个非常灵活和强大的异步编程模型。接下来,我会逐行解析代码的功能和原理:

1. 引入必要的库:

java 复制代码
import java.util.concurrent.*;

这里引入了Java并发编程包中的核心类。CompletableFuture 是用于处理异步任务的类,而 ExecutorServiceExecutors 用来管理线程池。

2. 主类与 main 方法:

java 复制代码
public class CompletableFutureExample {
    public static void main(String[] args) {

这段代码定义了一个名为 CompletableFutureExample 的公共类,并声明了 main 方法,程序的入口点。

3. 创建线程池:

java 复制代码
int numThreads = 10;
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
  • numThreads 设置了线程池的大小为10,这意味着线程池最多同时可以有10个线程在执行任务。
  • Executors.newFixedThreadPool(numThreads) 创建了一个固定大小的线程池,线程池的大小是 numThreads,这里是10。

4. 提交异步任务:

java 复制代码
for (int i = 0; i < 1000; i++) {
    CompletableFuture.runAsync(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + " is running");
            Thread.sleep(1000);  // 模拟耗时任务
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, executor);
}

这段代码向线程池提交了1000个异步任务,具体功能如下:

  • CompletableFuture.runAsync() 用来提交一个异步任务,这个任务通过 Runnable 接口表示。
  • () -> {...} 是一个Lambda表达式,表示异步执行的任务内容。每个任务打印当前线程的名称,并模拟一个1秒钟的耗时任务(使用 Thread.sleep(1000))。
  • runAsync() 方法有两个参数:
    • 第一个参数是一个 Runnable 对象,表示要执行的任务。
    • 第二个参数是 Executor,指定任务将在哪个线程池中执行。这里将线程池 executor 作为参数传入,确保任务使用线程池中的线程来执行。

5. 关闭线程池:

java 复制代码
executor.shutdown();
  • executor.shutdown() 会在所有已提交的任务完成后,关闭线程池。与 ExecutorServicesubmit() 方法不同,这里是使用 runAsync() 提交的异步任务,shutdown() 确保线程池中的所有任务完成后才会关闭。
  • shutdown() 并不会立即停止正在执行的任务,它会等待已提交的任务完成后再关闭线程池。

总结:

  • 线程池大小:创建了一个固定大小为10的线程池,最多允许同时有10个线程执行任务。
  • 提交异步任务 :通过 CompletableFuture.runAsync() 提交了1000个异步任务。每个任务都在独立的线程中运行,不会阻塞主线程。
  • 异步执行CompletableFuture.runAsync() 是一个异步执行方法,它不会阻塞主线程。它会直接返回一个 CompletableFuture 对象,但主线程继续执行,而任务会在后台线程池中的线程上异步执行。
  • 线程池管理 :通过 shutdown() 方法关闭线程池。 进一步说明:
  • CompletableFuture 是一个非常强大的工具,可以在异步任务完成时执行回调,或者将多个异步操作组合在一起进行处理。可以通过 thenRun(), thenApply(), thenCompose() 等方法进一步扩展异步操作的处理逻辑,进行链式调用。
  • 在这个例子中,虽然任务是异步的,但 runAsync() 没有返回任何结果,因此仅仅是执行任务。如果需要返回值,可以使用 CompletableFuture.supplyAsync()

这个示例展示了如何使用 CompletableFuture 来提高并发性能,同时避免了在多线程中使用传统的回调方法或显式的 Thread 创建。

4. 控制任务的提交速率

如果任务的提交速率过高(即一次性提交1000个任务),即使使用线程池,系统也可能会被压垮。通过限制提交任务的速率,可以有效避免系统被瞬时大量任务拖慢。常见的方式有:

  • 限流 :通过控制任务提交的速率,避免系统过载。可以使用如SemaphoreRateLimiter等技术来实现。
  • 批处理:将任务分批次提交,而不是一次性提交所有任务。

示例:使用Semaphore进行限流

如下是示例代码,仅供参考:

java 复制代码
import java.util.concurrent.*;

public class RateLimitedExecutor {
    private static final int MAX_CONCURRENT_TASKS = 10;  // 最大并发任务数
    private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_TASKS);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 1000; i++) {
            // 获取许可
            semaphore.acquire();
            executor.submit(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " is running");
                    Thread.sleep(1000);  // 模拟任务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放许可
                    semaphore.release();
                }
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}

原理

使用Semaphore控制最大并发数,确保最多只有MAX_CONCURRENT_TASKS个任务同时运行,其他任务会等待。

运行截图展示

  1. 任务并发控制:最多有10个任务同时在执行,而其他任务则会等待。
  2. 输出 :每个任务会打印出当前线程的名称,例如 pool-1-thread-1 is running,表示某个线程正在执行任务。由于有并发控制,所以你会看到最多10个线程并发执行。
  3. 任务延迟 :由于每个任务模拟了1秒的延时(Thread.sleep(1000)),所以执行时间会比较长,但并发任务的数量被限制在10个内。

如下是正式环境演示截图:

代码解析

如下是我对上述案例代码的详细剖析,目的就是为了帮助大家更好的理解代码,吃透原理。如上这段案例代码实现了一个基于信号量(Semaphore)的并发任务控制,限制了同时运行的任务数,以防止线程池中的任务过多,避免资源被耗尽。代码的关键在于使用 Semaphore 来控制最大并发任务数。下面我会逐行解析代码的原理和细节。

1. 引入必要的库:

java 复制代码
import java.util.concurrent.*;

这行代码引入了Java的并发包中的关键类:

  • ExecutorService:管理线程池,提交和执行任务。
  • Executors:用于创建不同类型的线程池的工具类。
  • Semaphore:用于控制并发访问共享资源的信号量。

2. 定义常量和信号量:

java 复制代码
private static final int MAX_CONCURRENT_TASKS = 10;  // 最大并发任务数
private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_TASKS);
  • MAX_CONCURRENT_TASKS 设置了最大并发任务数,这里设为10,意味着最多同时运行10个任务。
  • semaphore 是一个信号量,用来控制允许同时访问的任务数。Semaphore 的构造方法接收一个整数参数,表示允许并发的许可证数。这里,信号量的值为10,表示最多同时有10个线程可以运行。

3. 主方法及线程池初始化:

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(10);
  • Executors.newFixedThreadPool(10) 创建了一个固定大小的线程池,大小为10,意味着线程池最多同时运行10个线程。
  • executor 变量将用于提交任务到线程池。

4. 提交任务:

java 复制代码
for (int i = 0; i < 1000; i++) {
    // 获取许可
    semaphore.acquire();
    executor.submit(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + " is running");
            Thread.sleep(1000);  // 模拟任务
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放许可
            semaphore.release();
        }
    });
}
  • 通过一个 for 循环提交了1000个任务到线程池。
  • 获取许可
    • semaphore.acquire() 方法会阻塞当前线程,直到信号量中有可用的许可为止。如果信号量的许可已用尽,调用线程将被阻塞,直到有其他线程释放许可。
    • 这保证了在任意时刻,最多只有 MAX_CONCURRENT_TASKS(即10个)任务是并发执行的。
  • 提交任务
    • executor.submit() 方法将一个 Runnable 任务提交到线程池。这个任务只是打印当前线程的名称,模拟一个耗时的操作(Thread.sleep(1000))。
    • 每个任务执行完毕后都会释放信号量,以便其他被阻塞的任务能够获得许可并执行。
  • 释放许可
    • semaphore.release() 方法释放一个许可,允许其他等待的任务继续执行。
    • finally 块保证即使任务抛出异常,信号量也会被释放,避免导致死锁。

5. 关闭线程池:

java 复制代码
executor.shutdown();
  • executor.shutdown() 会在所有提交的任务完成后关闭线程池,停止接受新的任务。这是一个优雅的关闭方式,确保所有任务都完成后才会关闭线程池。

6. 总结与关键点:

  • 信号量控制并发数 :通过 Semaphore 来限制最大并发任务数。semaphore.acquire() 用于阻塞任务直到可以执行,而 semaphore.release() 用于释放许可。
  • 任务提交与线程池:任务被提交到一个固定大小的线程池中,线程池的大小是10,因此最多只有10个线程在同一时刻运行。
  • 防止线程过多:即使你提交了1000个任务,信号量限制确保了在任何时刻都不会超过10个任务并发执行。其他任务会被阻塞,直到有线程空闲出来。

使用 Semaphore 的好处:

  • 控制并发数Semaphore 允许我们非常精确地控制同时运行的任务数。对于某些资源有限的场景(比如数据库连接池、网络带宽等),可以通过信号量来保证不会超出资源的最大限制。
  • 避免线程过多导致的资源浪费 :如果没有信号量的控制,提交大量任务时线程池可能会创建更多的线程,导致系统资源的过度消耗,而 Semaphore 则通过限制并发数来避免这种情况。

这种方式适用于你希望对并发任务进行精细控制的场景,特别是当任务数较大,而同时运行的任务数应受限时。

5. 使用异步IO与非阻塞编程

对于IO密集型任务(如网络请求、文件读写),阻塞式的线程处理会浪费大量资源。可以使用异步IO或非阻塞编程模型,如Java的NIONetty等,来处理大规模的并发任务。

通过异步IO,可以在等待IO操作完成的同时,让CPU去做其他的工作,避免了线程阻塞。

6. 监控与调优

在系统中运行大量线程时,监控线程的使用情况至关重要。可以通过jconsolejvisualvm等工具来监控线程池的使用情况和资源消耗,及时调整线程池大小和任务策略,确保系统始终保持在合理的负载范围内。

总结

总而言之,若你要启动1000个线程时,为了防止系统卡顿,我们需要采取多种措施来合理管理线程和资源:

  1. 使用线程池 (如ExecutorService)来控制最大并发线程数,避免线程过多。
  2. 调整线程池大小,确保线程池不超负荷。
  3. 异步编程 (如使用CompletableFuture)能提高任务执行的效率。
  4. 限制任务提交速率,避免任务瞬时过载。
  5. 监控与调优,根据系统负载进行适时调整。

通过这些方法,我们可以有效地管理大量并发线程,避免卡顿和性能瓶颈,确保系统的平稳运行。

... ...

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

... ...

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

相关推荐
爱的叹息20 分钟前
AI推荐系统的详细解析 +推荐系统中滤泡效应(Filter Bubble)的详细解析+ 基于Java构建电商推荐系统的分步实现方案,结合机器学习与工程实践
java·人工智能·机器学习
勇哥java实战分享22 分钟前
聊聊四种实时通信技术:长轮询、短轮询、WebSocket 和 SSE
后端
sinat_2622921123 分钟前
Java面试实战:谢飞机的求职记 - Spring Boot、Redis与微服务技术问答解析
java·spring boot·redis·微服务·分布式事务
东方芷兰24 分钟前
Javase 基础入门 —— 02 基本数据类型
java·开发语言·笔记·spring·intellij-idea·idea
pwzs25 分钟前
掌握常见 HTTP 方法:GET、POST、PUT 到 CONNECT 全面梳理
java·后端·http
chendilincd32 分钟前
C++ 的史诗级进化:从C++98到C++20
java·c++·c++20
独行soc1 小时前
2025年渗透测试面试题总结-拷打题库08(题目+回答)
java·linux·运维·服务器·python·面试·职场和发展
~欸嘿1 小时前
pdf多文件合并
java·pdf
IT可乐1 小时前
人人都可以做个满血版的Manus智能体了
后端
艾文伯特1 小时前
Maven集成模块打包&使用
java·maven