Java-多线程_java 多线程
Java 的多线程(Multithreading)是实现并发编程的核心机制,允许程序同时执行多个任务,提高资源利用率和响应速度。Java 提供了强大的多线程支持,通过 java.lang.Thread
类和 java.util.concurrent
包实现。以下是对 Java 多线程的详细讲解,包括原理、实现方式、同步机制、线程池及最佳实践,帮助你彻底掌握 Java 多线程。
一、Java 多线程概述
1. 什么是多线程?
- 线程:操作系统调度的最小单元,属于同一进程内的执行流。
- 多线程:一个进程内多个线程并发执行,共享进程的内存空间(如堆、方法区),但每个线程有自己的栈。
- 优势:
- 提高效率:利用多核 CPU 并行执行任务。
- 快速响应:I/O 操作(如网络请求、文件读写)期间可切换线程。
- 资源共享:线程间共享内存,通信成本低。
2. Java 中的线程
- Java 线程基于操作系统的原生线程(非绿色线程),由 JVM 管理和调度。
- Java 提供
Thread
类和Runnable
接口作为线程基础,java.util.concurrent
包提供了高级并发工具。
3. 适用场景
- CPU 密集型任务:如复杂计算,利用多核 CPU。
- I/O 密集型任务:如网络请求、文件操作,线程可等待 I/O 时切换。
- 并发任务:如服务器处理多个客户端请求。
二、创建线程的四种方式
1. 继承 Thread
类
- 定义:继承
Thread
并重写run()
方法。 - 示例:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 运行");
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
- 输出(顺序可能不同):
线程 Thread-0 运行
线程 Thread-1 运行
- 特点:简单,但不能继承其他类。
2. 实现 Runnable
接口
- 定义:实现
Runnable
接口的run()
方法,将实例传递给Thread
。 - 示例:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 运行");
}
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
}
}
- 特点:灵活,可继承其他类,推荐使用。
3. 实现 Callable
接口
- 定义:实现
Callable
接口的call()
方法,返回结果并支持异常抛出,需结合Future
获取结果。 - 示例:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "线程 " + Thread.currentThread().getName() + " 返回结果";
}
public static void main(String[] args) throws Exception {
Callable<String> callable = new MyCallable();
FutureTask<String> task = new FutureTask<>(callable);
Thread t = new Thread(task);
t.start();
System.out.println(task.get()); // 等待结果
}
}
- 输出:
线程 Thread-0 返回结果
- 特点:支持返回值和异常处理,常用于需要结果的任务。
4. 使用线程池(ExecutorService)
- 定义:通过
java.util.concurrent.ExecutorService
创建线程池,避免手动创建线程。 - 示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> System.out.println("线程 " + Thread.currentThread().getName() + " 运行"));
executor.submit(() -> System.out.println("线程 " + Thread.currentThread().getName() + " 运行"));
executor.shutdown();
}
}
- 特点:高效管理线程,适合大量任务。
三、线程状态
Java 线程有以下六种状态(Thread.State
枚举):
- NEW:线程创建但未调用
start()
。 - RUNNABLE:线程正在运行或就绪(等待 CPU)。
- BLOCKED:线程等待锁(如进入
synchronized
块)。 - WAITING:线程等待其他线程通知(如
wait()
、join()
)。 - TIMED_WAITING:限时等待(如
sleep(1000)
、wait(1000)
)。 - TERMINATED:线程执行完成或异常终止。
四、线程同步机制
多线程共享资源可能导致数据竞争(Race Condition)或线程安全问题,需使用同步机制。
1. synchronized
关键字
- 作用:确保同一时间只有一个线程访问共享资源。
- 方式:
- 同步方法:
java public synchronized void increment() { counter++; }
- 同步块:
public void increment() { synchronized(this) { counter++; } }
- 示例(计数器线程安全):
public class Counter {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public int getCounter() {
return counter;
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数: " + counter.getCounter()); // 输出: 2000
}
}
2. Lock 接口
- 定义:
java.util.concurrent.locks.Lock
提供比synchronized
更灵活的锁机制,如ReentrantLock
。 - 示例:
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private int counter = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public int getCounter() {
return counter;
}
public static void main(String[] args) throws InterruptedException {
LockExample example = new LockExample();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数: " + example.getCounter()); // 输出: 2000
}
}
- 优势:支持公平锁、超时锁、条件变量。
3. 其他同步工具
volatile
:保证变量可见性,适合简单同步场景。
volatile boolean running = true;
Condition
:与Lock
配合,实现线程间等待/通知。Semaphore
:控制同时访问资源的线程数。CountDownLatch
:等待多个线程完成。CyclicBarrier
:多个线程等待彼此到达某点。
五、线程池(Executor Framework)
线程池通过 java.util.concurrent.ExecutorService
管理线程,减少创建/销毁线程的开销。
1. 常见线程池
Executors.newFixedThreadPool(n)
:固定大小线程池。Executors.newCachedThreadPool()
:动态大小线程池,适合短任务。Executors.newSingleThreadExecutor()
:单线程执行。Executors.newScheduledThreadPool(n)
:支持定时任务。
2. 示例(线程池)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("任务 " + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
- 输出(顺序可能不同):
任务 1 由线程 pool-1-thread-1 执行
任务 2 由线程 pool-1-thread-2 执行
任务 3 由线程 pool-1-thread-3 执行
任务 4 由线程 pool-1-thread-1 执行
任务 5 由线程 pool-1-thread-2 执行
3. 线程池管理
- 关闭线程池:
shutdown()
:等待任务完成后再关闭。shutdownNow()
:立即停止,尝试中断运行任务。- Future:获取任务结果或状态:
Future<Integer> future = executor.submit(() -> 42);
System.out.println(future.get()); // 输出: 42
六、线程安全问题
1. 数据竞争
- 原因:多个线程同时修改共享资源。
- 解决:使用
synchronized
、Lock
或线程安全集合(如ConcurrentHashMap
)。
2. 死锁
- 原因:多个线程相互等待对方释放锁。
- 解决:
- 按固定顺序获取锁。
- 使用超时锁(如
lock.tryLock(1, TimeUnit.SECONDS)
)。 - 工具检测:JVisualVM、JStack。
3. 线程安全集合
Collections.synchronizedXXX
:同步包装器(如synchronizedMap
)。ConcurrentHashMap
:高效并发映射。CopyOnWriteArrayList
:适合读多写少场景。
七、最佳实践
- 选择合适的线程创建方式:
- 优先使用
Runnable
或Callable
,避免继承Thread
。 - 大量任务使用线程池(
ExecutorService
)。
- 线程安全:
- 使用
synchronized
或Lock
保护共享资源。 - 优先使用
java.util.concurrent
提供的线程安全类。
- 线程池优化:
- 根据任务类型选择线程池(固定大小、缓存、定时)。
- 合理设置线程数(如 CPU 核心数 + 1)。
- 避免死锁:
- 按统一顺序加锁。
- 使用
tryLock
避免无限等待。
- 异常处理:
- 在
run()
或call()
中捕获异常,避免线程无声失败。
- 监控与调试:
- 使用
Thread.currentThread().getName()
跟踪线程。 - 借助工具(如 JVisualVM)分析线程状态。
- 关闭资源:
- 确保线程池使用
shutdown()
或shutdownNow()
关闭。 - 释放锁和其他资源。
八、常见问题与解决
- 问题:线程未结束,主线程退出
- 解决:使用
join()
等待线程完成,或设为守护线程(setDaemon(true)
)。
- 问题:数据竞争导致结果错误
- 解决:加锁或使用线程安全集合。
- 问题:死锁
- 解决:检查锁顺序,使用超时机制,分析堆栈跟踪。
- 问题:线程池任务堆积
- 解决:限制任务队列大小,优化任务处理逻辑。
九、总结
Java 多线程通过 Thread
、Runnable
、Callable
和线程池实现并发,适用于 CPU 密集型和 I/O 密集型任务。synchronized
和 Lock
确保线程安全,java.util.concurrent
提供高效并发工具(如 ExecutorService
、ConcurrentHashMap
)。遵循最佳实践(如使用线程池、避免死锁、优化线程数),可以编写高效、可靠的多线程程序。
如果需要更深入的示例(如线程池调优、并发集合使用、死锁调试)或与 ForkJoinPool
、CompletableFuture
的对比,请告诉我!