
hello,社区小伙伴们,又见面了,今天给大家分享一下java心得。做后端的小伙伴肯定知道,在后端开发、中间件设计等场景中,Java 并发编程是提升系统吞吐量的关键,但也常因线程安全、资源竞争、死锁等问题成为线上故障的重灾区。多数开发者仅停留在 “能用” 层面,却忽略了并发代码的 “高效性” 和 “稳定性”。今天我们跳出基础 API 讲解,聚焦 6 个实战级优化方向,结合真实业务场景我们如何从 “线程安全” 到 “性能突破” 。
Java 并发的本质是 “多线程共享资源的协同操作”,核心矛盾在于:
常见误区是 “为了安全牺牲效率”(如滥用 synchronized 导致串行执行),或 “为了效率忽视安全”(如无锁操作共享变量导致数据竞争)。优秀的并发代码,必须在二者间找到平衡点 —— 仅在必要时同步,且选择最轻量的同步方式。
synchronized 是 Java 最基础的同步工具,但早期实现为 “重量级锁”(依赖操作系统内核态互斥量),性能开销大。JDK 6 后引入偏向锁、轻量级锁、自旋锁等优化,但仍需通过 “锁粒度控制” 和 “锁类型选择” 进一步提升效率。
锁粒度最小化:仅对共享资源的操作加锁,而非整个方法或类。
锁类型精准选择:
电商秒杀场景中,商品库存是核心共享资源,特点是 “读多写少”(大量用户查询库存,少量用户下单扣减)。若用 synchronized 会导致查询线程阻塞,用 ReentrantReadWriteLock 可优化读并发性能。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ProductStock {
private int stock = 1000; // 商品库存
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// 查询库存(读操作)
public int getStock() {
readLock.lock();
try {
return stock; // 读操作无互斥,多线程同时执行
} finally {
readLock.unlock();
}
}
// 扣减库存(写操作)
public boolean deductStock(int num) {
writeLock.lock();
try {
if (stock >= num) {
stock -= num;
return true;
}
return false;
} finally {
writeLock.unlock();
}
}
}锁机制的核心问题是 “阻塞”—— 线程等待锁时会进入阻塞状态,触发上下文切换,开销较大。无锁编程基于 CAS(Compare-And-Swap) 机制,通过硬件指令保证原子操作,线程无需阻塞,大幅提升高并发场景下的性能。
CAS 包含 3 个参数:内存地址 V、预期值 A、新值 B。仅当内存地址 V 中的值等于预期值 A 时,才将其更新为 B,否则不做操作。整个过程是原子的,无需加锁。Java 中 java.util.concurrent.atomic 包下的类(如 AtomicInteger、AtomicReference)均基于 CAS 实现。
接口访问量统计是典型的高并发场景,无状态且仅需原子递增,用 AtomicInteger 比 synchronized 更高效。
import java.util.concurrent.atomic.AtomicInteger;
public class ApiCounter {
// 无锁计数器,基于 CAS 实现原子递增
private final AtomicInteger count = new AtomicInteger(0);
// 接口访问时调用,统计访问量
public void increment() {
count.incrementAndGet(); // CAS 原子操作,无阻塞
}
// 获取总访问量
public int getTotalCount() {
return count.get();
}
}CAS 存在 “ABA 问题”—— 线程 1 读取值为 A,线程 2 将 A 改为 B 再改回 A,线程 1 误以为值未变,执行更新操作。解决方式:
// AtomicStampedReference 解决 ABA 问题
AtomicStampedReference<Integer> stampedRef = new AtomicStampedReference<>(100, 0);
int stamp = stampedRef.getStamp(); // 获取当前版本号
// 仅当值为 100 且版本号为 stamp 时,更新为 200,版本号自增
boolean success = stampedRef.compareAndSet(100, 200, stamp, stamp + 1);Thread.yield() 降低自旋频率;创建线程的开销较大(需分配栈空间、内核态线程映射等),频繁创建销毁线程会严重影响性能。线程池通过 “线程复用” 减少线程创建销毁开销,同时统一管理线程生命周期,是并发编程的核心工具。但多数开发者仅使用 Executors 提供的默认线程池(如 Executors.newFixedThreadPool()),忽略了参数适配,导致线程池阻塞或资源耗尽。
自定义线程池,拒绝默认实现:
线程池参数精准配置:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class CustomThreadPool {
// CPU 核心数
private static final int CPU_CORES = Runtime.getRuntime().availableProcessors();
// 自定义线程池:IO 密集型,核心线程数=CPU*2,最大线程数=20,队列容量=100
public static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(
CPU_CORES * 2,
20,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列,避免 OOM
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者线程执行
);
// 关闭线程池(程序退出时调用)
public static void shutdown() {
EXECUTOR.shutdown();
try {
// 等待 30 秒,若仍有任务未完成则强制关闭
if (!EXECUTOR.awaitTermination(30, TimeUnit.SECONDS)) {
EXECUTOR.shutdownNow();
}
} catch (InterruptedException e) {
EXECUTOR.shutdownNow();
}
}
}ArrayList、HashMap 等普通容器线程不安全,多线程操作时会出现 ConcurrentModificationException 或数据错乱。多数开发者会用 Collections.synchronizedList() 或手动加锁解决,但性能较差(全程独占锁)。Java 提供了专门的并发容器(java.util.concurrent 包),通过分段锁、CAS 等机制优化并发性能。
这里列出来,大家可以参考的选型

实战场景:秒杀商品队列(生产者 - 消费者模型)
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class SeckillQueue {
// 有界阻塞队列,容量=1000,存储秒杀商品 ID
private static final BlockingQueue<String> QUEUE = new ArrayBlockingQueue<>(1000);
// 生产者:添加秒杀商品到队列
public static boolean addSeckillProduct(String productId) {
try {
// 队列满时阻塞,避免 OOM
QUEUE.put(productId);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
// 消费者:处理秒杀商品
public static void processSeckill() {
CustomThreadPool.EXECUTOR.submit(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
// 队列空时阻塞,避免空轮询
String productId = QUEUE.take();
System.out.println("处理秒杀商品:" + productId);
// 执行秒杀逻辑(如扣减库存、创建订单)
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}ThreadLocal 用于存储线程私有数据,避免多线程共享变量的同步开销,常见于 “线程上下文传递”(如用户登录信息、数据库连接)。但滥用 ThreadLocal 会导致内存泄漏,多数开发者忽略了其生命周期管理。
及时移除 ThreadLocal 数据:
避免存储大对象:
public class UserContext {
// ThreadLocal 存储当前线程的用户信息
private static final ThreadLocal<UserInfo> USER_THREAD_LOCAL = new ThreadLocal<>();
// 设置用户信息到上下文
public static void setUser(UserInfo userInfo) {
USER_THREAD_LOCAL.set(userInfo);
}
// 获取当前线程的用户信息
public static UserInfo getUser() {
return USER_THREAD_LOCAL.get();
}
// 移除用户信息,避免内存泄漏和数据污染
public static void removeUser() {
USER_THREAD_LOCAL.remove();
}
// 用户信息实体类
public static class UserInfo {
private String userId;
private String userName;
// getter/setter
}
}
// 线程池任务中使用
CustomThreadPool.EXECUTOR.submit(() -> {
try {
// 设置上下文
UserContext.setUser(new UserContext.UserInfo("1001", "张三"));
// 执行业务逻辑(如查询用户订单)
queryUserOrders();
} finally {
// 必须移除,避免线程复用导致数据错乱
UserContext.removeUser();
}
});死锁是并发编程的 “致命问题”,一旦发生会导致线程永久阻塞,无法自动恢复。死锁的产生需满足 4 个条件:资源互斥、持有并等待、不可剥夺、循环等待。优化的核心是 “破坏其中一个或多个条件”。
转账时需获取两个账户的锁,若线程 1 先获取账户 A 的锁,再获取账户 B 的锁;线程 2 先获取账户 B 的锁,再获取账户 A 的锁,会导致死锁。解决方案:统一按账户 ID 从小到大获取锁。
import java.util.concurrent.locks.ReentrantLock;
public class TransferService {
// 账户锁映射:key=账户ID,value=锁
private final Map<String, ReentrantLock> accountLocks = new ConcurrentHashMap<>();
// 初始化账户锁(若不存在则创建)
private ReentrantLock getAccountLock(String accountId) {
return accountLocks.computeIfAbsent(accountId, k -> new ReentrantLock());
}
// 转账操作:按账户ID从小到大获取锁
public boolean transfer(String fromAccount, String toAccount, int amount) {
// 统一锁获取顺序:先获取 ID 较小的账户锁
String lock1 = fromAccount.compareTo(toAccount) < 0 ? fromAccount : toAccount;
String lock2 = fromAccount.compareTo(toAccount) > 0 ? fromAccount : toAccount;
ReentrantLock reentrantLock1 = getAccountLock(lock1);
ReentrantLock reentrantLock2 = getAccountLock(lock2);
// 尝试获取锁,超时 3 秒
try {
boolean lock1Acquired = reentrantLock1.tryLock(3, TimeUnit.SECONDS);
if (!lock1Acquired) {
return false;
}
try {
boolean lock2Acquired = reentrantLock2.tryLock(3, TimeUnit.SECONDS);
if (!lock2Acquired) {
return false;
}
try {
// 执行转账逻辑(扣减转出账户余额,增加转入账户余额)
return doTransfer(fromAccount, toAccount, amount);
} finally {
reentrantLock2.unlock();
}
} finally {
reentrantLock1.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
// 实际转账逻辑(省略)
private boolean doTransfer(String fromAccount, String toAccount, int amount) {
// ...
return true;
}
}这里给大家总结了 3 个原则:
Java 并发编程的优化没有最优解,需结合业务场景(如并发量、读写比例、任务类型)灵活调整方案。我列出的6 个核心优化方向,可在保证线程安全的前提下,大幅提升并发代码的性能和稳定性,避免线上常见的并发故障,当然也不止这些,更多精彩的想法,大家可以在评论区一起交流。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。