Java多线程并发案例怎么实操

wen java案例 27

Java多线程并发案例实操:从理论到项目的完整实战指南

目录导读

  1. 为什么实战案例是掌握并发的关键?
  2. 基础案例:线程创建与生命周期管理
  3. 进阶案例:锁与同步机制的正确使用
  4. 高级案例:线程池、并发集合与CompletableFuture
  5. 典型面试问答:并发场景下的陷阱与解决
  6. 从案例中提炼的3个核心原则

为什么实战案例是掌握并发的关键?

许多开发者学习Java多线程时,能理解ThreadRunnablesynchronized的概念,但一到真实项目就手足无措。案例实操的价值在于:将抽象理论转化为可运行的代码,暴露并发中的典型陷阱(如死锁、可见性问题、线程饥饿),并通过调试与优化形成肌肉记忆。

Java多线程并发案例怎么实操

本篇文章精选3个不同层级的案例,覆盖从基础到生产级场景,所有代码均可在JDK 8+环境下直接运行。


基础案例:线程创建与生命周期管理

案例描述

模拟一个银行账户的存款与取款操作,要求两个线程同时操作同一账户,最终账户余额正确。

错误示范(不操实战会踩的坑)

class BankAccount {
    private int balance = 100;
    public void withdraw(int amount) {
        if (balance >= amount) {
            // 模拟网络延迟
            try { Thread.sleep(10); } catch (InterruptedException e) {}
            balance -= amount;
            System.out.println("取款成功,余额:" + balance);
        }
    }
}

问题:线程A判断余额足够后进入sleep,线程B同样判断通过,导致余额被扣两次,变为负数。

正确实操方式

class SafeBankAccount {
    private int balance = 100;
    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + "取款成功,余额:" + balance);
        }
    }
}

关键点:使用synchronized将方法变成原子操作,确保判断与修改不可分割。

实操指令

  1. 创建两个线程分别执行取款50元操作。
  2. 观察控制台输出,验证余额是否保持为0。
  3. 尝试将synchronized去掉,观察经典的“脏读”现象。

进阶案例:锁与同步机制的正确使用

案例描述

任务调度系统:多个线程同时处理任务,但同一时间只能有N个线程执行(例如只允许3个线程同时计算)。

使用ReentrantLock+Condition实现限流

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
class TaskScheduler {
    private final int maxConcurrent = 3;
    private int currentCount = 0;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    public void executeTask() throws InterruptedException {
        lock.lock();
        try {
            while (currentCount >= maxConcurrent) {
                System.out.println(Thread.currentThread().getName() + "等待中...");
                notFull.await(); // 释放锁并阻塞
            }
            currentCount++;
            System.out.println(Thread.currentThread().getName() + "执行任务,当前并发:" + currentCount);
        } finally {
            lock.unlock();
        }
        // 模拟任务执行
        Thread.sleep(100);
        // 完成时释放槽位
        lock.lock();
        try {
            currentCount--;
            notFull.signalAll(); // 唤醒等待线程
        } finally {
            lock.unlock();
        }
    }
}

实操技巧

  • 避免synchronized的局限性:当需要超时等待、可中断锁、公平锁时,必须使用ReentrantLock
  • Condition的精准唤醒:比synchronizedwait/notify更灵活,支持多条件队列。

高级案例:线程池、并发集合与CompletableFuture

案例描述

电商批量查询价格:需要同时调用5个微服务获取不同商品的价格,等待所有结果返回后合并展示。

使用ExecutorService+CompletableFuture实现

import java.util.concurrent.*;
import java.util.*;
public class PriceAggregator {
    private final ExecutorService pool = Executors.newFixedThreadPool(3);
    public CompletableFuture<Double> queryPrice(String product) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟耗时服务调用
            try { Thread.sleep(200); } catch (InterruptedException e) {}
            if (product.equals("手机")) return 4999.0;
            if (product.equals("电脑")) return 8999.0;
            return 100.0;
        }, pool);
    }
    public static void main(String[] args) throws Exception {
        PriceAggregator aggregator = new PriceAggregator();
        List<String> products = Arrays.asList("手机", "电脑", "耳机");
        // 并发查询所有价格
        List<CompletableFuture<Double>> futures = products.stream()
                .map(aggregator::queryPrice)
                .collect(Collectors.toList());
        // 等待所有完成
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
                futures.toArray(new CompletableFuture[0])
        );
        allFutures.join();
        // 提取结果
        List<Double> prices = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
        System.out.println("最终价格:" + prices); // [4999.0, 8999.0, 100.0]
        aggregator.pool.shutdown();
    }
}

实战要点

  • 线程池大小:I/O密集型可设置2 * CPU核心数,计算密集型设置CPU核心数 + 1
  • 并发集合优先:使用ConcurrentHashMap而非HashMap,使用CopyOnWriteArrayList替代ArrayList(写少读多场景)。
  • 异常处理CompletableFuture通过exceptionallyhandle优雅处理异常,避免漏掉。

典型面试问答:并发场景下的陷阱与解决

Q1:如何避免死锁?
A:按固定顺序加锁(例如所有线程先锁A再锁B);使用带超时的方法tryLock(3, TimeUnit.SECONDS);使用锁排序工具如LockSupport

Q2:volatile能保证原子性吗?
A:不能。volatile只保证可见性和有序性,对count++这样复合操作仍需AtomicIntegersynchronized

Q3:线程池的拒绝策略有什么实际用途?
ACallerRunsPolicy让提交线程自己运行任务(反压机制),DiscardPolicy丢弃任务,适用于丢失不影响核心流程。


从案例中提炼的3个核心原则

  1. 原子性第一:任何涉及共享数据的修改,必须用锁或原子类包装。
  2. 线程池代替裸线程:避免频繁创建/销毁线程,使用Executors或自定义参数。
  3. 异步编排用CompletableFuture:替代漫长的CountDownLatchFuture.get,可读性更强。

最终建议:将上述代码复制到IDE中,修改参数(如增减线程数、调整sleep时间),观察并发带来的数据不一致、卡顿等现象,这才是真正的“实操”。

抱歉,评论功能暂时关闭!