在 《Java异步编程 | CompletableFuture--实现复杂的异步控制流
》中,我们提到了 通过 自定义线程池 , 用于管理 异步任务的执行,避免频繁创建和销毁线程,提高性能。本文将整理并介绍一些 Java 中常见的 多线程创建方式,用于 更好地理解和选择 合适的线程管理策略。
本文整理了下述5种 创建多线程 的方式
- 实现 Runnable 接口
- 继承 Thread 类
- 使用 Callable 和 FutureTask
- 使用线程池(ExecutorService)
- CompletableFuture
一、实现 Runnable
接口
- 方式:通过 实现
Runnable
接口的run()
方法 ,定义线程要执行的任务,然后将Runnable
对象 传入Thread
构造函数,最后调用start()
方法启动线程。 - 优点:可以避免继承
Thread
类的限制,Runnable
可以被 多个线程共享。 - 代码示例:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
二、继承 Thread
类
- 方式:直接 继承
Thread
类,并重写run()
方法,创建 子类对象 并调用start()
方法启动线程。 - 优点:简单直观,适用于线程功能比较单一的情况。
- 缺点:Java 不支持多重继承,因此如果已经继承了其他类,就不能再继承
Thread
类。 - 代码示例:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
三、使用 Callable
和 FutureTask
- 方式:
Callable
接口与Runnable
类似,但它可以 返回结果 并 抛出异常。通过FutureTask
来包装Callable
对象,再通过Thread
启动。 - 优点:可以获取任务的 执行结果,并处理异常。
- 代码示例:
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 100;
}
}
public class Main {
public static void main(String[] args) throws Exception {
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
Integer result = task.get(); // 获取任务执行结果
System.out.println("Result: " + result);
}
}
四、使用线程池(ExecutorService
)
- 方式:线程池通过
ExecutorService
提供一个更高层的 API,管理线程的 创建、调度和销毁。可以使用线程池来提交Runnable
或Callable
任务,避免手动管理线程,特别适合 处理大量并发任务。 - 优点:线程池能够重用线程,避免线程的频繁创建和销毁,提高了效率;还可以 控制并发线程的数量。
- 代码示例:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running in a thread pool");
}
}
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个大小为10的线程池
executor.submit(new MyRunnable()); // 提交任务
executor.shutdown(); // 关闭线程池
}
}
(一) 核心方法
submit(Runnable task)
:提交一个没有返回值的任务。submit(Callable<T> task)
:提交一个有返回值的任务,返回一个Future<T>
对象,可以通过get()
方法获取任务的执行结果。invokeAll(Collection<? extends Callable<T>> tasks)
:提交一组任务并等待所有任务完成,返回每个任务的结果。invokeAny(Collection<? extends Callable<T>> tasks)
:提交一组任务,返回最先完成的任务的结果。shutdown()
:启动优雅关闭,停止接收新的任务,并等待现有任务完成。shutdownNow()
:立即关闭线程池,尝试中断所有正在执行的任务并返回待执行的任务列表。
(二) ExecutorService
的实现类
Java 提供了几种常用的线程池实现类:
1. newFixedThreadPool(int nThreads)
- 创建一个固定大小的线程池,线程池中始终有固定数量的线程。适合于处理需要固定线程数的任务。
- 如果线程池中有线程空闲,新的任务将由空闲线程执行;如果线程池已满,新任务将被放入任务队列等待。
示例:
ExecutorService executor = Executors.newFixedThreadPool(3);
2. newCachedThreadPool()
- 创建一个可以根据需要创建新线程的线程池,如果线程池中的线程空闲超过60秒就会被回收。适合于执行大量短时间的异步任务。
- 线程池的大小根据系统需求动态调整。
示例:
ExecutorService executor = Executors.newCachedThreadPool();
3. newSingleThreadExecutor()
- 创建一个只有一个线程的线程池,所有提交的任务将顺序执行。适合于需要按顺序执行任务的场景。
示例:
ExecutorService executor = Executors.newSingleThreadExecutor();
4. newScheduledThreadPool(int corePoolSize)
- 创建一个固定大小的线程池,适用于定时任务和周期性任务的执行。
示例:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
executor.schedule(() -> {
System.out.println("Scheduled task executed");
}, 1, TimeUnit.SECONDS);
(三) ExecutorService
的作用
- 管理线程池:
ExecutorService
负责创建和管理一个或多个线程池,它从池中获取线程来执行任务。与直接创建线程(如通过new Thread()
)相比,使用线程池更加高效和灵活,特别是在处理大量短小任务时,线程池能有效避免线程频繁创建和销毁的开销。 - 任务提交: 它通过
submit()
方法将任务提交给线程池执行,返回一个Future
对象,Future
允许我们获取任务的执行结果或取消任务。 - 任务调度:
ExecutorService
提供了强大的任务调度功能,可以安排任务在未来某个时刻执行,或者定期执行任务。例如,使用schedule()
方法,任务可以按照固定的延迟执行或以固定的周期重复执行。 - 线程复用: 线程池内部维护了一组线程,线程池中的线程可以被复用,执行完一个任务后,线程会返回池中准备执行下一个任务。这样避免了每次执行任务都需要创建和销毁线程的性能消耗。
- 优雅关闭:
ExecutorService
提供了shutdown()
方法来优雅地关闭线程池,这样线程池中的线程会完成正在执行的任务并终止,新的任务将不会被接受。它还提供了shutdownNow()
方法,用于立即关闭线程池并尝试停止所有正在执行的任务。
五、CompletableFuture
(本质也是线程池)
有关CompletableFuture,详见 《Java异步编程 | CompletableFuture–实现复杂的异步控制流》
- 方式:
CompletableFuture
是 Java 8 引入的 API,用于处理异步编程。它可以非常方便地处理异步任务之间的依赖关系,并支持链式调用。它默认使用ForkJoinPool
来执行任务,提供了比Future
更强大的功能。 - 优点:支持异步编程,并能链式调用,简化了异步任务的处理。
- 代码示例:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Asynchronous task");
});
future.thenRun(() -> {
System.out.println("Another task after first one");
});