在Java使用中多线程介绍、应用场景和示例代码

概述

在Java中,多线程是指在一个程序中每个并发执行多个线程的技术。线程是一个独立的执行路径,程序通过多线程来出现并发任务,从而提高效率,尤其是在处理I/O密集型或者计算密集型任务时。

一、多线程核心概念

1. 线程创建方式

(1) 继承 Thread 类(不推荐)
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }
}

// 使用
new MyThread().start();
(2) 实现 Runnable 接口(推荐)
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable running: " + Thread.currentThread().getName());
    }
}

// 使用
new Thread(new MyRunnable()).start();
(3) 实现 Callable + Future(获取返回值)
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
    TimeUnit.SECONDS.sleep(2);
    return 42;
});
System.out.println("Result: " + future.get()); // 阻塞获取结果
executor.shutdown();

2. 线程池(实际开发必用)

推荐使用 ThreadPoolExecutor 自定义线程池:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, // 核心线程数
    10, // 最大线程数
    60L, TimeUnit.SECONDS, // 空闲线程存活时间
    new ArrayBlockingQueue<>(100), // 任务队列
    new ThreadFactoryBuilder().setNameFormat("worker-%d").build(), // 线程命名
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
拒绝策略选择:
  • AbortPolicy(默认):抛出异常

  • CallerRunsPolicy:由调用线程执行任务

  • DiscardOldestPolicy:丢弃最旧任务

  • DiscardPolicy:静默丢弃


二、实际应用场景

场景 1:批量数据处理(电商订单处理)

List<Order> orders = getBatchOrders(1000); // 获取1000个待处理订单

// 分片处理:每100个订单为一组
List<List<Order>> chunks = Lists.partition(orders, 100); 

ExecutorService executor = Executors.newFixedThreadPool(10); // 10线程并发处理
List<Future<Boolean>> futures = new ArrayList<>();

for (List<Order> chunk : chunks) {
    futures.add(executor.submit(() -> {
        processOrders(chunk); // 处理订单组
        return true;
    }));
}

// 等待所有任务完成
for (Future<Boolean> future : futures) {
    future.get(); // 阻塞等待
}

executor.shutdown();

场景 2:异步日志记录(避免阻塞主流程)

// 使用单线程池处理日志(保证顺序)
private static final ExecutorService LOG_EXECUTOR = 
    Executors.newSingleThreadExecutor(r -> new Thread(r, "log-thread"));

public void saveOrder(Order order) {
    // 主流程快速返回
    LOG_EXECUTOR.submit(() -> {
        try {
            logService.saveLog("Order created: " + order.getId());
        } catch (Exception e) {
            // 必须捕获异常,否则会静默失败!
            logger.error("Log save failed", e);
        }
    });
}

场景 3:定时任务调度(使用 ScheduledExecutorService

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

// 每天凌晨执行数据统计
scheduler.scheduleAtFixedRate(() -> {
    generateDailyReport();
}, initialDelay, 24 * 60 * 60, TimeUnit.SECONDS);

// 每5分钟检查一次系统状态
scheduler.scheduleWithFixedDelay(() -> {
    checkSystemHealth();
}, 0, 5, TimeUnit.MINUTES);

三、线程安全与同步

1. 线程安全容器

// 代替 HashMap
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>(); 

// 代替 ArrayList
List<String> copyOnWriteList = new CopyOnWriteArrayList<>(); 

// 高并发计数器
AtomicInteger counter = new AtomicInteger(0); 
counter.incrementAndGet(); // 原子操作

2. 显式锁(更灵活的控制)

private final ReentrantLock lock = new ReentrantLock();
private int sharedResource = 0;

public void updateResource() {
    lock.lock();
    try {
        sharedResource++;
        // 其他操作...
    } finally {
        lock.unlock(); // 必须放在finally块中!
    }
}

3. 读写锁(读多写少场景)

private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();

public String readData() {
    readLock.lock();
    try {
        return data; // 并发读不互斥
    } finally {
        readLock.unlock();
    }
}

public void writeData(String newData) {
    writeLock.lock();
    try {
        data = newData; // 写操作独占
    } finally {
        writeLock.unlock();
    }
}

四、避坑指南(血泪经验)

1. 避免死锁

  • 按固定顺序获取锁

  • 使用 tryLock() 设置超时

if (lock1.tryLock(1, TimeUnit.SECONDS)) {
    try {
        if (lock2.tryLock(1, TimeUnit.SECONDS)) {
            try {
                // 操作资源
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

2. 防止资源泄漏

  • 必须关闭线程池!

executor.shutdown(); 
// 或强制终止
List<Runnable> unfinished = executor.shutdownNow();

3. 异常处理

  • 线程内部异常不会传递到外层

ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(() -> {
    try {
        riskyOperation();
    } catch (Exception e) {
        logger.error("Task failed", e); // 必须内部捕获
    }
});

4. 性能陷阱

  • 避免过度同步(缩小同步代码块)

  • 警惕上下文切换开销(线程数不是越多越好)

  • 使用 ThreadLocal 谨慎(可能导致内存泄漏)


五、高级特性(Java 8+)

1. CompletableFuture(异步编排)

CompletableFuture.supplyAsync(() -> fetchUserInfo(userId), executor)
    .thenApplyAsync(user -> calculateScore(user), executor)
    .thenAcceptAsync(score -> saveToDB(score), executor)
    .exceptionally(ex -> {
        logger.error("Pipeline failed", ex);
        return null;
    });

2. 并行流(谨慎使用)

List<Data> results = dataList.parallelStream()
    .filter(d -> d.getValue() > 100)
    .collect(Collectors.toList());

六、调试与监控

1. 线程堆栈分析

# 生成线程转储
jstack <pid> > thread_dump.txt

# 查找死锁(输出中搜索 "deadlock")

2. 监控工具

  • VisualVM:查看线程状态

  • Arthas:在线诊断生产环境线程问题

  • Prometheus + Grafana:监控线程池指标


七、最佳实践总结

  1. 尽量使用线程池,避免直接 new Thread

  2. 优先使用并发容器(如 ConcurrentHashMap

  3. 同步代码块尽量小

  4. 始终处理线程中的异常

  5. 生产环境必须命名线程(方便排查问题)

  6. 合理设置线程池参数(通过压测确定)

  7. 避免依赖线程优先级(不同平台行为不一致)

通过合理运用多线程技术,可以显著提升系统吞吐量,但需时刻警惕其复杂性。建议在关键模块编写详细的并发处理文档,并进行充分的压力测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值