哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:掘金/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()
。
运行截图展示
- 并发执行:因为线程池最多只有10个线程在运行,所以最多同时有10个任务在执行,其他任务会等待。
- 输出 :每个任务会打印类似于
pool-1-thread-1 is running
的信息,表示当前的线程在执行任务。任务间的执行顺序是不确定的,具体哪一个线程执行哪个任务取决于线程调度。 - 执行效率:任务是异步执行的,主线程提交任务后不会被阻塞,而是继续向下执行。这使得可以高效地提交大量任务并进行并发处理。
如下是正式环境演示截图:

代码解析
如下是我对上述案例代码的详细剖析,目的就是为了帮助大家更好的理解代码,吃透原理。如上这段案例代码展示了如何使用 CompletableFuture
来处理异步任务,并结合线程池来并发执行任务。CompletableFuture
是Java 8引入的一个强大工具,它提供了一个非常灵活和强大的异步编程模型。接下来,我会逐行解析代码的功能和原理:
1. 引入必要的库:
java
import java.util.concurrent.*;
这里引入了Java并发编程包中的核心类。CompletableFuture
是用于处理异步任务的类,而 ExecutorService
和 Executors
用来管理线程池。
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()
会在所有已提交的任务完成后,关闭线程池。与ExecutorService
的submit()
方法不同,这里是使用runAsync()
提交的异步任务,shutdown()
确保线程池中的所有任务完成后才会关闭。shutdown()
并不会立即停止正在执行的任务,它会等待已提交的任务完成后再关闭线程池。
总结:
- 线程池大小:创建了一个固定大小为10的线程池,最多允许同时有10个线程执行任务。
- 提交异步任务 :通过
CompletableFuture.runAsync()
提交了1000个异步任务。每个任务都在独立的线程中运行,不会阻塞主线程。 - 异步执行 :
CompletableFuture.runAsync()
是一个异步执行方法,它不会阻塞主线程。它会直接返回一个CompletableFuture
对象,但主线程继续执行,而任务会在后台线程池中的线程上异步执行。 - 线程池管理 :通过
shutdown()
方法关闭线程池。 进一步说明: CompletableFuture
是一个非常强大的工具,可以在异步任务完成时执行回调,或者将多个异步操作组合在一起进行处理。可以通过thenRun()
,thenApply()
,thenCompose()
等方法进一步扩展异步操作的处理逻辑,进行链式调用。- 在这个例子中,虽然任务是异步的,但
runAsync()
没有返回任何结果,因此仅仅是执行任务。如果需要返回值,可以使用CompletableFuture.supplyAsync()
。
这个示例展示了如何使用 CompletableFuture
来提高并发性能,同时避免了在多线程中使用传统的回调方法或显式的 Thread
创建。
4. 控制任务的提交速率
如果任务的提交速率过高(即一次性提交1000个任务),即使使用线程池,系统也可能会被压垮。通过限制提交任务的速率,可以有效避免系统被瞬时大量任务拖慢。常见的方式有:
- 限流 :通过控制任务提交的速率,避免系统过载。可以使用如
Semaphore
、RateLimiter
等技术来实现。 - 批处理:将任务分批次提交,而不是一次性提交所有任务。
示例:使用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
个任务同时运行,其他任务会等待。
运行截图展示
- 任务并发控制:最多有10个任务同时在执行,而其他任务则会等待。
- 输出 :每个任务会打印出当前线程的名称,例如
pool-1-thread-1 is running
,表示某个线程正在执行任务。由于有并发控制,所以你会看到最多10个线程并发执行。 - 任务延迟 :由于每个任务模拟了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的NIO
、Netty
等,来处理大规模的并发任务。
通过异步IO,可以在等待IO操作完成的同时,让CPU去做其他的工作,避免了线程阻塞。
6. 监控与调优
在系统中运行大量线程时,监控线程的使用情况至关重要。可以通过jconsole
、jvisualvm
等工具来监控线程池的使用情况和资源消耗,及时调整线程池大小和任务策略,确保系统始终保持在合理的负载范围内。
总结
总而言之,若你要启动1000个线程时,为了防止系统卡顿,我们需要采取多种措施来合理管理线程和资源:
- 使用线程池 (如
ExecutorService
)来控制最大并发线程数,避免线程过多。 - 调整线程池大小,确保线程池不超负荷。
- 异步编程 (如使用
CompletableFuture
)能提高任务执行的效率。 - 限制任务提交速率,避免任务瞬时过载。
- 监控与调优,根据系统负载进行适时调整。
通过这些方法,我们可以有效地管理大量并发线程,避免卡顿和性能瓶颈,确保系统的平稳运行。
... ...
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
... ...
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!