你好呀,我是老码农张三,一个专注于 Java 并发编程的“老司机”。今天,咱们聊聊在高并发场景下,如何玩转 Java 并发工具箱里的“红绿灯”——Semaphore,并把它调教得服服帖帖,性能杠杠的!
1. Semaphore 是什么?
简单来说,Semaphore 就像一个“许可证”管理器。你可以设定一个“许可证”的数量,线程想要执行某个操作,就必须先“获取”一个许可证。如果许可证用完了,线程就只能“排队”等候,直到有许可证被“释放”。
1.1 核心概念
- 许可证 (Permits):Semaphore 的核心,代表了允许多少个线程同时访问共享资源。
acquire()
:尝试获取一个许可证。如果许可证可用,则立即获取;如果不可用,则阻塞等待。release()
:释放一个许可证,供其他线程使用。- 公平模式 (Fair Mode):线程按照请求的先后顺序获取许可证,先到先得。
- 非公平模式 (Nonfair Mode):允许线程“插队”,可能导致某些线程长期无法获取许可证。
1.2 为什么需要 Semaphore?
在高并发场景下,我们需要对共享资源的访问进行限制,防止资源被过度使用,导致系统崩溃。比如:
- 数据库连接池:限制同时连接数据库的线程数量。
- 线程池:控制并发执行的任务数量。
- 资源访问:限制对文件、网络等资源的并发访问。
2. Semaphore 的两种模式:公平与非公平
Semaphore 提供了两种模式:公平模式和非公平模式。这两种模式在性能和公平性之间做了权衡,我们需要根据实际情况进行选择。
2.1 公平模式 (Fair Mode)
- 特点:
- 线程按照请求的先后顺序获取许可证,保证了线程的公平性。
- 避免了“饥饿”问题,即某些线程长时间无法获取许可证。
- 实现原理:
- 内部使用一个 FIFO (First-In-First-Out) 队列来管理等待的线程。
acquire()
方法首先检查是否有可用的许可证,如果没有,则将线程加入队列,并阻塞等待。release()
方法释放许可证时,从队列头部取出线程,并唤醒它。
- 代码示例:
import java.util.concurrent.Semaphore;
public class FairSemaphoreExample {
private static final int PERMITS = 2; // 许可证数量
private static final Semaphore semaphore = new Semaphore(PERMITS, true); // 公平模式
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
final int threadId = i;
new Thread(() -> {
try {
System.out.println("线程 " + threadId + " 尝试获取许可证");
semaphore.acquire();
System.out.println("线程 " + threadId + " 获取到许可证,开始执行任务");
Thread.sleep(2000); // 模拟任务执行时间
System.out.println("线程 " + threadId + " 释放许可证");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}).start();
}
}
}
- 适用场景:
- 对公平性要求较高的场景,例如:
- 某些场景下,如果线程长期无法获取资源,可能导致业务逻辑出现问题。
- 需要避免“饥饿”问题,确保所有线程都有机会获取资源。
- 对公平性要求较高的场景,例如:
2.2 非公平模式 (Nonfair Mode)
- 特点:
- 允许线程“插队”,优先获取许可证。
- 可能导致某些线程长期无法获取许可证,出现“饥饿”问题。
- 性能通常比公平模式更好,因为减少了线程切换的开销。
- 实现原理:
acquire()
方法首先尝试直接获取许可证,如果成功,则立即返回。- 如果获取失败,才会将线程加入队列,并阻塞等待。
release()
方法释放许可证时,优先尝试唤醒等待队列中的线程。
- 代码示例:
import java.util.concurrent.Semaphore;
public class NonfairSemaphoreExample {
private static final int PERMITS = 2; // 许可证数量
private static final Semaphore semaphore = new Semaphore(PERMITS, false); // 非公平模式
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
final int threadId = i;
new Thread(() -> {
try {
System.out.println("线程 " + threadId + " 尝试获取许可证");
semaphore.acquire();
System.out.println("线程 " + threadId + " 获取到许可证,开始执行任务");
Thread.sleep(2000); // 模拟任务执行时间
System.out.println("线程 " + threadId + " 释放许可证");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}).start();
}
}
}
- 适用场景:
- 对性能要求较高的场景,例如:
- 需要尽可能提高系统的吞吐量。
- 可以容忍一定程度的“饥饿”问题。
- 对性能要求较高的场景,例如:
2.3 公平模式 vs 非公平模式:如何选择?
特性 | 公平模式 | 非公平模式 | 选择建议 |
---|---|---|---|
公平性 | 保证线程按照请求顺序获取许可证,避免“饥饿”问题 | 允许线程“插队”,可能导致“饥饿”问题 | 如果对公平性要求高,或者需要避免“饥饿”问题,则选择公平模式。 |
性能 | 性能通常比非公平模式差 | 性能通常比公平模式好,吞吐量更高 | 如果对性能要求高,并且可以容忍一定程度的“饥饿”问题,则选择非公平模式。 |
实现复杂度 | 相对简单 | 相对复杂 | 在性能满足需求的前提下,优先考虑公平模式,降低代码复杂度。 |
经验之谈:
- 在大多数情况下,非公平模式的性能更好,是首选。但要确保你的业务场景可以容忍一定程度的“饥饿”问题。
- 如果你的业务场景对公平性有严格要求,或者需要避免“饥饿”问题,则选择公平模式。
- 你可以通过测试来评估不同模式下的性能表现,从而做出最佳选择。
3. Semaphore 性能调优策略
除了选择合适的模式,我们还可以通过一些技巧来优化 Semaphore 的性能。
3.1 调整许可证数量
- 核心思想:合理设置许可证数量,既要充分利用系统资源,又要避免资源过度竞争。
- 如何确定许可证数量:
- 观察系统资源:监控 CPU、内存、磁盘 I/O 等资源的使用情况。
- 测试:通过压力测试,模拟高并发场景,观察不同许可证数量下的系统性能表现(吞吐量、响应时间、错误率等)。
- 经验值:
- 如果你的任务是 CPU 密集型的,可以设置许可证数量为 CPU 核心数或略大于 CPU 核心数。
- 如果你的任务是 I/O 密集型的,可以适当增加许可证数量,提高 I/O 并发度。
- 过多的许可证:可能导致资源竞争加剧,降低系统性能。
- 过少的许可证:可能导致系统吞吐量下降,资源利用率不足。
3.2 避免长时间持有许可证
- 核心思想:尽快释放许可证,让其他线程有机会获取资源。
- 常见问题:
- 在
acquire()
和release()
之间执行耗时操作,例如:网络请求、数据库查询等。 - 在持有许可证期间发生异常,导致许可证无法释放。
- 在
- 优化方案:
- 缩短临界区:尽量减少
acquire()
和release()
之间的代码量,只保留必要的共享资源访问操作。 - 使用
try-finally
块:确保在finally
块中释放许可证,即使发生异常也能保证资源释放。 - 异步操作:对于耗时操作,可以考虑使用异步方式,例如:使用线程池执行耗时任务,避免长时间占用许可证。
- 缩短临界区:尽量减少
import java.util.concurrent.Semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AsyncSemaphoreExample {
private static final int PERMITS = 2; // 许可证数量
private static final Semaphore semaphore = new Semaphore(PERMITS);
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
final int threadId = i;
executor.submit(() -> {
try {
semaphore.acquire();
System.out.println("线程 " + threadId + " 获取到许可证,开始执行任务");
// 使用异步方式执行耗时操作
executor.submit(() -> {
try {
Thread.sleep(2000); // 模拟耗时操作
System.out.println("线程 " + threadId + " 异步任务完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
System.out.println("线程 " + threadId + " 释放许可证");
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
}
3.3 减少竞争
- 核心思想:降低线程之间的竞争,提高并发性能。
- 优化方案:
- 减小临界区:前面已经提到,缩短
acquire()
和release()
之间的代码量。 - 使用局部变量:如果可能,尽量使用局部变量,避免共享变量的并发访问。
- 使用锁分离:如果共享资源可以被分割成多个部分,可以考虑使用锁分离技术,例如:读写锁 (ReadWriteLock)。
- 减小临界区:前面已经提到,缩短
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private static final ReadWriteLock lock = new ReentrantReadWriteLock();
private static int data = 0;
public static void main(String[] args) {
// 模拟多个线程读取数据
for (int i = 0; i < 3; i++) {
new Thread(() -> {
lock.readLock().lock(); // 获取读锁
try {
System.out.println("线程 " + Thread.currentThread().getName() + " 读取数据: " + data);
Thread.sleep(1000); // 模拟读取耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.readLock().unlock(); // 释放读锁
}
}, "Reader-" + i).start();
}
// 模拟一个线程写入数据
new Thread(() -> {
lock.writeLock().lock(); // 获取写锁
try {
System.out.println("线程 " + Thread.currentThread().getName() + " 写入数据");
data = 100; // 模拟写入数据
Thread.sleep(2000); // 模拟写入耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}, "Writer").start();
}
}
3.4 监控和调优
- 核心思想:通过监控系统性能指标,发现潜在问题,并进行调优。
- 监控指标:
- Semaphore 的等待线程数:如果等待线程数持续增加,说明许可证数量不足,或者线程持有许可证的时间过长。
- 系统 CPU 使用率:如果 CPU 使用率过高,可能存在资源竞争,或者线程上下文切换开销过大。
- 系统内存使用率:如果内存使用率过高,可能存在内存泄漏,或者线程创建过多。
- 线程状态:监控线程的状态,例如:阻塞、运行、等待等。
- 调优手段:
- 调整许可证数量:根据监控结果,调整许可证数量。
- 优化代码:优化临界区代码,减少竞争,缩短持有许可证的时间。
- 调整线程池配置:调整线程池的线程数量,优化线程调度策略。
- 使用性能分析工具:使用 JProfiler、VisualVM 等工具,分析系统性能瓶颈。
4. 结合实际案例分析
咱们来结合几个实际案例,看看如何在高并发场景下使用 Semaphore 进行性能优化。
4.1 数据库连接池
- 场景描述:
- 在高并发 Web 应用中,数据库连接是宝贵的资源。
- 如果数据库连接数量超过限制,会导致数据库连接超时,甚至数据库崩溃。
- 解决方案:
- 使用 Semaphore 来限制同时连接数据库的线程数量。
- 将数据库连接封装成一个资源,使用
acquire()
和release()
来控制连接的获取和释放。
- 代码示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.Semaphore;
public class ConnectionPool {
private static final int MAX_CONNECTIONS = 10; // 最大连接数
private static final String DB_URL = "jdbc:mysql://localhost:3306/test";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
private static final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS);
public Connection getConnection() throws InterruptedException, SQLException {
semaphore.acquire(); // 获取许可证
try {
// 创建数据库连接
return DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
} catch (SQLException e) {
semaphore.release(); // 释放许可证
throw e;
}
}
public void releaseConnection(Connection connection) {
if (connection != null) {
try {
connection.close(); // 关闭连接
} catch (SQLException e) {
// 忽略异常
} finally {
semaphore.release(); // 释放许可证
}
}
}
public static void main(String[] args) {
ConnectionPool pool = new ConnectionPool();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
Connection connection = null;
try {
System.out.println("线程 " + Thread.currentThread().getName() + " 尝试获取连接");
connection = pool.getConnection();
System.out.println("线程 " + Thread.currentThread().getName() + " 获取到连接,开始执行任务");
// 使用连接执行数据库操作
Thread.sleep(1000); // 模拟数据库操作
} catch (InterruptedException | SQLException e) {
Thread.currentThread().interrupt();
} finally {
pool.releaseConnection(connection);
System.out.println("线程 " + Thread.currentThread().getName() + " 释放连接");
}
}).start();
}
}
}
- 优化建议:
- 配置合适的
MAX_CONNECTIONS
:根据数据库的连接限制和系统的负载情况,调整MAX_CONNECTIONS
的值。 - 使用连接池技术:可以使用 Apache DBCP、HikariCP 等连接池技术,提高连接的复用率,减少连接的创建和销毁开销。
- 监控连接状态:监控数据库连接的活跃数、空闲数、等待数等指标,及时发现问题。
- 配置合适的
4.2 限制文件并发访问
- 场景描述:
- 在多线程环境下,对文件的并发读写需要进行控制,防止数据损坏。
- 例如:多个线程同时写入同一个文件,可能导致数据混乱。
- 解决方案:
- 使用 Semaphore 来限制同时访问文件的线程数量。
- 根据读写场景,可以设置读锁和写锁,允许多个线程同时读取文件,但只允许一个线程写入文件。
- 代码示例:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class FileAccessControl {
private static final int MAX_WRITERS = 1; // 最大写线程数
private static final Semaphore writeSemaphore = new Semaphore(MAX_WRITERS);
private static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static final String FILE_PATH = "./test.txt";
public void writeFile(String content) throws InterruptedException, IOException {
writeSemaphore.acquire(); // 获取写锁
try {
System.out.println("线程 " + Thread.currentThread().getName() + " 准备写入文件");
FileWriter writer = new FileWriter(FILE_PATH, true); // true 表示追加写入
writer.write(content + "\n");
writer.close();
System.out.println("线程 " + Thread.currentThread().getName() + " 写入文件完成");
} finally {
writeSemaphore.release(); // 释放写锁
}
}
public void readFile() {
readWriteLock.readLock().lock(); // 获取读锁
try {
System.out.println("线程 " + Thread.currentThread().getName() + " 准备读取文件");
// 读取文件内容
System.out.println("线程 " + Thread.currentThread().getName() + " 读取文件完成");
} finally {
readWriteLock.readLock().unlock(); // 释放读锁
}
}
public static void main(String[] args) {
FileAccessControl control = new FileAccessControl();
// 模拟多个线程写入文件
for (int i = 0; i < 3; i++) {
final int threadId = i;
new Thread(() -> {
try {
control.writeFile("写入内容 - " + threadId);
Thread.sleep(1000); // 模拟写入耗时
} catch (InterruptedException | IOException e) {
Thread.currentThread().interrupt();
}
}).start();
}
// 模拟多个线程读取文件
for (int i = 0; i < 5; i++) {
new Thread(() -> {
control.readFile();
}).start();
}
}
}
- 优化建议:
- 使用更高效的 I/O 操作:例如:使用
RandomAccessFile
,提高文件读写效率。 - 缓冲读写:使用缓冲流,减少磁盘 I/O 次数。
- 监控文件访问:监控文件读写操作的耗时,及时发现性能瓶颈。
- 使用更高效的 I/O 操作:例如:使用
4.3 限制 API 调用频率
- 场景描述:
- 在调用第三方 API 时,通常有限制调用频率的策略,防止 API 被滥用,或者超过 API 的服务能力。
- 例如:限制每秒只能调用 10 次 API。
- 解决方案:
- 使用 Semaphore 来限制 API 的调用频率。
- 使用一个定时任务,定期释放许可证,例如:每秒释放 10 个许可证。
- 代码示例:
import java.util.concurrent.Semaphore;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ApiRateLimiter {
private static final int MAX_PERMITS = 10; // 每秒最大调用次数
private static final Semaphore semaphore = new Semaphore(MAX_PERMITS);
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public ApiRateLimiter() {
// 定时任务,每秒释放许可证
scheduler.scheduleAtFixedRate(() -> semaphore.release(MAX_PERMITS), 0, 1, TimeUnit.SECONDS);
}
public void callApi() throws InterruptedException {
semaphore.acquire(); // 获取许可证
try {
// 调用 API
System.out.println("线程 " + Thread.currentThread().getName() + " 调用 API");
Thread.sleep(100); // 模拟 API 调用耗时
} finally {
// 不需要手动释放,定时任务会自动释放
}
}
public static void main(String[] args) {
ApiRateLimiter limiter = new ApiRateLimiter();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
try {
limiter.callApi();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
}
- 优化建议:
- 调整调用频率:根据 API 的限制和系统的需求,调整
MAX_PERMITS
的值。 - 使用令牌桶算法或漏桶算法:除了 Semaphore,还可以使用令牌桶算法或漏桶算法,实现更灵活的限流策略。
- 监控 API 调用情况:监控 API 的调用次数、响应时间、错误率等指标,及时发现问题。
- 调整调用频率:根据 API 的限制和系统的需求,调整
5. 总结与最佳实践
Semaphore 是一个强大的并发控制工具,在高并发场景下可以发挥重要的作用。下面我来总结一下,并分享一些最佳实践:
5.1 总结
- 核心概念:Semaphore 就像一个“许可证”管理器,用于限制并发访问共享资源的线程数量。
- 两种模式:公平模式和非公平模式,需要在公平性和性能之间进行权衡。
- 调优策略:
- 调整许可证数量。
- 避免长时间持有许可证。
- 减少竞争。
- 监控和调优。
- 实际案例:数据库连接池、限制文件并发访问、限制 API 调用频率。
5.2 最佳实践
- 选择合适的模式:
- 优先选择非公平模式,除非对公平性有严格要求。
- 通过测试评估不同模式下的性能表现。
- 合理设置许可证数量:
- 根据系统资源和负载情况,调整许可证数量。
- 监控系统性能指标,及时发现问题。
- 缩短临界区:
- 尽量减少
acquire()
和release()
之间的代码量。 - 使用
try-finally
块,确保资源释放。
- 尽量减少
- 使用异步操作:
- 对于耗时操作,使用异步方式,避免长时间占用许可证。
- 监控和调优:
- 监控 Semaphore 的等待线程数、系统 CPU 使用率、内存使用率等指标。
- 使用性能分析工具,分析系统性能瓶颈。
- 根据监控结果,调整许可证数量、优化代码、调整线程池配置。
- 代码规范:
acquire()
和release()
必须成对出现,避免资源泄漏。- 处理
InterruptedException
异常,避免线程被中断后无法释放许可证。
6. 进阶知识:与其它并发工具的结合
Semaphore 也可以与其他并发工具结合使用,发挥更强大的作用。
6.1 与 CountDownLatch
结合
- 应用场景:多个线程并发执行,等待所有线程完成后再继续执行。
- 思路:使用
CountDownLatch
计数器,在每个线程执行完后,调用countDown()
方法,Semaphore
用于控制并发线程的数量。 - 示例:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SemaphoreWithCountDownLatch {
private static final int THREAD_COUNT = 5; // 线程数量
private static final int PERMITS = 2; // Semaphore 许可证数量
private static final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
private static final Semaphore semaphore = new Semaphore(PERMITS);
private static final ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadId = i;
executor.submit(() -> {
try {
semaphore.acquire(); // 获取 Semaphore 许可证
System.out.println("线程 " + threadId + " 获取 Semaphore 许可证,开始执行任务");
Thread.sleep(2000); // 模拟任务执行时间
System.out.println("线程 " + threadId + " 执行任务完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放 Semaphore 许可证
latch.countDown(); // CountDownLatch 计数器减 1
}
});
}
latch.await(); // 等待所有线程执行完成
System.out.println("所有线程执行完成,主线程继续执行");
executor.shutdown();
}
}
6.2 与 CyclicBarrier
结合
- 应用场景:多个线程到达某个同步点后,一起继续执行。
- 思路:使用
CyclicBarrier
等待所有线程到达同步点,使用Semaphore
控制并发线程的数量。 - 示例:
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SemaphoreWithCyclicBarrier {
private static final int THREAD_COUNT = 5; // 线程数量
private static final int PERMITS = 2; // Semaphore 许可证数量
private static final CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT);
private static final Semaphore semaphore = new Semaphore(PERMITS);
private static final ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadId = i;
executor.submit(() -> {
try {
semaphore.acquire(); // 获取 Semaphore 许可证
System.out.println("线程 " + threadId + " 获取 Semaphore 许可证,开始执行任务");
Thread.sleep(1000); // 模拟任务执行时间
System.out.println("线程 " + threadId + " 执行任务完成,准备到达同步点");
barrier.await(); // 等待所有线程到达同步点
System.out.println("线程 " + threadId + " 冲破屏障,继续执行");
} catch (Exception e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放 Semaphore 许可证
}
});
}
executor.shutdown();
}
}
6.3 与 ThreadPoolExecutor
结合
- 应用场景:控制线程池中任务的并发执行数量。
- 思路:在提交任务到线程池之前,使用
Semaphore
获取许可证,控制同时提交的任务数量。 - 示例:
import java.util.concurrent.*;
public class SemaphoreWithThreadPool {
private static final int PERMITS = 3; // Semaphore 许可证数量
private static final Semaphore semaphore = new Semaphore(PERMITS);
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize
10, // maximumPoolSize
60, // keepAliveTime
TimeUnit.SECONDS, // unit
new LinkedBlockingQueue<>(100) // workQueue
);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
try {
semaphore.acquire(); // 获取 Semaphore 许可证
System.out.println("任务 " + taskId + " 获取 Semaphore 许可证,开始执行");
Thread.sleep(2000); // 模拟任务执行时间
System.out.println("任务 " + taskId + " 执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放 Semaphore 许可证
}
});
}
executor.shutdown();
}
}
7. 总结与展望
Semaphore 是 Java 并发编程中一个非常重要的工具,掌握它的使用和调优技巧,对于提升高并发场景下的系统性能至关重要。希望今天的分享能够帮助你更好地理解和应用 Semaphore。
记住,并发编程是一门实践性很强的技术,理论知识只是基础,多动手实践,才能真正掌握它的精髓。 祝你在并发编程的道路上越走越远,成为一名真正的“并发大师”!
如果你还有其他关于并发编程的问题,或者想了解更多关于性能优化的知识,随时欢迎来找我交流! 咱们下次再见!