Java创建线程的三种方式
/ 定义线程类@Override// 线程要执行的任务i < 100;i++) {" 执行:" + i);// 使用线程// 创建线程对象// 启动线程(注意:必须调用start(),不是run())t1.start();t2.start();// 主线程继续执行i < 100;i++) {System.out.println("main线程执行:" + i);
Java创建线程的三种方式
本文将详细讲解Java中创建线程的三种主要方式:Thread、Runnable和Callable,帮助你深入理解它们的原理、使用场景和优缺点。
Thread方式创建线程
基本用法
Thread是Java中最直接创建线程的方式,通过继承Thread类并重写run()方法来定义线程任务。
方式1:定义Thread子类
// 定义线程类
public class MyThread extends Thread {
@Override
public void run() {
// 线程要执行的任务
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() +
" 执行:" + i);
}
}
}
// 使用线程
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 启动线程(注意:必须调用start(),不是run())
t1.start();
t2.start();
// 主线程继续执行
for (int i = 0; i < 100; i++) {
System.out.println("main线程执行:" + i);
}
}
}
方式2:匿名内部类
public class ThreadDemo {
public static void main(String[] args) {
// 使用匿名内部类创建线程
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(getName() + " 执行:" + i);
}
}
};
t1.start();
// 主线程任务
for (int i = 0; i < 50; i++) {
System.out.println("main线程执行:" + i);
}
}
}
Thread构造器
Thread类提供了多个构造器:
// 无参构造
public Thread()
// 指定线程名称
public Thread(String name)
// 指定Runnable任务
public Thread(Runnable target)
// 指定Runnable任务和线程名称
public Thread(Runnable target, String name)
// 指定线程组和Runnable任务
public Thread(ThreadGroup group, Runnable target)
// 更多构造器...
使用示例:
// 创建带名称的线程
Thread t1 = new MyThread();
t1.setName("工作线程-1"); // 设置名称
// 或直接通过构造器设置
Thread t2 = new Thread("工作线程-2") {
@Override
public void run() {
System.out.println(getName() + " 正在运行");
}
};
优缺点分析
✅ 优点
- 编码简单:直接继承Thread类,重写run方法即可
- 直接操作:可以直接在类中操作Thread的方法
public class MyThread extends Thread {
@Override
public void run() {
// 可以直接使用getName()、getId()等方法
System.out.println("线程名: " + getName());
System.out.println("线程ID: " + getId());
}
}
❌ 缺点
- 单继承限制:Java只支持单继承,继承Thread后无法继承其他类
- 耦合度高:线程任务和线程本身耦合在一起,不利于复用
// ❌ 错误示例:无法再继承其他类
public class MyThread extends Thread, SomeOtherClass { // 编译错误!
// ...
}
// ✅ 正确做法:使用Runnable接口
public class MyTask extends SomeOtherClass implements Runnable {
@Override
public void run() {
// ...
}
}
Runnable方式创建线程
基本原理
Runnable是一个函数式接口,只包含一个抽象方法run():
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Thread类本身也实现了Runnable接口:
public class Thread implements Runnable {
private Runnable target; // 持有Runnable对象
public Thread(Runnable target) {
this.target = target;
}
@Override
public void run() {
// 如果有target,执行target的run方法
if (target != null) {
target.run();
}
}
}
基本用法
方式1:实现Runnable接口
// 定义任务类
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() +
" -> " + i);
}
}
}
// 使用任务
public class RunnableDemo {
public static void main(String[] args) {
// 创建任务对象
Runnable task = new MyRunnable();
// 把任务对象包装成线程对象
Thread t1 = new Thread(task, "1号线程");
Thread t2 = new Thread(task, "2号线程");
// 启动线程
t1.start();
t2.start();
}
}
方式2:匿名内部类
public class RunnableDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() +
" -> " + i);
}
}
}, "工作线程");
t1.start();
}
}
方式3:Lambda表达式(推荐)
因为Runnable是函数式接口,可以使用Lambda表达式简化:
public class RunnableDemo {
public static void main(String[] args) {
// 使用Lambda表达式
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() +
" -> " + i);
}
}, "工作线程");
t1.start();
}
}
优缺点分析
✅ 优点
- 避免单继承限制:实现接口的方式,还可以继承其他类
public class MyTask extends BaseClass implements Runnable {
@Override
public void run() {
// 可以同时继承和实现接口
}
}
- 资源共享:同一个Runnable对象可以被多个线程共享
public class TicketRunnable implements Runnable {
private int ticketCount = 100; // 共享的票数
@Override
public void run() {
while (ticketCount > 0) {
System.out.println(Thread.currentThread().getName() +
" 卖出第" + ticketCount-- + "张票");
}
}
}
// 多个线程共享同一个任务对象
Runnable task = new TicketRunnable();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
- 解耦设计:线程任务代码与线程对象分离,代码更灵活
// 任务可以独立测试和复用
Runnable task = new DataProcessTask();
// 可以用不同方式执行同一个任务
new Thread(task).start(); // 方式1:新线程
task.run(); // 方式2:当前线程
executorService.execute(task); // 方式3:线程池
- 适合线程池:线程池接受Runnable和Callable任务
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new MyRunnable()); // 提交Runnable任务
❌ 缺点
- 代码稍复杂:相比Thread方式多了一层包装
- 无返回值:run()方法没有返回值
Callable方式创建线程
基本原理
Callable是一个泛型接口,可以返回结果并且可以抛出异常:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
关键组件:
Callable<V>:定义有返回值的任务FutureTask<V>:包装Callable,实现Runnable接口Future<V>:表示异步计算的结果
关系图:
Callable<V>
↓ (包装)
FutureTask<V> (implements RunnableFuture<V>)
↓ (implements)
Runnable + Future
↓ (传入)
Thread
基本用法
完整示例
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 1. 定义任务类,实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
// 模拟耗时计算
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
Thread.sleep(10); // 模拟耗时
}
return Thread.currentThread().getName() +
" 计算1到" + n + "的和为:" + sum;
}
}
// 2. 使用Callable
public class CallableDemo {
public static void main(String[] args) {
try {
// 创建Callable任务对象
Callable<String> callable = new MyCallable(100);
// 把Callable包装成FutureTask(未来任务对象)
FutureTask<String> futureTask = new FutureTask<>(callable);
// 把FutureTask包装成Thread
Thread t1 = new Thread(futureTask, "计算线程");
// 启动线程
t1.start();
// 主线程继续执行其他任务
System.out.println("主线程继续执行其他任务...");
// 获取线程执行结果(会阻塞等待)
String result = futureTask.get();
System.out.println("获取到结果:" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
FutureTask详解
FutureTask是一个可以取消的异步计算任务,它实现了RunnableFuture接口:
public class FutureTask<V> implements RunnableFuture<V> {
// 内部状态
private volatile int state;
private static final int NEW = 0; // 新建
private static final int COMPLETING = 1; // 完成中
private static final int NORMAL = 2; // 正常完成
private static final int EXCEPTIONAL = 3; // 异常
private static final int CANCELLED = 4; // 已取消
// 保存Callable任务
private Callable<V> callable;
// 保存执行结果
private Object outcome;
// 构造器
public FutureTask(Callable<V> callable) {
this.callable = callable;
this.state = NEW;
}
// 执行任务
public void run() {
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
try {
result = c.call(); // 调用Callable的call方法
set(result); // 设置结果
} catch (Throwable ex) {
setException(ex); // 设置异常
}
}
} finally {
// ...
}
}
// 获取结果
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L); // 等待任务完成
return report(s); // 返回结果
}
}
常用方法
1. get() - 获取结果
// 阻塞等待,直到任务完成
V get() throws InterruptedException, ExecutionException
// 限时等待
V get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException,
TimeoutException
使用示例:
FutureTask<Integer> task = new FutureTask<>(() -> {
Thread.sleep(2000);
return 42;
});
new Thread(task).start();
// 方式1:无限等待
Integer result = task.get();
// 方式2:限时等待(推荐)
try {
Integer result = task.get(3, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("任务超时!");
task.cancel(true); // 取消任务
}
2. cancel() - 取消任务
boolean cancel(boolean mayInterruptIfRunning)
FutureTask<String> task = new FutureTask<>(() -> {
for (int i = 0; i < 10; i++) {
if (Thread.currentThread().isInterrupted()) {
return "任务被中断";
}
Thread.sleep(1000);
}
return "任务完成";
});
new Thread(task).start();
Thread.sleep(2000);
// 取消任务(尝试中断正在执行的任务)
boolean cancelled = task.cancel(true);
System.out.println("取消成功:" + cancelled);
3. 状态查询方法
boolean isDone() // 任务是否完成
boolean isCancelled() // 任务是否被取消
System.out.println("任务是否完成:" + task.isDone());
System.out.println("任务是否取消:" + task.isCancelled());
优缺点分析
✅ 优点
- 可以获取返回值:这是Callable最大的优势
Callable<Integer> task = () -> {
// 复杂计算...
return 42;
};
FutureTask<Integer> future = new FutureTask<>(task);
new Thread(future).start();
Integer result = future.get(); // 获取返回值
- 可以抛出异常:call()方法可以抛出受检异常
Callable<String> task = () -> {
if (someCondition) {
throw new Exception("出错了!");
}
return "成功";
};
try {
String result = futureTask.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause(); // 获取原始异常
System.out.println("任务执行出错:" + cause.getMessage());
}
- 适合需要结果的场景:如异步计算、数据查询等
// 异步查询用户信息
Callable<User> queryUser = () -> userService.getUserById(123);
FutureTask<User> future = new FutureTask<>(queryUser);
executor.execute(future);
// 主线程继续其他工作...
doOtherWork();
// 需要时获取结果
User user = future.get();
❌ 缺点
- 编码复杂:需要FutureTask包装,代码较多
- 阻塞获取结果:get()方法会阻塞当前线程
// ❌ 可能导致主线程长时间阻塞
String result = futureTask.get(); // 如果任务很慢,这里会一直等待
// ✅ 建议使用超时机制
try {
String result = futureTask.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("任务超时");
futureTask.cancel(true);
}
三种方式对比
对比表格
| 特性 | Thread | Runnable | Callable |
|---|---|---|---|
| 实现方式 | 继承Thread类 | 实现Runnable接口 | 实现Callable接口 |
| 方法签名 | void run() |
void run() |
V call() throws Exception |
| 返回值 | ❌ 无 | ❌ 无 | ✅ 有 |
| 异常处理 | 无法抛出受检异常 | 无法抛出受检异常 | 可以抛出受检异常 |
| 单继承限制 | ❌ 有限制 | ✅ 无限制 | ✅ 无限制 |
| 资源共享 | 不便共享 | ✅ 便于共享 | ✅ 便于共享 |
| 线程池支持 | 不推荐 | ✅ 支持 | ✅ 支持 |
| 代码复杂度 | ⭐ 简单 | ⭐⭐ 一般 | ⭐⭐⭐ 较复杂 |
| 使用场景 | 简单任务 | 一般任务 | 需要返回值的任务 |
使用建议
🎯 选择Thread的场景
// ✅ 适用于:简单的、一次性的线程任务
new Thread() {
@Override
public void run() {
System.out.println("简单任务");
}
}.start();
🎯 选择Runnable的场景
// ✅ 适用于:
// 1. 需要继承其他类
// 2. 多个线程共享资源
// 3. 与线程池配合使用
// 场景1:需要继承
public class MyTask extends BaseClass implements Runnable {
@Override
public void run() {
// ...
}
}
// 场景2:资源共享
Runnable task = new TicketSeller();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
// 场景3:线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.execute(() -> System.out.println("任务执行"));
🎯 选择Callable的场景
// ✅ 适用于:
// 1. 需要获取执行结果
// 2. 需要处理异常
// 3. 异步计算场景
// 场景1:需要返回值
Callable<Integer> sumTask = () -> {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
};
// 场景2:异步计算
ExecutorService pool = Executors.newFixedThreadPool(5);
Future<String> future = pool.submit(() -> {
// 复杂计算...
return "计算结果";
});
String result = future.get();
深入理解start和run
start()方法
作用:启动新线程,由JVM调用该线程的run()方法。
public synchronized void start() {
// 1. 检查线程状态
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 2. 加入线程组
group.add(this);
boolean started = false;
try {
// 3. 调用native方法启动线程
start0(); // 这是一个native方法
started = true;
} finally {
// ...
}
}
// native方法,由JVM实现
private native void start0();
底层流程:
调用start()
↓
JVM调用start0()(native方法)
↓
操作系统创建新线程
↓
新线程进入就绪状态
↓
获得CPU时间片
↓
JVM回调run()方法
↓
线程执行
run()方法
本质:一个普通的实例方法。
@Override
public void run() {
if (target != null) {
target.run(); // 执行Runnable的run方法
}
}
start() vs run() 对比
❌ 错误:直接调用run()
Thread t1 = new Thread(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
});
t1.run(); // ❌ 错误:在main线程中同步执行
// 输出:当前线程:main(不是t1!)
后果:
- ❌ 不会创建新线程
- ❌ 在当前线程(如main)中同步执行
- ❌ 失去多线程的意义
✅ 正确:调用start()
Thread t1 = new Thread(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
});
t1.start(); // ✅ 正确:启动新线程
// 输出:当前线程:Thread-0(新线程!)
效果:
- ✅ 创建新线程
- ✅ 新线程与main线程并发执行
- ✅ 体现多线程的优势
形象比喻
start() = 聘请一个新员工(新线程)
run() = 自己干活(当前线程)
// 调用start()
老板(main线程): "小李(新线程),你去做这个任务"
小李: "好的!" (并行工作)
老板: 继续做自己的事情
// 直接调用run()
老板(main线程): "算了,我自己做吧"
老板: 亲自做这个任务(没有小李什么事)
实验验证
public class StartVsRun {
public static void main(String[] args) {
System.out.println("=== 测试run() ===");
testRun();
System.out.println("\n=== 测试start() ===");
testStart();
}
// 直接调用run()
private static void testRun() {
Thread t = new Thread(() -> {
System.out.println(" 任务线程: " +
Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "WorkThread");
long start = System.currentTimeMillis();
t.run(); // 直接调用run
System.out.println(" 主线程: " + Thread.currentThread().getName());
System.out.println(" 耗时: " + (System.currentTimeMillis() - start) + "ms");
}
// 调用start()
private static void testStart() {
Thread t = new Thread(() -> {
System.out.println(" 任务线程: " +
Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "WorkThread");
long start = System.currentTimeMillis();
t.start(); // 调用start
System.out.println(" 主线程: " + Thread.currentThread().getName());
System.out.println(" 耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
运行结果:
=== 测试run() ===
任务线程: main ← run在main线程执行
主线程: main
耗时: 1000ms ← 阻塞了1秒
=== 测试start() ===
主线程: main
耗时: 0ms ← 立即返回,不阻塞
任务线程: WorkThread ← 在新线程执行
最佳实践
1. 优先使用Runnable或Callable
// ✅ 推荐
class MyTask implements Runnable {
@Override
public void run() {
// ...
}
}
// ❌ 不推荐(除非确实需要继承Thread)
class MyThread extends Thread {
@Override
public void run() {
// ...
}
}
原因:
- 避免单继承限制
- 代码更灵活,易于测试
- 便于与线程池配合
2. 使用Lambda表达式简化代码
// ❌ 繁琐写法
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
});
// ✅ 简洁写法
Thread t2 = new Thread(() -> System.out.println("Hello"));
3. 给线程命名
// ✅ 良好实践:给线程起有意义的名字
Thread t = new Thread(() -> {
// ...
}, "数据处理线程-1");
// 或者
Thread t = new Thread(() -> {
// ...
});
t.setName("数据处理线程-1");
好处:
- 便于调试和日志分析
- 出现问题时容易定位
4. 处理异常
// ✅ Runnable:内部处理异常
Thread t = new Thread(() -> {
try {
// 可能抛出异常的代码
riskyOperation();
} catch (Exception e) {
// 必须在内部捕获
logger.error("任务执行失败", e);
}
});
// ✅ Callable:可以向外抛出异常
Callable<String> task = () -> {
// 可以直接抛出异常
if (error) {
throw new Exception("出错了");
}
return "成功";
};
5. 使用线程池而不是直接创建线程
// ❌ 不推荐:频繁创建销毁线程
for (int i = 0; i < 1000; i++) {
new Thread(() -> doWork()).start();
}
// ✅ 推荐:使用线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.execute(() -> doWork());
}
pool.shutdown();
实战案例
案例1:多线程下载器
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class MultiThreadDownloader {
// 使用Callable实现带返回值的下载任务
static class DownloadTask implements Callable<DownloadResult> {
private String url;
private String filename;
public DownloadTask(String url, String filename) {
this.url = url;
this.filename = filename;
}
@Override
public DownloadResult call() throws Exception {
long startTime = System.currentTimeMillis();
long fileSize = 0;
try {
URL downloadUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) downloadUrl.openConnection();
// 获取文件大小
fileSize = conn.getContentLengthLong();
// 下载文件
try (InputStream in = conn.getInputStream();
FileOutputStream out = new FileOutputStream(filename)) {
byte[] buffer = new byte[8192];
int bytesRead;
long totalRead = 0;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
totalRead += bytesRead;
// 打印进度
int progress = (int) (totalRead * 100 / fileSize);
System.out.printf("[%s] 下载进度: %d%%\r",
filename, progress);
}
}
long endTime = System.currentTimeMillis();
return new DownloadResult(filename, fileSize, endTime - startTime, true);
} catch (Exception e) {
return new DownloadResult(filename, fileSize, 0, false);
}
}
}
// 下载结果
static class DownloadResult {
String filename;
long fileSize;
long timeMillis;
boolean success;
public DownloadResult(String filename, long fileSize, long timeMillis, boolean success) {
this.filename = filename;
this.fileSize = fileSize;
this.timeMillis = timeMillis;
this.success = success;
}
@Override
public String toString() {
if (success) {
double speed = fileSize / 1024.0 / 1024.0 / (timeMillis / 1000.0);
return String.format("%s 下载成功!大小: %.2fMB, 耗时: %.2fs, 速度: %.2fMB/s",
filename, fileSize / 1024.0 / 1024.0, timeMillis / 1000.0, speed);
} else {
return filename + " 下载失败!";
}
}
}
public static void main(String[] args) throws Exception {
// 模拟下载任务
String[] urls = {
"http://example.com/file1.zip",
"http://example.com/file2.zip",
"http://example.com/file3.zip"
};
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交所有下载任务
List<Future<DownloadResult>> futures = new ArrayList<>();
for (int i = 0; i < urls.length; i++) {
DownloadTask task = new DownloadTask(urls[i], "file" + (i + 1) + ".zip");
Future<DownloadResult> future = executor.submit(task);
futures.add(future);
}
// 获取所有下载结果
System.out.println("\n=== 下载结果汇总 ===");
for (Future<DownloadResult> future : futures) {
DownloadResult result = future.get(); // 阻塞等待结果
System.out.println(result);
}
executor.shutdown();
}
}
案例2:售票系统(资源共享)
public class TicketSystem {
// 使用Runnable实现资源共享
static class TicketSeller implements Runnable {
private int ticketCount = 100; // 共享的票数
private final Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (ticketCount <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() +
" 卖出第 " + ticketCount + " 张票");
ticketCount--;
try {
Thread.sleep(10); // 模拟卖票耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
// 创建一个共享的售票任务
TicketSeller seller = new TicketSeller();
// 创建多个售票窗口(共享同一个seller对象)
Thread window1 = new Thread(seller, "窗口1");
Thread window2 = new Thread(seller, "窗口2");
Thread window3 = new Thread(seller, "窗口3");
window1.start();
window2.start();
window3.start();
}
}
案例3:并行计算(Callable)
import java.util.*;
import java.util.concurrent.*;
public class ParallelCalculator {
// 使用Callable实现带返回值的计算任务
static class SumTask implements Callable<Long> {
private long start;
private long end;
public SumTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
public Long call() throws Exception {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName() +
" 计算 [" + start + ", " + end + "] = " + sum);
return sum;
}
}
public static void main(String[] args) throws Exception {
// 计算1到10亿的和,分成10个任务并行计算
long n = 1_000_000_000L;
int threadCount = 10;
long range = n / threadCount;
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
// 提交所有子任务
List<Future<Long>> futures = new ArrayList<>();
long startTime = System.currentTimeMillis();
for (int i = 0; i < threadCount; i++) {
long start = i * range + 1;
long end = (i == threadCount - 1) ? n : (i + 1) * range;
SumTask task = new SumTask(start, end);
Future<Long> future = executor.submit(task);
futures.add(future);
}
// 汇总所有结果
long totalSum = 0;
for (Future<Long> future : futures) {
totalSum += future.get(); // 获取每个子任务的结果
}
long endTime = System.currentTimeMillis();
System.out.println("\n=== 计算结果 ===");
System.out.println("1到" + n + "的和为: " + totalSum);
System.out.println("耗时: " + (endTime - startTime) + "ms");
// 对比单线程计算
startTime = System.currentTimeMillis();
long singleSum = 0;
for (long i = 1; i <= n; i++) {
singleSum += i;
}
endTime = System.currentTimeMillis();
System.out.println("单线程耗时: " + (endTime - startTime) + "ms");
executor.shutdown();
}
}
🎯 知识点总结
三种方式核心对比
Thread方式:
优点: 简单直接
缺点: 单继承限制
场景: 简单一次性任务
Runnable方式:
优点: 无继承限制、资源共享、灵活
缺点: 无返回值
场景: 大部分多线程场景(推荐)
Callable方式:
优点: 有返回值、可抛异常
缺点: 代码复杂、获取结果阻塞
场景: 需要返回值的异步任务
最佳实践总结
- ✅ 优先使用Runnable或Callable
- ✅ 使用Lambda表达式简化代码
- ✅ 给线程起有意义的名字
- ✅ 使用线程池而不是直接创建线程
- ✅ 必须调用start()而不是run()
- ✅ 正确处理异常
💡 常见面试题
Q1:创建线程有几种方式?
答:主要有三种方式:
- 继承Thread类
- 实现Runnable接口(推荐)
- 实现Callable接口(需要返回值时使用)
Q2:为什么推荐使用Runnable而不是Thread?
答:
- Java只支持单继承,实现接口更灵活
- Runnable可以方便地实现资源共享
- 代码解耦,任务与线程分离
- 适合与线程池配合使用
Q3:Runnable和Callable的区别?
答:
- 返回值:Callable有返回值,Runnable没有
- 异常:Callable可以抛出受检异常,Runnable不能
- 方法:Callable是call()方法,Runnable是run()方法
- 使用:Callable需要FutureTask包装
Q4:为什么不能直接调用run()方法?
答:直接调用run()不会创建新线程,run()会在当前线程中同步执行,失去了多线程的意义。必须调用start()方法,由JVM创建新线程并在新线程中调用run()方法。
Q5:一个线程可以多次调用start()吗?
答:不可以。一个线程对象只能调用一次start()方法,第二次调用会抛出IllegalThreadStateException。如果需要重复执行任务,应该创建新的线程对象或使用线程池。
更多推荐


所有评论(0)