Thread类是Java多线程编程的基石,掌握它的使用对于编写高效、稳定的并发程序至关重要。下面我将详细解析Thread类的核心概念和方法,并通过实际项目案例帮助你深化理解。
🔍 Java Thread类全面解析与实战指南
✨ 线程基础概念
在深入Thread类之前,让我们先了解几个核心概念。线程是进程中的一个执行单元,是程序执行的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存、文件句柄等),并可以并发执行。与进程相比,线程的创建和销毁开销更小,因此在提高程序效率和资源利用率方面具有显著优势。
多线程编程主要带来三大优势:提高程序响应性(避免界面卡顿)、提升执行效率(利用多核CPU资源)以及更高效的资源共享。
📋 Thread类的核心构造方法
Thread类提供了多个构造函数,满足不同场景的需求:
| 构造方法 | 说明 | 适用场景 |
|---|---|---|
Thread() |
创建线程对象 | 基本线程创建 |
Thread(Runnable target) |
使用Runnable对象创建线程对象 | 推荐的方式,任务与线程分离 |
Thread(String name) |
创建线程对象并命名 | 需要调试或识别线程的场景 |
Thread(Runnable target, String name) |
使用Runnable对象创建线程对象并命名 | 需要识别线程的异步任务 |
Thread(ThreadGroup group, Runnable target) |
将线程分组管理 | 需要统一管理一组线程 |
🧵 创建线程的三种方式
1. 继承Thread类
arduino
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("MyThread执行: " + i);
}
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
2. 实现Runnable接口(推荐)
java
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("MyRunnable执行: " + i);
}
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
3. 使用Callable和FutureTask(可获取返回值)
java
public class FutureTaskTest {
public static class CallerTask implements Callable<String> {
@Override
public String call() throws Exception {
return "hello";
}
}
public static void main(String[] args) throws InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
new Thread(futureTask).start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
对比分析:继承Thread类代码简单直观,但由于Java单继承限制,扩展性较差;实现Runnable接口更灵活,可实现多个接口,便于资源共享,是推荐使用的方式;Callable方式可以获取线程执行结果,适用于需要返回值的场景。
🔄 线程的生命周期与状态转换
线程从创建到销毁会经历不同的状态,理解这些状态及其转换关系对编写正确的多线程程序至关重要:
- NEW(新建状态):线程被创建但尚未启动时的状态。
- RUNNABLE(可运行状态):线程已经启动,正在执行或准备执行(等待CPU分配时间片)。
- BLOCKED(阻塞状态):线程等待获取监视器锁以进入同步块/方法时处于此状态。
- WAITING(等待状态):线程无限期等待其他线程执行特定操作时处于此状态。
- TIMED_WAITING(计时等待状态):线程等待特定时间后自动唤醒时处于此状态。
- TERMINATED(终止状态):线程执行完毕后的状态。
下面是状态转换的完整示例代码:
arduino
public class ThreadLifecycleDemo {
public static void main(String[] args) throws InterruptedException {
// 1. NEW状态
Thread thread = new Thread(() -> {
synchronized (ThreadLifecycleDemo.class) {
try {
// 3. 进入TIMED_WAITING状态
Thread.sleep(1000);
// 4. 进入WAITING状态
ThreadLifecycleDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("创建后状态: " + thread.getState()); // NEW
thread.start();
System.out.println("启动后状态: " + thread.getState()); // RUNNABLE
// 让主线程稍等,确保子线程进入sleep
Thread.sleep(100);
System.out.println("sleep时状态: " + thread.getState()); // TIMED_WAITING
// 等待子线程sleep结束
Thread.sleep(1000);
// 此时子线程在wait()方法中
System.out.println("wait时状态: " + thread.getState()); // WAITING
// 唤醒线程
synchronized (ThreadLifecycleDemo.class) {
ThreadLifecycleDemo.class.notify();
}
// 等待线程结束
thread.join();
System.out.println("结束后状态: " + thread.getState()); // TERMINATED
}
}
⚙️ Thread类的核心方法详解
1. start() 与 run() 方法
- start():启动线程,使线程进入就绪状态。JVM会为该线程分配资源,并在合适的时机执行其run()方法。
- run():定义线程的具体任务逻辑。如果继承Thread类,需要重写该方法;如果通过Runnable接口创建线程,run()方法的实现逻辑在Runnable的实现类中。
重要区别:启动线程需调用start()方法,而不是直接调用run()方法。直接调用run()方法相当于普通的方法调用,不会开启新线程。
2. join() 方法
join()方法用于让当前线程等待指定线程执行完毕。
csharp
public class JoinExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000); // 模拟线程执行耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行完毕");
});
thread.start();
try {
thread.join(); // 主线程等待子线程执行完毕
System.out.println("子线程已结束,主线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3. sleep() 方法
sleep()方法使当前线程暂停执行指定的毫秒数,线程进入阻塞状态。
arduino
public class SleepExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000); // 每隔1秒执行一次
System.out.println("线程执行: " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
4. yield() 方法
yield()方法使当前线程主动让出CPU资源,进入就绪状态,允许其他具有相同优先级的线程执行。但该方法并不能保证一定会切换到其他线程。
5. 线程中断机制(interrupt())
interrupt()方法用于向线程发送中断信号,是一种协作式的中断机制。
arduino
public class InterruptExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程被中断,退出执行");
Thread.currentThread().interrupt(); // 重新设置中断状态
break;
}
}
});
thread.start();
try {
Thread.sleep(3000);
thread.interrupt(); // 中断线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
🔒 线程同步与线程安全
多线程环境下,如果多个线程同时访问共享资源,可能会导致数据不一致等线程安全问题。
1. synchronized 关键字
csharp
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
System.out.println("最终计数: " + counter.getCount()); // 应该是2000
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2. Lock 接口
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockExample {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
🚀 项目实战案例
案例一:多线程文件下载器
java
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class MultiThreadDownloader {
public static void main(String[] args) {
String[] urls = {
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
"https://example.com/image3.jpg",
"https://example.com/image4.jpg"
};
for (int i = 0; i < urls.length; i++) {
DownloadThread thread = new DownloadThread(urls[i], "download_" + i + ".jpg");
thread.start();
}
}
}
class DownloadThread extends Thread {
private String url;
private String fileName;
public DownloadThread(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}
@Override
public void run() {
try {
System.out.println("开始下载: " + fileName);
FileUtils.copyURLToFile(new URL(url), new File(fileName));
System.out.println("下载完成: " + fileName);
} catch (IOException e) {
System.out.println("下载失败: " + fileName + ", 错误: " + e.getMessage());
}
}
}
案例二:龟兔赛跑模拟
typescript
public class Race implements Runnable {
private String winner;
@Override
public void run() {
for (int steps = 1; steps <= 100; steps++) {
// 模拟兔子每20步休息一下
if (Thread.currentThread().getName().equals("兔子") && steps % 20 == 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "跑了 " + steps + " 步");
// 检查比赛是否结束
if (isRaceOver(steps)) {
break;
}
}
}
private boolean isRaceOver(int steps) {
if (winner != null) {
return true;
}
if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println("🎉 " + winner + " 赢得了比赛!");
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "乌龟").start();
new Thread(race, "兔子").start();
}
}
案例三:生产者-消费者模型
java
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumerExample {
private static final int MAX_SIZE = 5;
private final Queue<Integer> queue = new LinkedList<>();
private final Object lock = new Object();
class Producer implements Runnable {
@Override
public void run() {
int value = 0;
while (true) {
synchronized (lock) {
while (queue.size() == MAX_SIZE) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
queue.offer(value);
System.out.println("生产: " + value);
value++;
lock.notifyAll();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
synchronized (lock) {
while (queue.isEmpty()) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
int value = queue.poll();
System.out.println("消费: " + value);
lock.notifyAll();
}
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
public void start() {
Thread producer1 = new Thread(new Producer(), "Producer-1");
Thread consumer1 = new Thread(new Consumer(), "Consumer-1");
Thread consumer2 = new Thread(new Consumer(), "Consumer-2");
producer1.start();
consumer1.start();
consumer2.start();
}
public static void main(String[] args) {
new ProducerConsumerExample().start();
}
}
💡 最佳实践与注意事项
- 优先使用Runnable接口:避免Java单继承的限制,提高代码的灵活性
- 正确处理中断:使用interrupt()机制而不是已废弃的stop()方法
- 避免死锁:按固定顺序获取锁,设置合理的超时时间
- 使用线程池:避免频繁创建和销毁线程的开销
- 同步范围最小化:只在必要的时候进行同步,减少性能开销
- volatile关键字:确保变量的可见性,但不保证原子性
📊 线程优先级与调度
Java中线程优先级范围是1~10,默认值为5。
ini
Thread t1 = new Thread(() -> System.out.println("低优先级线程"));
Thread t2 = new Thread(() -> System.out.println("高优先级线程"));
t1.setPriority(Thread.MIN_PRIORITY); // 1
t2.setPriority(Thread.MAX_PRIORITY); // 10
t1.start();
t2.start();
注意:线程优先级只是对调度器的提示,并不保证高优先级线程一定先执行,具体取决于操作系统的调度策略。