文章目录
- 前言
- 一、两种方式的代码实现
-
- 1.方式一:继承Thread类
- 2.方式二:实现Runnable接口
- 二、核心区别深度解析
-
- 1.继承限制:单继承VS多实现
- 2.任务与线程的耦合度
- 3.资源共享能力
- 4.代码复用性
- 三、实际开发中该怎么选?
-
- 什么时候用继承Thread?
- 什么时候用实现Runnable?
- 现代Java的写法:Lambda表达式
- 五、总结
前言
在Java多线程编程中,创建线程是入门的第一道坎,很多初学者都会遇到这样一个问题:到底是继承Thread类,还是实现Runnable接口? 这两种方式看起来都能实现多线程,但背后却有着本质的区别。选对了,代码灵活易维护;选错了,可能后续扩展处处受限。今天这篇文章,我们就来彻底讲透这两种方式的区别,帮你一次性搞清楚到底该怎么选。
一、两种方式的代码实现
1.方式一:继承Thread类
通过继承Thread类并重写run方法,可以定义线程的执行逻辑。Thread类本身已经实现了Runnable接口。
class MyThread extends Thread{
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"执行任务"+i);
}
}
}
publid class ThreadDemo{
public static void main(String[] args){
MyThread t1=new MyThread();
MyThread t2=new MyThread();
t1.start();
t2.start();
}
}
注意:一定要调用start()方法,而不是直接调用run(),直接调用run()不会创建新线程,只是一个普通的方法调用。
2.方式二:实现Runnable接口
定义一个类实现Runnable接口,重写run()方法,然后将该类的实例作为参数传给Thread的构造方法。
class MyRunnable implements Runable{
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"执行任务"+i);
}
}
}
public class RunnableDemo{
public static void main(String[] args){
MyRunnable myRunnable=new MyRunnable();
Thread t1=new Thread(myRunnable,"线程1");
Thread t2=new Thread(myRunnable,"线程2");
t1.start();
t2.start();
}
}
二、核心区别深度解析
1.继承限制:单继承VS多实现
这是两者最本质的区别。 Java是单继承语言,一旦你的类继承了Thread类,就无法再继承其他类了。如果你的业务类本来需要继承某个基类,用Thread的方式就会直接起冲突。 而实现Runnable接口则没有这个限制——Java支持一个类实现多个接口,实现了Runnable的同时还可以继承其他父类、实现其他接口。
//无法同时继承Thread类和其他类
class MyService extends BaseService,Thread{//编译错误!
//…
}
//可以同时继承BaseService并实现Runnable
class MyService extends BaseService implements Runnable{
//..
}
2.任务与线程的耦合度
继承Thread类的方式,线程对象和任务是绑在一起的——任务代码直接写在Thread的子类中,线程和任务无法分离。 而实现Runnable接口的方式,Runnable只负责定义任务逻辑,Thread负责执行,实现了任务与线程的分离,更符合单一职责原则。
3.资源共享能力
这是一个非常关键的区别。 继承Thread类时,每个线程都拥有独立的Thread实例,线程资源无法共享。 而实现Runnable接口时,多个线程可以共享一个Runnable实例,从而轻松实现资源共享。 来看一个经典的卖票例子: 继承Thread类(无法共享资源):
class TicketThread extends Thread{
private int tickets=10;
@Override
public void run(){
while(tickets>0){
System.out.println(getName()+"卖出一张票,剩余:"+ —tickets);
}
}
}
//创建三个线程,每个线程都有自己的10张票=总共卖了30张
TicketThread t1=new TicketThread();
TicketThread t2=new TicketThread();
TicketThread t3=new TicketThread();
实现Runnable接口(共享资源):
class TicketRunnable implements Runnable{
private int tickets=10;
@Override
public void run(){
while (tickets>0){
System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余:"+ —tickets);
}
}
}
//三个线程共享同一个Runnable实例=总共只卖10张票
TicketRunnable task=new TicketRunnable();
Thread t1=new Thread(task);
Thread t2=new Thread(task);
Thread t3=new Thread(task);
4.代码复用性
由于Runnable是一个接口,同一个Runnable实现可以被多个线程复用,提高了代码的复用性。而继承Thread创建的每个线程都是独立的,无法直接共享代码。
三、实际开发中该怎么选?
什么时候用继承Thread?
- 非常简单的线程任务,不需要继承其他类
- 需要直接操作Thread类的特有方法
- 快速原型验证
什么时候用实现Runnable?
- 绝大多数实际开发场景都推荐使用
- 类需要继承其他父类时
- 多个线程需要共享同一份资源或数据时
- 希望将任务逻辑与线程管理分离,提高代码的可维护性时
现代Java的写法:Lambda表达式
从Java 8开始,因为Runnable是函数式接口(只有一个抽象方法run()),可以用Lambda表达式进一步简化代码:
new Thread(()->{
System.out.println("Lambda创建的线程正在运行");
}).start();
这在日常开发中非常常见,简洁又优雅。
五、总结
继承Thread类的方式简单直接,适合非常简单的场景。而实现Runnable接口的方式则更加灵活、可扩展,支持资源共享、任务解耦,并且不受单继承的限制。 一句话建议:在实际开发中,有限选择实现Runnable接口的方式,除非你有非常特殊的理由必须继承Thread。
希望这篇文章能帮你彻底搞懂这两种方式的区别。
