Java 高并发编程实战:从线程池到分布式锁,解决生产环境并发问题

一、痛点直击:生产环境最常见的并发问题​

  1. 并发问题统计(基于 50 + 企业案例):
  • 超卖 / 库存不一致(占比 38%)
  • 数据重复提交(占比 25%)
  • 线程池耗尽导致服务不可用(占比 20%)
  • 死锁(占比 17%)

真实场景:某秒杀系统因未处理并发,导致超卖 1000 单,直接损失 5 万元​

二、核心原理:Java 并发基础(JDK 8 通用)​

  1. 线程安全的三大核心:
  • 原子性:synchronized、AtomicInteger等原子类
  • 可见性:volatile关键字(禁止指令重排序)
  • 有序性:Happens-Before原则
  1. 并发工具对比(企业选型指南):

| 工具类 | 适用场景 | 优点 | 缺点 |
| synchronize | 简单并发场景(单服务) | 使用简单,JDK 优化好 | 不可中断,性能一般 |
| ReentrantLock | | 复杂并发场景(如超时) | 可中断,支持公平锁 | 需手动释放锁 |
| CountDownLatch | 等待多线程完成 | 轻量级,使用灵活 | 不可重复使用 |
| CyclicBarrier | 多线程协同工作 | 可重复使用 | 仅支持同一 JVM 内线程 |
| Semaphore | 限流场景 | 控制并发数精准 | 不支持分布式场景 |

Redis 分布式锁 分布式系统并发控制 支持跨服务、跨机房 需处理锁超时、重入

三、实战案例:6 个生产级并发问题解决方案​

案例 1:秒杀超卖问题(库存一致性)​

  1. 问题代码(存在超卖):

    java 复制代码
    @Service
    public class SeckillService {
        @Autowired
        private ProductMapper productMapper;
        
        public boolean seckill(Long productId, Long userId) {
            // 1. 查询库存
            Product product = productMapper.selectById(productId);
            if (product.getStock()  {
                return false;
            }
            // 2. 扣减库存(并发时会超卖)
            productMapper.decreaseStock(productId);
            // 3. 创建订单
            createOrder(productId, userId);
            return true;
        }
    }
  2. 解决方案 1:数据库悲观锁(简单但性能低)

    java 复制代码
    // Mapper.xml中添加悲观锁
    <select id="selectByIdForUpdate" resultType="Product">
        SELECT * FROM product WHERE id = #{id} FOR UPDATE
    >
    
    // 服务层修改
    public boolean seckill(Long productId, Long userId) {
        // 加悲观锁查询
        Product product = productMapper.selectByIdForUpdate(productId);
        if (product.getStock() ) {
            return false;
        }
        productMapper.decreaseStock(productId);
        createOrder(productId, userId);
        return true;
    }
  3. 解决方案 2:数据库乐观锁(高并发首选)

    java 复制代码
    // 实体类添加版本号字段
    public class Product {
        private Long id;
        private Integer stock;
        private Integer version; // 版本号
        // getter/setter
    }
    
    // Mapper.xml扣减库存时校验版本号
    <update id="decreaseStockWithVersion">
        UPDATE product 
        SET stock = stock - 1, version = version + 1 
        WHERE id = #{id} AND version = #{version} AND stock > 0
    // 服务层重试机制
    public boolean seckill(Long productId, Long userId) {
        int retryCount = 3; // 重试3次
        while (retryCount-- > 0) {
            Product product = productMapper.selectById(productId);
            if (product.getStock() 
                return false;
            }
            // 乐观锁扣减库存
            int rows = productMapper.decreaseStockWithVersion(productId, product.getVersion());
            if (rows > 0) {
                // 扣减成功,创建订单
                createOrder(productId, userId);
                return true;
            }
            // 扣减失败,重试
            try {
                Thread.sleep(10); // 避免频繁重试
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        return false;
    }
  4. 解决方案 3:Redis 预扣库存(秒杀高并发场景)

    java 复制代码
    @Service
    public class SeckillService {
        @Autowired
        private StringRedisTemplate redisTemplate;
        @Autowired
        private ProductMapper productMapper;
        
        // 秒杀预热:将库存加载到Redis
        public void preloadStock(Long productId, Integer stock) {
            redisTemplate.opsForValue().set("seckill:stock:" + productId, stock.toString());
        }
        
        public boolean seckill(Long productId, Long userId) {
            String stockKey = "seckill:stock:" + productId;
            String userKey = "seckill:user:" + productId + ":" + userId;
            
            // 1. 检查用户是否已抢购(避免重复下单)
            Boolean isExists = redisTemplate.hasKey(userKey);
            if (Boolean.TRUE.equals(isExists)) {
                return false;
            }
            
            // 2. Redis预扣库存(原子操作)
            Long stock = redisTemplate.opsForValue().decrement(stockKey);
            if (stock < 0) {
                // 库存不足,回滚
                redisTemplate.opsForValue().increment(stockKey);
                return false;
            }
            
            // 3. 记录用户已抢购
            redisTemplate.opsForValue().set(userKey, "1", 24, TimeUnit.HOURS);
            
            // 4. 异步同步到数据库(提高响应速度)
            CompletableFuture.runAsync(() -> {
                productMapper.decreaseStock(productId);
                createOrder(productId, userId);
            });
            
            return true;
        }
    }

案例 2:线程池参数不合理导致服务雪崩​

  1. 问题代码(默认线程池,高并发下耗尽):

    java 复制代码
    // 错误示例:无界队列+默认线程数(200)
    ExecutorService executor = Executors.newFixedThreadPool(200);
  2. 企业级线程池配置(核心参数详解):

    java 复制代码
    @Configuration
    public class ThreadPoolConfig {
        @Bean
        public ExecutorService businessThreadPool() {
            // 核心参数计算:核心线程数 = CPU核心数 * 2 + 1
            int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
            int maxPoolSize = corePoolSize * 2; // 最大线程数
            long keepAliveTime = 60; // 空闲线程存活时间(秒)
            // 有界队列(避免内存溢出)
            BlockingQueue<Runnable> queue = new ArrayBlockingQueue00);
            // 拒绝策略(避免任务丢失,使用调用者运行策略)
            RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
            
            return new ThreadPoolExecutor(
                corePoolSize,
                maxPoolSize,
                keepAliveTime,
                TimeUnit.SECONDS,
                queue,
                new NamedThreadFactory("business-thread-"),
                handler
            );
        }
    }
  3. 线程池监控(集成 Spring Boot Actuator):

    javascript 复制代码
    management:
      endpoints:
        web:
          exposure:
            include: threadpool,health,info
    java 复制代码
    @Component
    @Endpoint(id = "threadpool")
    public class ThreadPoolEndpoint {
        @Autowired
        private ExecutorService businessThreadPool;
        
        @ReadOperation
        public Map getThreadPoolInfo() {
            ThreadPoolExecutor executor = (ThreadPoolExecutor) businessThreadPool;
            Map Object> info = new HashMap<>();
            info.put("corePoolSize", executor.getCorePoolSize());
            info.put("activeCount", executor.getActiveCount());
            info.put("queueSize", executor.getQueue().size());
            info.put("completedTaskCount", executor.getCompletedTaskCount());
            info.put("rejectedCount", ((MyRejectedExecutionHandler) executor.getRejectedExecutionHandler()).getRejectedCount());
            return info;
        }
    }

案例 3:分布式锁解决跨服务并发问题​

  1. Redis 分布式锁实现(Redisson):

    java 复制代码
    @Configuration
    public class RedissonConfig {
        @Bean
        public RedissonClient redissonClient() {
            Config config = new Config();
            config.useSingleServer()
                .setAddress("redis://localhost:6379")
                .setPassword("123456")
                .setDatabase(0);
            return Redisson.create(config);
        }
    }
    
    @Service
    public class DistributeLockService {
        @Autowired
        private RedissonClient redissonClient;
        
        public > T executeWithLock(String lockKey, long leaseTime, TimeUnit unit, Supplier> supplier) {
            RLock lock = redissonClient.getLock(lockKey);
            try {
                // 尝试获取锁,最多等待3秒,锁定后30秒自动释放
                boolean locked = lock.tryLock(3, leaseTime, unit);
                if (!locked) {
                    throw new RuntimeException("获取锁失败,请稍后重试");
                }
                // 执行业务逻辑
                return supplier.get();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("获取锁异常", e);
            } finally {
                // 释放锁(只有持有锁的线程才能释放)
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }
    
    // 业务使用(跨服务更新库存)
    @Service
    public class InventoryService {
        @Autowired
        private DistributeLockService lockService;
        @Autowired
        private InventoryMapper inventoryMapper;
        
        public void updateInventory(Long productId, Integer num) {
            // 分布式锁key:产品ID(保证同一产品并发安全)
            String lockKey = "inventory:lock:" + productId;
            lockService.executeWithLock(lockKey, 30, TimeUnit.SECONDS, () -> {
                // 业务逻辑:查询库存→扣减库存
                Inventory inventory = inventoryMapper.selectById(productId);
                if (inventory.getStock()                 throw new RuntimeException("库存不足");
                }
                inventoryMapper.decreaseStock(productId, num);
                return null;
            });
        }
    }

案例 4-6:其他高频并发问题​

  1. 数据重复提交:基于 Redis + 令牌机制(前端请求时获取令牌,提交时校验)
  2. 死锁排查:`jstack 分析线程堆栈,避免嵌套锁、锁顺序不一致
  3. 并发集合使用:HashMap → ConcurrentHashMap,ArrayList → CopyOnWriteArrayList(读多写少场景)

四、并发编程避坑指南(生产环境必看)​

  1. 避免使用ArrayList、HashMap等非线程安全集合
  2. 线程池避免使用Executors默认实现(无界队列易导致 OOM)
  3. synchronized锁粒度不宜过大(建议锁方法而非类)
  4. 分布式锁必须设置过期时间(避免死锁)
  5. 高并发场景下,优先使用乐观锁而非悲观锁

如果您想要获取更多专业知识以及实战能力,欢迎订阅《程序员实战避坑手册:从面试到职场的问题一站式解决》专栏,专栏内容包含Java 后端开发实战避坑指南。适配各阶段 Java 开发者,直击开发全链路高频痛点,囊括 IDEA/Git 配置、Docker 环境搭建、MyBatis-Plus/SpringBoot 性能优化、MySQL 调优及大厂面试技巧。专栏内容均为实战案例 + 避坑步骤 + 落地解决方案,帮你规避开发陷阱。

相关推荐
夏幻灵1 小时前
面向对象编程综合实战
java
lots洋1 小时前
使用docker-compose安装mysql+redis+nacos
redis·mysql·docker
CoderCodingNo2 小时前
【GESP】C++五级练习题 luogu-P3353 在你窗外闪耀的星星
开发语言·c++·算法
定偶2 小时前
mysql
c语言·数据库·mysql
NMIXX爻2 小时前
线程控制 下
java·开发语言·jvm
Howrun7772 小时前
C++ 类间交互
开发语言·c++
Gold Steps.2 小时前
MySQL Operator for Kubernetes自动实现整个生命周期
mysql·云原生·kubernetes
Nandeska2 小时前
10、MySQL8.0新增特性
数据库·mysql
2401_857683542 小时前
C++代码静态检测
开发语言·c++·算法