Java线程生命周期详解及多线程实现方式对比
Java多线程开发是高性能应用的核心基础。掌握线程从创建到销毁的完整生命周期,以及正确选择Thread或Runnable的实现方式,能有效避免资源竞争问题并提升代码扩展性。本文将系统梳理线程状态流转逻辑,并通过经典案例对比两种主流实现方案的异同。
Java线程的五种生命周期状态
在Java中,一个完整的线程对象在其执行过程中会经历五种不同的状态。理解这些状态的转换条件是进行并发编程的前提。
1. 新建状态 (New)
当程序使用 new 关键字创建一个线程对象后,该线程即进入新建状态。此时线程尚未启动,例如执行 Thread thread = new Thread(); 时。
2. 就绪状态 (Runnable)
被称为“可执行状态”。当调用线程对象的 start() 方法后,线程进入就绪状态。此时线程已获得除CPU时间片以外的所有必要资源,随时准备被调度执行。需要注意的是,就绪状态并不保证立即执行,而是等待操作系统分配CPU。
3. 运行状态 (Running)
线程获取CPU使用权并开始执行其 run() 方法体中的逻辑。线程只能从就绪状态直接进入运行状态,不能跳跃。
4. 阻塞状态 (Blocked)
线程由于某种原因主动放弃CPU使用权,暂停执行,直到满足特定条件才重新转入就绪状态。常见的阻塞情况包括三种:
- 等待阻塞:通过调用
wait()方法,使线程等待某项工作的完成。 - 同步阻塞:在获取
synchronized同步锁失败时(锁已被其他线程占用),线程进入此状态。 - 其他阻塞:调用
sleep()、join()或发起I/O请求时进入。当sleep()超时、join()等待结束或I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态 (Dead)
线程执行完 run() 方法或因未捕获异常退出,标志着线程生命周期的终结。
涉及的核心机制:Object、Thread与synchronized
上述状态的变化深度依赖于JDK底层的几个关键概念:
- Object类:定义了
wait()、notify()、notifyAll()等用于线程间通信和休眠/唤醒的方法。之所以将这些方法定义在Object而非Thread中,是因为锁是绑定在对象上的,唤醒操作也需针对特定对象进行。 - Thread类:提供了丰富的线程操作API,如
sleep()(休眠)、interrupt()(中断)、getName()(获取名称)等。 - synchronized关键字:用于区分同步代码块和同步方法,确保同一时刻只有一个线程能访问被锁定的资源,解决竞态条件问题。
多线程实现的两种主要方式
在Java中实现多线程,最基础的两种方式是继承 Thread 类和实现 Runnable 接口。理解它们的底层关系有助于编写更高质量的代码。
1. Runnable接口实现
Runnable 是一个函数式接口,仅包含一个抽象方法 run()。其源码结构如下:
public interface Runnable {
public abstract void run();
}
实现步骤:自定义类实现该接口,并重写 run() 方法;然后创建 Thread 实例,将实现了Runnable的对象传入构造函数:new Thread(new MyRunnable())。
2. Thread类继承实现
Thread 本身就是一个类,且在Java源码中已实现了 Runnable 接口:
public class Thread implements Runnable {}
实现步骤:自定义类直接继承 Thread 类,重写 run() 方法,并调用 start() 启动。
Thread与Runnable的深度对比及推荐
虽然两者都能实现多线程,但在实际开发中存在显著差异:
- 继承限制:Java不支持多重继承。如果采用继承Thread的方式,子类便无法再继承其他父类,限制了类的扩展能力。而实现Runnable接口没有此限制,更符合面向对象的设计原则。
- 资源共享:这是两者最大的区别。当多个线程需要共享同一份数据(如库存、车票)时,必须基于同一个Runnable对象创建多个Thread实例。若每个线程都继承独立的Thread对象,则各自拥有独立的数据副本,无法实现共享逻辑。
- 解耦:Runnable将“任务(做什么)”与“执行者(谁来做)”分离,便于代码维护和复用。
结论:除非需要重写Thread类的特定行为,否则强烈建议使用 Runnable 接口来实现多线程。
代码实战:卖票场景示例
为了直观展示两者的差异,我们通过一个简单的“售票员卖票”案例来对比运行结果。
方式一:继承Thread(多资源,不共享)
在此模式下,我们创建了三个不同的 MyThread 对象(t1, t2, t3),每个对象内部都有一份独立的 ticket 变量。
class MyThread extends Thread{
private int ticket=10;
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println(this.getName()+" 卖票:ticket"+this.ticket--);
}
}
}
};
public class ThreadTest {
public static void main(String[] args) {
// 创建3个独立的线程实例
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
// 分别启动
t1.start();
t2.start();
t3.start();
}
}
结果分析:
每个线程实例都会卖出自己的10张票。总票数并非固定的单一池,而是3个线程各卖10张。这证明了各个线程互不干扰,未实现资源共享。
方式二:实现Runnable(单资源,共享)
在此模式下,我们只创建一个 MyThread 实例(mt),然后将它传递给三个不同的 Thread 构造函数。
class MyThread implements Runnable{
private int ticket=10;
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+this.ticket--);
}
}
}
};
public class RunnableTest {
public static void main(String[] args) {
// 只有一个Runnable对象实例
MyThread mt=new MyThread();
// 3个线程共用这一个任务对象
Thread t1=new Thread(mt);
Thread t2=new Thread(mt);
Thread t3=new Thread(mt);
t1.start();
t2.start();
t3.start();
}
}
结果分析:
三个线程交替输出,直到票数为0为止。总计只卖出10张票。这说明 t1、t2、t3 操作的是内存中同一份 ticket 变量,成功实现了资源共享。
总结
线程的生命周期管理是并发编程的基础知识。在实际项目中,优先考虑使用 Runnable 接口实现多线程,不仅因为避免了单继承的限制,更因为它天然适合处理需要共享资源的复杂业务场景。以上就是Java线程状态及多线程实现方式的详细内容,更多关于Java并发编程技巧的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









