多线程是 Java 并发编程的核心,它允许程序同时执行多个任务,显著提升程序效率。本文将系统梳理 Java 多线程的实现方式、核心 API、线程安全及经典协作模式,帮助读者全面掌握多线程编程技巧。
一、多线程基础概念
1. 线程与进程
- 进程:程序的一次执行过程,是操作系统资源分配的基本单位
- 线程:进程内的执行单元,是 CPU 调度的最小单位,一个进程可包含多个线程
- 多线程:单个进程中同时运行多个线程,共享进程资源
2. 并发与并行
- 并发:多个线程在单个 CPU 上交替执行(宏观同时,微观交替)
- 并行:多个线程在多个 CPU 上同时执行(真正意义上的同时)
二、多线程的三种实现方式
1. 继承 Thread 类
通过继承Thread
类并重写run()
方法实现多线程:
// 定义线程类
class MyThread1 extends Thread {
// 构造方法指定线程名
public MyThread1(String name) {
super(name);
}
// 重写run()方法,定义线程执行逻辑
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "启动了");
}
}
// 使用线程
MyThread1 thread1 = new MyThread1("线程1");
thread1.start(); // 启动线程(底层调用run())
特点:实现简单,但受限于单继承机制,灵活性较低。
2. 实现 Runnable 接口
实现Runnable
接口的run()
方法,再通过Thread
包装:
// 定义任务类
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "启动了");
}
}
// 使用线程
MyThread2 task = new MyThread2();
Thread thread2 = new Thread(task, "线程2"); // 包装任务并指定线程名
thread2.start();
特点:避免单继承限制,适合多线程共享同一个任务对象的场景。
3. 实现 Callable 接口(带返回值)
通过Callable
接口实现有返回值的多线程,需配合FutureTask
使用:
// 定义带返回值的任务
class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "启动了");
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum; // 返回计算结果
}
}
// 使用线程
MyThread3 callableTask = new MyThread3();
FutureTask<Integer> futureTask = new FutureTask<>(callableTask); // 管理结果
Thread thread3 = new Thread(futureTask, "线程3");
thread3.start();
Integer result = futureTask.get(); // 获取线程执行结果
System.out.println("计算结果:" + result);
特点:可获取线程执行结果,适合需要返回值的场景,但实现相对复杂。
三、Thread 类核心方法
方法名 | 功能描述 |
---|---|
String getName() | 获取线程名称 |
void setName(String name) | 设置线程名称 |
static Thread currentThread() | 获取当前执行的线程对象 |
static void sleep(long millis) | 让线程休眠指定毫秒数 |
void setPriority(int priority) | 设置线程优先级(1-10,默认 5) |
int getPriority() | 获取线程优先级 |
void setDaemon(boolean on) | 设置为守护线程(后台线程) |
boolean isDaemon() | 判断是否为守护线程 |
static void yield() | 线程礼让(让出 CPU 执行权) |
void join() | 线程插队(让调用线程先执行完) |
四、线程安全问题与解决方案
当多个线程共享资源时,可能出现数据不一致的安全问题(如多窗口售票超卖)。解决方式主要有:
1. 同步代码块
通过synchronized
关键字锁定共享资源:
synchronized (锁对象) {
// 操作共享资源的代码
}
要求:多个线程必须使用同一个锁对象。
2. 同步方法
将synchronized
关键字加到方法上:
// 非静态同步方法(锁对象为this)
public synchronized void sellTicket() {
// 售票逻辑
}
// 静态同步方法(锁对象为类的字节码文件)
public static synchronized void sellTicket() {
// 售票逻辑
}
3. Lock 锁(JDK 5+)
更灵活的锁机制,需手动获取和释放锁:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
Lock lock = new ReentrantLock(); // 创建锁对象
try {
lock.lock(); // 获取锁
// 操作共享资源的代码
} finally {
lock.unlock(); // 释放锁(确保锁一定会被释放)
}
五、死锁问题
死锁是多线程因争夺资源而形成的僵局,例如:
- 线程 A 持有资源 1,等待资源 2
- 线程 B 持有资源 2,等待资源 1
避免方式:
- 按顺序获取资源
- 限时获取资源
- 减少锁的嵌套使用
六、生产者消费者模式(线程协作)
这是多线程协作的经典模式,通过等待唤醒机制实现生产与消费的平衡。
1. 基于等待唤醒机制的实现
// 共享资源类
class Desk {
public static int foodFlag = 0; // 0:无食物 1:有食物
public static int count = 10; // 总数量
public static Object lock = new Object(); // 锁对象
}
// 生产者线程
class Producer extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) break; // 生产完毕
if (Desk.foodFlag == 1) {
try {
Desk.lock.wait(); // 有食物则等待
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("厨师制作了食物");
Desk.foodFlag = 1;
Desk.lock.notifyAll(); // 唤醒消费者
}
}
}
}
}
// 消费者线程
class Consumer extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) break; // 消费完毕
if (Desk.foodFlag == 0) {
try {
Desk.lock.wait(); // 无食物则等待
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
Desk.count--;
System.out.println("消费者吃了食物,还剩" + Desk.count + "个");
Desk.foodFlag = 0;
Desk.lock.notifyAll(); // 唤醒生产者
}
}
}
}
}
2. 基于阻塞队列的实现(JDK 5+)
使用BlockingQueue
简化协作,当队列空时消费者阻塞,队列满时生产者阻塞:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
// 生产者
class Producer implements Runnable {
private BlockingQueue<String> queue;
// 构造方法注入队列
public Producer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
queue.put("食物"); // 放入元素,满则阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费者
class Consumer implements Runnable {
private BlockingQueue<String> queue;
// 构造方法注入队列
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
String food = queue.take(); // 取出元素,空则阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 使用
BlockingQueue<String> queue = new ArrayBlockingQueue<>(1); // 容量为1的队列
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
七、线程的生命周期
Java 线程有 6 种状态,定义在Thread.State
枚举中:
- NEW:新建状态(未启动)
- RUNNABLE:运行状态(就绪或正在运行)
- BLOCKED:阻塞状态(等待锁)
- WAITING:无限等待状态(需被唤醒)
- TIMED_WAITING:计时等待状态(超时自动唤醒)
- TERMINATED:终止状态(已执行完毕)
八、总结
多线程是 Java 开发中提升程序性能的关键技术,掌握其实现方式、线程安全控制及协作模式至关重要。实际开发中需注意:
- 根据场景选择合适的线程实现方式(无返回值用 Runnable,有返回值用 Callable)
- 优先使用 Lock 锁(灵活性更高)处理线程安全问题
- 避免死锁,合理设计资源竞争逻辑
- 复杂协作场景优先使用阻塞队列简化代码
通过合理运用多线程,能有效提升程序的并发处理能力,构建高效、响应迅速的应用系统。