在 Java EE 项目中,多线程并发处理是提升系统性能的关键。本文将深入探讨**Java EE初阶启程记09---多线程案例(2)**涉及的常见问题,从底层原理、代码示例到实战经验,帮助开发者更好地理解和应用多线程技术。
问题场景重现:电商秒杀系统
假设我们正在开发一个电商秒杀系统。在高并发场景下,如果多个用户同时尝试抢购同一件商品,库存扣减和订单创建逻辑必须保证线程安全,否则会出现超卖、重复订单等问题。传统的同步锁 (synchronized) 在高并发下会成为性能瓶颈。我们需要更高效的并发控制方案。
底层原理深度剖析:CAS 和原子类
为了避免synchronized的性能瓶颈,可以考虑使用CAS(Compare and Swap)操作和原子类。CAS是一种乐观锁机制,它尝试将内存位置的值更新为新值,前提是该位置的当前值与预期值匹配。原子类(如AtomicInteger、AtomicLong等)基于CAS实现,提供了线程安全的原子操作。
代码解决方案:基于 AtomicInteger 的库存扣减
以下代码展示了如何使用 AtomicInteger 实现线程安全的库存扣减:
import java.util.concurrent.atomic.AtomicInteger;
public class StockManager {
private AtomicInteger stock = new AtomicInteger(100); // 初始库存 100
public boolean decreaseStock() {
int currentStock = stock.get();
if (currentStock > 0) {
// 尝试原子性地将库存减 1
if (stock.compareAndSet(currentStock, currentStock - 1)) {
System.out.println("库存扣减成功,剩余库存:" + stock.get());
return true;
} else {
// CAS 操作失败,说明有其他线程已经修改了库存,重试
return decreaseStock(); // 递归重试
}
} else {
System.out.println("库存不足!");
return false;
}
}
public int getStock() {
return stock.get();
}
public static void main(String[] args) throws InterruptedException {
StockManager stockManager = new StockManager();
// 模拟 10 个线程同时抢购
for (int i = 0; i < 10; i++) {
new Thread(() -> {
stockManager.decreaseStock();
}).start();
}
Thread.sleep(2000); // 等待所有线程执行完毕
System.out.println("最终库存:" + stockManager.getStock());
}
}
结合 Redis 分布式锁解决超卖问题
单机环境下的 AtomicInteger 可以解决单个 JVM 进程内的线程安全问题。但在分布式环境中,我们需要使用分布式锁来保证多个 JVM 进程之间的线程安全。 Redis 分布式锁是一个常用的解决方案。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
public class RedisLock {
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁的key
* @param requestId 锁的value,区分不同请求
* @param expireTime 超时时间,单位秒
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
SetParams setParams = SetParams.setParams().nx().ex(expireTime);
String result = jedis.set(lockKey, requestId, setParams);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁的key
* @param requestId 锁的value
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, 1, lockKey, requestId);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
在秒杀业务代码中,先尝试获取 Redis 锁,成功后再进行库存扣减和订单创建。释放锁时,使用 Lua 脚本保证原子性。
实战避坑经验总结
- 锁的粒度:锁的粒度要适中。锁的范围太大,会降低并发度;锁的范围太小,又无法保证线程安全。
- 死锁:避免死锁。确保锁的释放,可以使用 try-finally 块或者 Redis 锁的过期时间。
- Redis 锁的续约:如果业务逻辑执行时间可能超过 Redis 锁的过期时间,需要考虑锁的续约机制,防止锁被意外释放。
- 性能监控:使用 Arthas 等工具对 Java EE 应用进行性能监控,分析多线程相关的性能瓶颈,并针对性地进行优化。例如,可以观察线程池的利用率、锁的竞争情况等。
- 选择合适的线程池:根据任务的类型(CPU 密集型、IO 密集型)选择合适的线程池大小和配置。不合理的线程池配置可能导致性能下降甚至系统崩溃。可以参考阿里巴巴 Java 开发手册中的建议。
通过理解 Java EE初阶启程记09---多线程案例(2) 背后的原理,并结合实际案例进行练习,可以提升Java EE应用的并发处理能力,构建高性能、高可用的系统。
冠军资讯
代码一只喵