Java创建线程只有两种方式

本文详细解析了Java并发编程中创建线程的两种核心方式:实现Runnable接口和继承Thread类,探讨了它们的优缺点及适用场景。此外,还介绍了线程池、Callable、FutureTask和定时器等高级线程管理技术。

Java并发方面有很多书籍以及博客,针对于线程创建方式有着不同描述,例如实现Runnable接口、集成Thread类、使用线程池工具类以及结合Callable和Future创建线程等。

创建线程的两种方式

Oracle官方文档,即java.lang.Thread类注释的表述是有如下两种创建线程的方式。https://docs.oracle.com/javase/8/docs/api/index.html

方式一:实现Runnable接口,传入Thread类

public class RunnableStyle implements Runnable{

public static void main(String[] args) {
    Thread thread = new Thread(new RunnableStyle());
    thread.start();
}

@Override
public void run() {
    System.out.println("Runnable接口方式创建线程");
}

}

方式二:继承Thread类,重写run方法

public class ThreadStyle extends Thread {
@Override
public void run() {
System.out.println(“用Thread方式创建线程”);
}

public static void main(String[] args) {
    new ThreadStyle().start();
}

}

两种方式的比较

推荐采用实现Runnable接口,传入Thread类。

从代码架构角度:具体执行的任务(run方法)应该和创建/运行线程的机制(Thread类)解耦。
与重写Thread类run方法一样,实现Runnable接口run方法的内容也是具体执行的任务。但是Runnable方式则可以创建单独任务类实现Runnable接口,然后传入任务实例到Thread类中。这样同一个任务类可以传给不同的Thread,同时任务类不需要负责创建线程等工作,因此是解耦的。

从继承的角度上,Java是单继承,如果继承了Thread类就无法继承其它类,限制可扩展性。

从资源节约的角度上讲,如果是Thread类方式,每次新建一个任务只能新建一个独立的线程,会有额外线程创建/销毁等损耗。而Runnable更加方便采用线程池工具,减少创建、销毁线程带来的损耗。

两种方式本质对比

两种方式在多线程实现的本质上是一致的,都是调用thread对象的start方法创建线程。主要区别在于run方法的内容来源上。

java.lang.Thread

private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}

实现Runnable方式,最终调用了Runnable对象target的run方法;而继承Thread方式是重写了整个run方法。

示例:代码同时使用两种方式
匿名内部类实现Runnable接口,并在类内部重写run方法。重写run方法的代码直接覆盖Thread类run方法代码,Runnable中run方法实现代码不会执行。

Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(“使用Runnable方式创建线程”);
}
}) {
@Override
public void run() {
System.out.println(“使用Thread方式创建线程”);
}
};
thread.start();

多线程的其他代码实现形式

线程池创建线程的方式

public class ThreadPoolStyle {
public static void main(String[] args) {
// 不提倡的创建线程池方式–原因参见阿里规约
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
pool.submit(new Task());
}
}
}

class Task implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}

我们通过Executors创建线程池,深入源码可以看到,内部创建ThreadPoolExecutor时会使用ThreadFactory。以DefaultThreadFactory为例,其内部创建线程的方法newThread仍然是通过Runnable创建线程。

java.util.concurrent.Executors

public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}

通过Callable和FutureTask创建线程的方式

public class CallableFutureTaskStyle {
public static void main(String[] args) {
// 创建任务,启动线程
FutureTask futureTask = new FutureTask<>(new CallableTask());
new Thread(futureTask).start();
}
}

class CallableTask implements Callable {
@Override
public String call() throws Exception {
return “使用Callable和FutureTask创建线程”;
}
}

Callable接口是一个独立的接口,用于创建任务,通常与FutureTask配合使用。而FutureTask的父类是Runnable,因此其本质仍然是Runnable方式创建线程。

定时器创建线程

public class TimerStyle {
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},100,100);
}
}

定时器任务类TimerTask实现Runnable接口。

public abstract class TimerTask implements Runnable

匿名内部类和Lambda代码实现形式

多线程的代码实现方式有很多种,但其本质仍然是出于继承Thread和实现Runnable接口两种方式。线程池、Callable以及定时器,对创建线程做了一定的封装,但本质仍然没有变。至于线程匿名内部类和Lambda实现,只是代码实现形式的不同,不能作为实现线程的方式。

new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();

较为准确的描述

(1)首先从不同的角度看,会有不同的答案。例如从代码实现,还是从本质上说。

(2)通常我们可以分为两种,分别是实现Runnable接口和继承Thread类。Thread类的注释也是这样表述的。

(3)描述Runnable方式和Thread方式的不同(3个角度)。

(4)其实Thread类实现了Runnable接口,类中实现了run方法。可以发现两种方式在创建线程的本质上是一样的,都是调用Thread对象的start方法,主要区别在于run方法内容的来源不同:Runnable方式最终是调用Ruannble对象target的run方法,而Thread方式则是使用了重写的run方法。

(5)还有其他实现线程的方式,例如线程池也能新建线程,但是细看源码,其本质是Runnable方式。

(6)准确的讲,创建线程只有一种方式,那就是创建Thread类,而实现线程的run方法有两种方式。除此之外,从表面上看线程池、定时器等工具类也可创建线程,但是本质没有变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值