在Java中,创建线程的方式有多种。尽管我们常常听到三种或四种主要的创建方式,但本质上都是通过new Thread().start()
来启动线程。本文将从源码角度详细解读这些创建线程的方法,并说明每种方式的具体应用场景和优缺点。
1. 继承Thread
类
这是最直接的一种方式。通过继承Thread
类并重写其run
方法来定义线程执行的任务。
java
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
源码解读:
当我们调用start()
方法时,实际上是调用了Thread
类中的start0()
方法,这是一个本地方法(native),用于启动新线程。
java
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
start0();
}
优缺点:
- 优点:简单直接,适合少量的线程创建。
- 缺点:由于Java是单继承模型,继承
Thread
类会限制类的扩展能力。
2. 实现Runnable
接口
相比于继承Thread
类,实现Runnable
接口更为灵活,因为它不受限于单继承模型。
java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // 启动线程
}
}
源码解读:
Thread
类的构造函数可以接受一个Runnable
对象。当run
方法被调用时,实际执行的是传入的Runnable
对象的run
方法。
java
public void run() {
if (target != null) {
target.run();
}
}
优缺点:
- 优点:适合需要共享资源的多个线程,可以避免单继承的限制。
- 缺点:代码略显复杂,需要额外传递
Runnable
对象。
3. 实现Callable
接口
Callable
接口与Runnable
接口类似,但它可以有返回值,并且可以抛出异常。
java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Thread is running";
}
public static void main(String[] args) {
MyCallable callable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
try {
String result = futureTask.get(); // 获取返回值
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
源码解读:
FutureTask
类实现了Runnable
接口,并且包含一个Callable
对象。当线程执行时,会调用Callable
对象的call
方法,并将结果保存。
java
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
优缺点:
- 优点:可以有返回值,便于获取线程执行结果。
- 缺点:相对于
Runnable
需要额外的FutureTask
包装。
4. 使用线程池
线程池是管理和复用线程的高级工具,适合高并发的场景。
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println("Thread is running");
});
}
executorService.shutdown();
}
}
源码解读:
ExecutorService
接口提供了管理线程的抽象。通过Executors
类可以方便地创建不同类型的线程池。
java
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addWorker(command, true))
if (isRunning(command))
addWorker(command, false);
}
优缺点:
- 优点:高效管理、复用线程,避免频繁创建销毁线程的开销。
- 缺点:需要适当配置线程池参数,否则可能导致资源浪费或线程不足。
5. 使用CompletableFuture
CompletableFuture
是Java 8引入的异步编程工具,支持链式调用和组合操作。
java
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture.runAsync(() -> {
System.out.println("Thread is running");
}).thenRun(() -> {
System.out.println("Thread has finished");
});
}
}
源码解读:
CompletableFuture
内部使用了ForkJoinPool
来管理线程,支持异步任务的执行和结果的组合。
java
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
优缺点:
- 优点:支持复杂的异步操作组合,便于进行异步编程。
- 缺点:学习曲线较高,适合有一定异步编程经验的开发者。
总结
通过上述几种方式,我们可以灵活地创建和管理线程。每种方法都有其适用的场景和优势,选择合适的线程创建方式可以提高程序的性能和可维护性。
- 继承
Thread
类:简单直接,适合少量线程的简单任务。 - 实现
Runnable
接口:灵活性高,适合共享资源的多线程任务。 - 实现
Callable
接口:适合需要返回结果的线程任务。 - 使用线程池:适合高并发场景,能高效管理和复用线程。
- 使用
CompletableFuture
:支持复杂的异步操作组合,适合现代异步编程。