一、多线程概述
在现代计算机系统中,多线程编程已成为提高程序性能、充分利用多核处理器能力的重要手段。Java作为一门广泛使用的编程语言,提供了丰富的多线程支持,使得开发者能够相对容易地实现并发程序。
什么是多线程?
多线程是指在一个进程中同时运行多个线程,每个线程可以独立执行不同的任务。与传统的单线程程序相比,多线程程序能够更高效地利用系统资源,特别是在多核CPU环境下。
二、进程与线程的区别
理解多线程之前,必须清楚进程和线程这两个核心概念的区别:
特性 | 进程 | 线程 |
---|---|---|
定义 | 程序的一次执行,资源分配的基本单位 | 进程中的一个执行单元,CPU调度的基本单位 |
资源占用 | 独立的内存空间和系统资源 | 共享进程的内存和资源 |
创建开销 | 大,需要分配独立资源 | 小,只需分配栈和程序计数器 |
通信方式 | 进程间通信(IPC)机制,如管道、消息队列等 | 可直接读写进程数据段(需同步机制) |
切换开销 | 大,涉及内存映射等操作 | 小,只需保存线程上下文 |
独立性 | 一个进程崩溃不会影响其他进程 | 一个线程崩溃可能导致整个进程终止 |
多核利用 | 可运行在不同CPU核心上 | 可被调度到不同CPU核心上 |
示例 | 浏览器和Word是两个不同进程 | 浏览器中多个标签页可能属于同一进程的不同线程 |
关键区别总结:
- 进程是资源分配的最小单位,线程是CPU调度的最小单位
- 线程共享进程的资源,进程之间资源独立
- 线程间通信比进程间通信更高效
- 线程创建和切换的开销远小于进程
三、Java多线程实现原理
Java的多线程实现基于两个核心机制:
- JVM线程模型:Java线程本质上映射到操作系统的原生线程
- 线程调度:由JVM和操作系统共同完成线程调度
Java线程生命周期
Java线程在其生命周期中会经历多种状态:
public enum State {
NEW, // 新建但尚未启动
RUNNABLE, // 可运行(可能在运行或等待CPU)
BLOCKED, // 被阻塞,等待监视器锁
WAITING, // 无限期等待其他线程执行特定操作
TIMED_WAITING,// 有限期等待
TERMINATED; // 已终止
}
Java内存模型(JMM)
Java内存模型定义了线程如何与内存交互,关键概念包括:
- 主内存:所有线程共享的内存区域
- 工作内存:每个线程私有的内存区域
- happens-before原则:确保内存可见性的规则
四、Java多线程实现方式
Java提供了三种主要的线程创建方式:
1. 继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行中: " + Thread.currentThread().getName());
}
}
// 使用
MyThread thread = new MyThread();
thread.start();
2. 实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable线程运行: " + Thread.currentThread().getName());
}
}
// 使用
Thread thread = new Thread(new MyRunnable());
thread.start();
3. 实现Callable接口(可返回结果)
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable返回结果: " + Thread.currentThread().getName();
}
}
// 使用
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // 获取返回结果
executor.shutdown();
五、线程池技术
直接创建线程存在性能开销,实际开发中应使用线程池:
// 创建固定大小的线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 10; i++) {
fixedPool.execute(() -> {
System.out.println("线程执行: " + Thread.currentThread().getName());
});
}
// 关闭线程池
fixedPool.shutdown();
Java提供的线程池类型:
- FixedThreadPool:固定大小线程池
- CachedThreadPool:可缓存线程池
- ScheduledThreadPool:定时任务线程池
- SingleThreadExecutor:单线程池
- ForkJoinPool:分治任务线程池(Java7+)
六、线程同步与通信
多线程环境下,共享资源的访问需要同步机制来保证线程安全。
1. synchronized关键字
// 同步方法
public synchronized void syncMethod() {
// 临界区代码
}
// 同步代码块
public void syncBlock() {
synchronized(this) {
// 临界区代码
}
}
2. Lock接口
Lock lock = new ReentrantLock();
public void lockMethod() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
}
3. 线程通信
// 使用wait/notify
class SharedResource {
private boolean ready = false;
public synchronized void waitForReady() throws InterruptedException {
while(!ready) {
wait(); // 释放锁并等待
}
}
public synchronized void setReady() {
ready = true;
notifyAll(); // 唤醒所有等待线程
}
}
七、Java并发工具类
Java并发包(java.util.concurrent)提供了强大的并发工具:
1. CountDownLatch
CountDownLatch latch = new CountDownLatch(3);
// 工作线程
new Thread(() -> {
// 执行任务
latch.countDown();
}).start();
// 主线程等待
latch.await();
System.out.println("所有任务完成");
2. CyclicBarrier
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 执行任务
barrier.await(); // 等待其他线程
}).start();
}
3. Semaphore
Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问
public void accessResource() throws InterruptedException {
semaphore.acquire();
try {
// 访问共享资源
} finally {
semaphore.release();
}
}
八、多线程应用场景
- 提高程序响应性:GUI应用中保持界面响应
- 提高CPU利用率:计算密集型任务
- 异步处理:网络请求、文件IO等
- 批处理任务:大数据处理
- 实现复杂算法:分治、并行计算等
九、多线程编程最佳实践
- 优先使用线程池而非直接创建线程
- 尽量减少同步范围,降低锁粒度
- 使用并发集合(如ConcurrentHashMap)替代同步集合
- 避免死锁(按固定顺序获取多个锁)
- 注意线程安全问题(原子性、可见性、有序性)
- 合理设置线程优先级
- 使用ThreadLocal保存线程私有数据
- 考虑使用不可变对象
十、总结
Java多线程编程是Java开发中的高级主题,理解进程与线程的区别是基础。通过合理使用多线程技术,可以显著提高程序性能,但同时也带来了复杂性。掌握线程同步、通信机制以及并发工具类的使用,是编写高效、安全并发程序的关键。
随着Java版本的更新,并发API也在不断改进(如CompletableFuture、Flow API等),开发者应持续学习新的并发编程模式和最佳实践,以应对日益复杂的并发编程挑战。