引言
多线程是Java并发编程的核心技术之一,广泛应用于服务器开发、数据处理、实时系统等领域。通过多线程,程序可以充分利用CPU资源,提高执行效率,同时处理多个任务。本文将从多线程的基本概念、实现方式、线程状态、同步与通信到常见问题与解决方案,深入解析Java多线程的原理与实践。
一、多线程的核心概念
1. 什么是线程?
线程是操作系统能够进行运算调度的最小单位。一个进程可以包含多个线程,它们共享进程的资源(如内存),但每个线程有独立的执行路径。
2. 多线程的优势
- 提高程序性能:通过并行执行多个任务,充分利用多核CPU。
- 异步处理:避免主线程阻塞(如GUI应用、网络请求)。
- 资源共享:线程间可直接共享进程的内存数据(需注意线程安全)。
3. 多线程与并发的区别
- 并发:多个任务交替执行,逻辑上“同时”进行(如单核CPU的多任务调度)。
- 并行:多个任务真正同时执行(如多核CPU的多线程协作)。
二、Java多线程的实现方式
1. 继承 Thread
类
class MyThread extends Thread {@Overridepublic void run() {System.out.println("线程执行:" + Thread.currentThread().getName());}
}
// 启动线程
new MyThread().start();
缺点:Java单继承限制,无法继承其他类。
2. 实现 Runnable
接口(推荐)
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("线程执行:" + Thread.currentThread().getName());}
}
// 启动线程
new Thread(new MyRunnable()).start();
优点:避免单继承限制,便于资源共享(如多个线程操作同一实例)。
3. 使用 Callable
接口(带返回值)
class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "结果:" + Thread.currentThread().getName();}
}
// 提交任务
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get());
executor.shutdown();
4. 线程池(推荐)
线程池管理线程复用,避免频繁创建销毁线程:
ExecutorService pool = Executors.newFixedThreadPool(5); // 固定大小线程池
pool.execute(() -> System.out.println("任务执行"));
pool.shutdown();
三、线程的生命周期与状态
Java线程有以下5种基本状态(JDK 1.8):
状态 | 说明 |
---|---|
新建(New) | 创建线程对象后,调用 start() 之前的状态。 |
就绪(Runnable) | 调用 start() 后,等待CPU调度执行。 |
运行(Running) | CPU开始调度线程,执行 run() 方法。 |
阻塞(Blocked) | 线程因等待锁、I/O、休眠等原因暂时停止。 |
终止(Terminated) | 线程执行完毕或抛出异常退出。 |
阻塞状态细分:
- 等待阻塞:调用
wait()
,进入等待池(需notify()
唤醒)。 - 同步阻塞:未获取到
synchronized
锁。 - 其他阻塞:调用
sleep()
、join()
或I/O操作。
四、线程同步与线程安全
1. 什么是线程安全?
当多个线程并发访问共享资源时,程序仍能正确运行(无数据错误、状态混乱)。
2. 常见问题与解决方案
(1)原子性问题
- 问题:非原子操作(如
count++
)被多个线程拆分执行。 - 解决方案:
synchronized
关键字:public synchronized void increment() {count++; }
ReentrantLock
显式锁:ReentrantLock lock = new ReentrantLock(); public void increment() {lock.lock();try {count++;} finally {lock.unlock();} }
- 原子类(AtomicXXX):
AtomicInteger atomicCount = new AtomicInteger(0); atomicCount.incrementAndGet(); // 线程安全的自增
(2)可见性问题
- 问题:线程A修改变量后,线程B未及时读取到最新值。
- 解决方案:
volatile
关键字:private volatile boolean flag = false; // 禁止指令重排序,保证可见性
(3)有序性问题
- 问题:编译器或CPU优化导致指令顺序变化。
- 解决方案:
synchronized
/volatile
:通过内存屏障确保顺序。
五、线程通信:生产者-消费者模型
经典场景
- 生产者:每秒生成一个产品,交给店员。
- 消费者:每秒购买一个产品。
- 店员:最多管理20个产品。
实现代码
class Clerk {private int productCount = 0;private static final int MAX_CAPACITY = 20;// 生产产品public synchronized void produce() throws InterruptedException {while (productCount >= MAX_CAPACITY) {System.out.println(Thread.currentThread().getName() + " 库存已满,等待...");wait();}productCount++;System.out.println(Thread.currentThread().getName() + " 生产了1个产品,库存:" + productCount);notifyAll();}// 消费产品public synchronized void consume() throws InterruptedException {while (productCount <= 0) {System.out.println(Thread.currentThread().getName() + " 没有产品,等待...");wait();}productCount--;System.out.println(Thread.currentThread().getName() + " 购买了1个产品,库存:" + productCount);notifyAll();}
}// 生产者
class Producer implements Runnable {private Clerk clerk;public Producer(Clerk clerk) { this.clerk = clerk; }@Overridepublic void run() {while (true) {try {clerk.produce();Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}}
}// 消费者
class Customer implements Runnable {private Clerk clerk;public Customer(Clerk clerk) { this.clerk = clerk; }@Overridepublic void run() {while (true) {try {clerk.consume();Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}}
}
六、多线程避坑指南
1. 死锁问题
死锁条件:
- 互斥:资源不可共享。
- 请求与保持:线程持有资源并申请新资源。
- 不可剥夺:资源只能由持有线程释放。
- 循环等待:多个线程形成资源环。
解决方案:
- 按固定顺序加锁:避免循环等待。
- 超时机制:使用
tryLock()
设置超时时间。 - 资源监控:通过
jstack
分析死锁。
2. 线程池使用不当
错误示例:
void realJob() {ThreadPoolExecutor exe = new ThreadPoolExecutor(...); // 每次请求新建线程池exe.submit(new Runnable() {...});
}
后果:线程池爆炸,耗尽系统资源。
正确做法:
- 将线程池作为静态变量复用。
- 使用
Executors
工厂方法(如newFixedThreadPool
)。
七、总结
Java多线程是提升程序性能的关键技术,但也伴随着复杂的并发问题。掌握以下核心要点:
- 线程创建与启动:优先使用
Runnable
和线程池。 - 线程同步:
synchronized
、ReentrantLock
、原子类。 - 线程通信:
wait()
/notify()
实现生产者-消费者模型。 - 避免死锁:按顺序加锁、超时机制。
- 线程池管理:避免资源耗尽,复用线程。
通过合理设计和实践,多线程可以成为构建高性能、高可靠系统的重要工具。