目录
前言
java线程的创建方式是一道经典的面试题,在Java中,创建线程主要有四种方式,每种方式各有特点,适合不同的使用场景。面试中,不仅要了解其创建方式,更要熟悉其应用场景。
1. 继承Thread类
使用:创建一个新的类继承自java.lang.Thread
类,并重写其run()
方法,在run()
方法中定义线程需要执行的逻辑。然后创建这个子类的实例,并调用其start()
方法来启动线程。
优点:实现简单,可以直接操作线程对象。
缺点:Java不支持多重继承,如果类已经继承了其他类,就不能再继承Thread
类。
示例代码:
class MyThread extends Thread {
public void run() {
System.out.println("通过继承Thread类创建线程");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // 启动线程
}
}
2. 实现Runnable接口
使用:定义一个类实现java.lang.Runnable
接口,实现run()
方法,然后将该类的实例作为参数传递给Thread
类的构造函数,创建Thread
对象。最后调用Thread
对象的start()
方法来启动线程。
优点:由于Java支持接口的多重实现,因此这种方式更为灵活,可以避免单继承的局限性,同时使得线程任务(Runnable对象)与线程(Thread对象)分离,有利于共享资源。
示例代码:
class MyRunnable implements Runnable {
public void run() {
System.out.println("通过实现Runnable接口创建线程");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start(); // 启动线程
}
}
3.实现Callable接口
Callable有些麻烦。
首先,你需要定义一个类实现Callable
接口。Callable
接口有一个泛型参数V,表示call()方法的返回类型。call()方法可以抛出异常,这是它与Runnable
接口的run()
方法的主要区别。
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum; // 返回求和结果
}
}
3.1使用FutureTask包装Callable
接下来,由于Thread
类只能接受Runnable
对象,所以需要使用FutureTask
来包装Callable
对象。FutureTask
是一个实现了Runnable
接口的类,同时也允许你获取Callable
的返回值和检查任务的状态。
import java.util.concurrent.FutureTask;
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
3.2创建并启动线程
最后,你可以通过创建Thread
实例并传入FutureTask
对象来启动线程。
public class Main {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start(); // 启动线程
try {
// 获取并打印线程执行结果
Integer result = futureTask.get();
System.out.println("计算结果为:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
4.使用Executor框架
除了上述三种基本方式,Java还提供了Executor框架(位于java.util.concurrent
包中),它是创建和管理线程的高级工具。最常用的是ExecutorService
接口,它提供了线程池管理功能,通过Executors
类的工厂方法可以创建不同类型的线程池,如newFixedThreadPool
、newSingleThreadExecutor
、newCachedThreadPool
等。
优点:线程池可以重用线程,减少线程创建和销毁的开销,提高响应速度和系统的资源利用率。
示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Task implements Runnable {
public void run() {
System.out.println("通过Executor框架创建线程");
}
}
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new Task()); // 提交任务给线程池执行
executor.shutdown(); // 关闭线程池
}
}
5.应用场景
在实际开发中,使用ExecutorService
和Callable/Runnable
结合的方式更为常见,尤其是当需要处理并发任务、管理线程池、获取任务结果或处理异常时。
后台任务和异步处理场景:当需要执行一些耗时操作但不希望阻塞主线程时,如文件上传、数据库操作、网络请求等,推荐使用ExecutorService
搭配Runnable
或Callable
。对于需要获取结果的场景,应使用Callable。
定时任务和调度场景:如定时数据同步、定时清理任务等,虽然直接使用 Scheduled ExecutorService
(ExecutorService的扩展)更合适,但底层仍然是基于Runnable
或Callable
的
批量数据处理场景:处理大量数据,如数据导入导出、数据分析等,通过ExecutorService
提交多个任务,根据数据分片或逻辑单元进行并行处理,能显著提升处理速度。
6总结
创建线程的方式有四种,要根据不同的需求,选择不同的方式额。