Java 多线程
一、一句话总结
多线程 = 让程序同时做多件事。通过线程实现并发,提高CPU利用率,但需要处理线程安全和同步问题
二、三种创建线程的方式
1. 继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 运行中");
}
}
// 使用
MyThread thread = new MyThread();
thread.start(); // 启动线程
2. 实现Runnable接口(推荐)
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 运行中");
}
}
// 使用
Thread thread = new Thread(new MyRunnable());
thread.start();
// 或者使用Lambda(Java 8+)
new Thread(() -> {
System.out.println("Lambda线程");
}).start();
3. 实现Callable接口(有返回值)
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + " 的结果";
}
}
// 使用
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
String result = future.get(); // 获取结果(会阻塞)
executor.shutdown();
三、线程状态和生命周期
新建(New) → 就绪(Runnable) → 运行(Running) → 阻塞(Blocked) → 死亡(Terminated)
start() 调度器选中 wait()/sleep() run()结束
四、线程同步(核心)
1. synchronized关键字
// 1. 同步方法
class Counter {
private int count = 0;
public synchronized void increment() { // 同步方法
count++;
}
public synchronized int getCount() {
return count;
}
}
// 2. 同步代码块
class SafeCounter {
private int count = 0;
private final Object lock = new Object(); // 专用锁对象
public void increment() {
synchronized(lock) { // 同步代码块
count++;
}
}
}
// 3. 静态同步方法(类锁)
class StaticCounter {
private static int count = 0;
public static synchronized void increment() { // 类锁
count++;
}
}
2. Lock接口(更灵活)
import java.util.concurrent.locks.*;
class LockCounter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 必须释放锁
}
}
// 尝试获取锁(避免死锁)
public boolean tryIncrement() {
if (lock.tryLock()) { // 尝试获取锁
try {
count++;
return true;
} finally {
lock.unlock();
}
}
return false;
}
}
3. volatile关键字
// 保证可见性,不保证原子性
class VolatileExample {
private volatile boolean flag = false; // 对多线程可见
public void writer() {
flag = true; // 写操作立即对其它线程可见
}
public void reader() {
if (flag) { // 总是读取最新值
// do something
}
}
}
三种创建线程方式的优缺点对比
一、继承Thread类
优点:
1. **简单直接** - 代码量最少
class SimpleThread extends Thread {
public void run() { /* 任务代码 */ }
}
new SimpleThread().start();
2. **直接访问Thread方法** - 可以直接调用sleep()、interrupt()等
public void run() {
this.sleep(1000); // 直接使用
this.interrupt(); // 直接调用
}
3. **适用于简单场景** - 快速原型开发
缺点:
1. **单继承限制** - Java是单继承,不能再继承其他类
// ❌ 无法同时继承
class MyThread extends Thread, OtherClass { } // 编译错误
2. **任务与线程绑定** - 不利于资源共享
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// t1和t2是不同对象,不能共享同一个任务
3. **不符合面向对象设计** - 线程是执行机制,任务是业务逻辑
// 把业务逻辑和线程机制耦合在一起
适用场景:
- 快速测试、原型开发
- 简单的独立任务
- 需要直接操作线程方法的场景
二、实现Runnable接口(推荐)
优点:
1. **解耦设计** - 任务与线程分离
class MyTask implements Runnable { // 纯任务类
public void run() { /* 业务逻辑 */ }
}
// 可以用不同线程执行同一任务
Thread t1 = new Thread(new MyTask());
Thread t2 = new Thread(new MyTask());
2. **可以继承其他类** - 不受单继承限制
class MyTask extends BaseClass implements Runnable {
// 还可以继承其他类
}
3. **资源共享方便** - 多个线程可共享同一个Runnable实例
Runnable task = new SharedTask();
new Thread(task).start(); // 线程1
new Thread(task).start(); // 线程2 共享同一任务
4. **函数式编程友好** - 支持Lambda表达式
new Thread(() -> System.out.println("Hello")).start();
缺点:
1. **无法直接获取返回值** - run()方法返回void
Runnable task = () -> {
int result = calculate(); // 计算结果无法直接返回
};
2. **不能抛出受检异常** - run()方法签名没有throws
Runnable task = () -> {
// ❌ 不能抛出IOException等受检异常
// throw new IOException(); // 编译错误
};
适用场景:
- 绝大多数情况(90%以上)
- 需要任务复用的场景
- 使用线程池的场景
- Lambda表达式简化代码
三、实现Callable接口(有返回值)
优点:
1. **有返回值** - 可以返回计算结果
Callable<Integer> task = () -> {
return 42; // 可以返回计算结果
};
2. **可以抛出异常** - 能处理受检异常
Callable<String> task = () -> {
if (error) throw new IOException("错误"); // ✅ 可以抛出
return "成功";
};
3. **Future模式支持** - 支持异步获取结果
Future<String> future = executor.submit(callable);
// 继续做其他事情...
String result = future.get(); // 需要时再获取
4. **任务执行状态可控** - 可以取消、查询状态
Future<?> future = executor.submit(task);
future.cancel(true); // 取消任务
future.isDone(); // 查询是否完成
缺点:
1. **使用复杂** - 需要ExecutorService配合
// 不能直接new Thread()
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(callable);
executor.shutdown(); // 必须记得关闭
2. **Future.get()会阻塞** - 可能影响性能
Future<String> future = executor.submit(task);
String result = future.get(); // ❌ 阻塞当前线程
3. **代码量多** - 相比Runnable更繁琐
适用场景:
- 需要返回结果的异步计算
- 需要异常处理的复杂任务
- 使用线程池的批量任务处理
- 需要任务取消和状态查询的场景
四、详细对比表格
| 特性 | Thread类 | Runnable接口 | Callable接口 |
|---|---|---|---|
| 继承限制 | ❌ 单继承限制 | ✅ 可继承其他类 | ✅ 可继承其他类 |
| 代码复杂度 | ⭐⭐ 简单 | ⭐⭐⭐ 中等 | ⭐⭐⭐⭐ 复杂 |
| 返回值 | ❌ 无返回值 | ❌ 无返回值 | ✅ 有返回值 |
| 异常处理 | ❌ 不能抛受检异常 | ❌ 不能抛受检异常 | ✅ 可以抛异常 |
| 资源共享 | ❌ 不方便 | ✅ 很方便 | ✅ 很方便 |
| 线程池支持 | ⚠️ 有限支持 | ✅ 完全支持 | ✅ 完全支持 |
| Lambda支持 | ⚠️ 有限 | ✅ 完美支持 | ✅ 完美支持 |
| 任务控制 | ⚠️ 有限 | ⚠️ 有限 | ✅ 强大(Future) |
| 推荐度 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
五、实际选择建议
场景决策树:
需要创建线程吗?
├── 需要返回结果或抛异常吗?
│ ├── 是 → 使用Callable + 线程池
│ └── 否 → 继续判断
│
├── 任务会被多个线程执行吗?
│ ├── 是 → 使用Runnable接口
│ └── 否 → 继续判断
│
├── 需要继承其他类吗?
│ ├── 是 → 使用Runnable接口
│ └── 否 → 继续判断
│
└── 是简单的一次性任务吗?
├── 是 → 可以考虑Thread(但不推荐)
└── 否 → 使用Runnable接口
代码示例对比:
// 场景:计算1-100的和
// 1. Thread方式(不推荐)
class SumThread extends Thread {
private int result;
public void run() {
for (int i = 1; i <= 100; i++) result += i;
}
public int getResult() { return result; }
}
// 使用不便,需要等待线程结束
// 2. Runnable方式(需要额外处理返回值)
class SumRunnable implements Runnable {
private int result;
public void run() {
for (int i = 1; i <= 100; i++) result += i;
}
public int getResult() { return result; }
}
// 需要手动同步获取结果
// 3. Callable方式(推荐)
class SumCallable implements Callable<Integer> {
public Integer call() {
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum; // 直接返回结果
}
}
// 配合Future,使用方便
最佳实践总结:
-
95%的情况用Runnable
// 简单任务用Lambda new Thread(() -> doSomething()).start(); // 复杂任务用实现类 executor.execute(new MyRunnableTask()); -
需要返回值时用Callable
Future<Integer> future = executor.submit(() -> { return calculateSomething(); }); // 稍后获取结果 Integer result = future.get(); -
避免直接继承Thread
// ❌ 不要这样(除非有特殊需求) class MyThread extends Thread { } // ✅ 应该这样 class MyTask implements Runnable { } -
线程池是标配
// 总是使用线程池,而不是直接new Thread() ExecutorService executor = Executors.newFixedThreadPool(10); executor.execute(() -> doTask()); // 对Runnable Future<?> future = executor.submit(() -> doTask()); // 对Callable
记住:Runnable是默认选择,Callable是高级选择,Thread是最后的选择。
最佳实践总结
✅ 必须遵守的:
- 使用线程池,不要直接new Thread()
- 处理异常,线程中的异常不会传播到主线程
- 释放资源,锁、数据库连接等必须释放
- 避免死锁,按固定顺序获取锁
✅ 推荐做法:
- 优先使用并发集合而不是同步包装
- 使用原子类替代synchronized的简单计数器
- 使用CompletableFuture进行异步编程
- 监控线程状态,及时发现问题
❌ 绝对禁止:
- 不要使用stop()方法强制停止线程
- 不要忽略InterruptedException
- 不要过度同步(缩小同步范围)
- 不要依赖线程优先级
记住:多线程是一把双刃剑,用好了提升性能,用错了制造灾难。理解原理,谨慎使用!