Java 并发编程创建线程的四种方式示例详解
作者:钟琛
本文对比Java创建线程的四种方式:继承Thread类(单继承限制)、实现Runnable接口(灵活推荐)、实现Callable接口(支持返回值)、使用线程池(最佳实践,资源管理高效),推荐优先使用线程池提升系统稳定性与资源利用率,感兴趣的朋友一起看看吧
1. 继承 Thread 类
特点:
- 直接继承
Thread
类 - 重写
run()
方法 - 启动线程调用
start()
方法 - 缺点:Java 单继承限制,不够灵活
class MyThread extends Thread { @Override public void run() { // 线程执行逻辑 System.out.println("继承Thread类的线程: " + Thread.currentThread().getName()); } } public class ThreadDemo { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // 启动线程 } }
2. 实现 Runnable 接口(推荐)
特点:
- 实现
Runnable
接口 - 线程与任务解耦
- 可复用同一个任务对象
- 支持 Lambda 表达式简化代码
- 优点:避免单继承限制,更灵活
public class RunnableDemo { public static void main(String[] args) { // 方式1:匿名内部类 new Thread(new Runnable() { @Override public void run() { System.out.println("Runnable匿名内部类线程: " + Thread.currentThread().getName()); } }).start(); // 方式2:Lambda表达式(推荐) new Thread(() -> { System.out.println("Runnable Lambda线程: " + Thread.currentThread().getName()); }).start(); // 方式3:实现类 Runnable task = new MyRunnable(); new Thread(task).start(); } } class MyRunnable implements Runnable { @Override public void run() { System.out.println("实现Runnable接口的线程: " + Thread.currentThread().getName()); } }
3. 实现 Callable 接口(带返回值)
特点:
- 实现
Callable
接口 - 可返回执行结果(通过
FutureTask
) - 可抛出受检异常
- 配合
FutureTask
获取结果 - 适用场景:需要获取异步任务结果的场景
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建Callable任务 Callable<String> callableTask = () -> { Thread.sleep(1000); return "Callable线程执行结果: " + Thread.currentThread().getName(); }; // 包装为FutureTask FutureTask<String> futureTask = new FutureTask<>(callableTask); // 启动线程 new Thread(futureTask).start(); // 获取结果(会阻塞直到任务完成) String result = futureTask.get(); System.out.println(result); } }
4. 使用线程池(最佳实践)
特点:
- 使用
ExecutorService
管理线程 - 避免频繁创建销毁线程的开销
- 提供任务队列和线程复用
- 支持两种任务提交方式:
execute()
:提交 Runnable 任务submit()
:提交 Callable 任务,返回 Future
- 最佳实践:生产环境推荐使用
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ThreadPoolDemo { public static void main(String[] args) throws Exception { // 创建固定大小线程池 ExecutorService executor = Executors.newFixedThreadPool(3); // 提交Runnable任务 executor.execute(() -> { System.out.println("线程池执行Runnable任务: " + Thread.currentThread().getName()); }); // 提交Callable任务 Future<String> future = executor.submit(() -> { Thread.sleep(500); return "线程池执行Callable任务结果: " + Thread.currentThread().getName(); }); // 获取Callable结果 System.out.println(future.get()); // 关闭线程池 executor.shutdown(); } }
5.四种方式对比
创建方式 | 返回值 | 异常处理 | 资源消耗 | 灵活性 | 推荐指数 |
---|---|---|---|---|---|
继承 Thread | ❌ | 受限于重写 | 高 | 低 | ⭐☆☆☆ |
实现 Runnable | ❌ | 自由处理 | 中 | 高 | ⭐⭐⭐☆ |
实现 Callable | ✅ | 自由处理 | 中 | 高 | ⭐⭐⭐⭐ |
线程池 | ✅ | 统一管理 | 低 | 极高 | ⭐⭐⭐⭐⭐ |
6.线程池的进阶用法
自定义线程池
import java.util.concurrent.*; public class CustomThreadPool { public static void main(String[] args) { // 创建自定义线程池 ExecutorService customPool = new ThreadPoolExecutor( 4, // 核心线程数 10, // 最大线程数 60, // 空闲线程存活时间 TimeUnit.SECONDS, // 时间单位 new ArrayBlockingQueue<>(100), // 任务队列 Executors.defaultThreadFactory(), // 线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); // 提交任务 for (int i = 0; i < 20; i++) { int taskId = i; customPool.execute(() -> { System.out.println("执行任务 " + taskId + " 线程: " + Thread.currentThread().getName()); try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // 优雅关闭 customPool.shutdown(); try { if (!customPool.awaitTermination(60, TimeUnit.SECONDS)) { customPool.shutdownNow(); } } catch (InterruptedException e) { customPool.shutdownNow(); Thread.currentThread().interrupt(); } } }
线程池关键参数解析
参数 | 说明 |
---|---|
核心线程数 | 线程池长期维持的线程数 |
最大线程数 | 线程池允许的最大线程数 |
空闲存活时间 | 非核心线程空闲超过此时间将被回收 |
任务队列 | 存放待执行任务的阻塞队列 |
线程工厂 | 用于创建新线程,可自定义线程名称、优先级等 |
拒绝策略 | 当任务太多时的处理策略(Abort-抛异常, Discard-丢弃, CallerRuns-调用者执行) |
四种拒绝策略对比
策略 | 行为 |
---|---|
AbortPolicy | 默认策略,抛出 RejectedExecutionException |
DiscardPolicy | 静默丢弃新任务 |
DiscardOldestPolicy | 丢弃队列中最老的任务,然后重试提交 |
CallerRunsPolicy | 由调用者线程(提交任务的线程)直接执行该任务 |
实践建议
- 优先选择线程池
- 资源利用率高
- 避免线程创建销毁开销
- 提供更好的系统稳定性
- 避免直接创建线程
// 不推荐 new Thread(() -> {...}).start(); // 推荐使用线程池 executor.execute(() -> {...});
- 合理配置线程池参数
- CPU 密集型任务:线程数 ≈ CPU 核数
- IO 密集型任务:线程数 ≈ CPU 核数 * (1 + 平均等待时间/平均计算时间)
- CPU密集型任务:较小队列(避免任务堆积)
- IO密集型任务:较大队列(充分利用CPU)
- 使用合适的任务类型
- 不需要返回值:
Runnable
+execute()
- 需要返回值:
Callable
+submit()
- 不需要返回值:
- 正确处理线程池关闭
shutdown()
:停止接收新任务,处理队列中任务shutdownNow()
:尝试中断所有任务awaitTermination()
:等待线程池完全终止
7.线程池任务处理流程图
最大处理能力 = 核心线程数 + 任务队列容量 + (最大线程数 - 核心线程数)
任务的执行顺序不一定与提交顺序一致
举个例子(假设任务执行时间较长,核心线程一直被任务1,任务2占用):核心线程数是2,最大线程数是10,任务队列长5;那么最大处理能力为15(2+5+8),第16个任务执行拒绝策略,任务3会在任务队列里排队,而任务8在发现核心线程都在工作时且任务队列已满,但线程数小于最大线程的情况下,会创建非核心线程,并直接开始执行任务8,所以说任务8会比任务3更快执行,因此任务的执行顺序不一定与提交顺序一致
到此这篇关于Java 并发编程:创建线程的 4 种方式的文章就介绍到这了,更多相关java创建线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!