跳到主要内容

线程创建方式

在 Java 并发编程中,线程是执行任务的基本单位。Java 提供了多种创建线程的方式,每种方式都有其适用的场景和优缺点。本文将详细介绍这些方式,并通过代码示例和实际案例帮助你更好地理解。

1. 继承 Thread 类

最简单的方式是继承 Thread 类并重写 run() 方法。run() 方法中定义了线程执行的任务。

java
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程正在运行");
}
}

public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}

输出:

线程正在运行
备注

继承 Thread 类的方式简单直接,但由于 Java 不支持多继承,这种方式限制了类的扩展性。

2. 实现 Runnable 接口

更推荐的方式是实现 Runnable 接口。这种方式更灵活,因为一个类可以实现多个接口。

java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程正在运行");
}
}

public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // 启动线程
}
}

输出:

线程正在运行
提示

实现 Runnable 接口的方式更灵活,适合需要扩展其他功能的场景。

3. 使用 Callable 和 Future

Callable 接口与 Runnable 类似,但它可以返回一个结果,并且可以抛出异常。Future 用于获取 Callable 的返回结果。

java
import java.util.concurrent.*;

class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "线程执行完成";
}
}

public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // 获取线程执行结果
executor.shutdown();
}
}

输出:

线程执行完成
警告

CallableFuture 适用于需要返回结果或处理异常的场景,但需要注意线程池的管理。

4. 使用线程池

线程池是一种管理线程的机制,可以有效地复用线程,减少线程创建和销毁的开销。

java
import java.util.concurrent.*;

public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);

for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在运行");
});
}

executor.shutdown();
}
}

输出:

线程 pool-1-thread-1 正在运行
线程 pool-1-thread-2 正在运行
线程 pool-1-thread-1 正在运行
线程 pool-1-thread-2 正在运行
线程 pool-1-thread-1 正在运行
注意

线程池适用于需要执行大量短期任务的场景,但需要注意线程池的大小和任务队列的管理。

实际案例:多线程下载文件

假设我们需要从多个 URL 下载文件,可以使用线程池来提高下载效率。

java
import java.util.concurrent.*;

public class FileDownloader {
public static void main(String[] args) {
String[] urls = {"url1", "url2", "url3"};
ExecutorService executor = Executors.newFixedThreadPool(urls.length);

for (String url : urls) {
executor.submit(() -> {
System.out.println("开始下载 " + url);
// 模拟下载过程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("下载完成 " + url);
});
}

executor.shutdown();
}
}

输出:

开始下载 url1
开始下载 url2
开始下载 url3
下载完成 url1
下载完成 url2
下载完成 url3

总结

Java 提供了多种创建线程的方式,每种方式都有其适用的场景。继承 Thread 类简单直接,但灵活性较差;实现 Runnable 接口更灵活,适合需要扩展功能的场景;CallableFuture 适用于需要返回结果或处理异常的场景;线程池则适用于需要管理大量线程的场景。

附加资源

练习

  1. 尝试使用 Runnable 接口实现一个多线程计数器。
  2. 使用 CallableFuture 实现一个多线程计算斐波那契数列的程序。
  3. 使用线程池实现一个多线程文件下载器,并测试其性能。