Java 多线程

33 阅读6分钟

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,使用方便

最佳实践总结:

  1. 95%的情况用Runnable

    // 简单任务用Lambda
    new Thread(() -> doSomething()).start();
    
    // 复杂任务用实现类
    executor.execute(new MyRunnableTask());
    
  2. 需要返回值时用Callable

    Future<Integer> future = executor.submit(() -> {
        return calculateSomething();
    });
    // 稍后获取结果
    Integer result = future.get();
    
  3. 避免直接继承Thread

    // ❌ 不要这样(除非有特殊需求)
    class MyThread extends Thread { }
    
    // ✅ 应该这样
    class MyTask implements Runnable { }
    
  4. 线程池是标配

    // 总是使用线程池,而不是直接new Thread()
    ExecutorService executor = Executors.newFixedThreadPool(10);
    executor.execute(() -> doTask());  // 对Runnable
    Future<?> future = executor.submit(() -> doTask());  // 对Callable
    

记住:Runnable是默认选择,Callable是高级选择,Thread是最后的选择。

最佳实践总结

✅ 必须遵守的:

  1. 使用线程池,不要直接new Thread()
  2. 处理异常,线程中的异常不会传播到主线程
  3. 释放资源,锁、数据库连接等必须释放
  4. 避免死锁,按固定顺序获取锁

✅ 推荐做法:

  1. 优先使用并发集合而不是同步包装
  2. 使用原子类替代synchronized的简单计数器
  3. 使用CompletableFuture进行异步编程
  4. 监控线程状态,及时发现问题

❌ 绝对禁止:

  1. 不要使用stop()方法强制停止线程
  2. 不要忽略InterruptedException
  3. 不要过度同步(缩小同步范围)
  4. 不要依赖线程优先级

记住:多线程是一把双刃剑,用好了提升性能,用错了制造灾难。理解原理,谨慎使用!