ToolkitX
知识库工具箱

多线程

Thread, Runnable, 线程池, synchronized

30min·高级

01. 线程的创建方式

创建线程有三种方式:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口(有返回值)。推荐用 Runnable 方式,因为 Java 单继承,Thread 方式会浪费唯一一次继承机会。Callable 可以返回结果和抛异常,配合 Future 使用。
java
// 方式1: 实现 Runnable(推荐)
Thread t1 = new Thread(() -> {
    System.out.println("线程运行中");
});
t1.start();

// 方式2: Callable + Future
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "结果";
});
String result = future.get(); // 阻塞等待结果

02. 线程的生命周期

线程有六个状态:NEW(创建了但没 start)、RUNNABLE(运行中或就绪)、BLOCKED(等锁)、WAITING(无限等待如 wait)、TIMED_WAITING(限时等待如 sleep)、TERMINATED(执行完毕)。start 从 NEW 到 RUNNABLE,sleep 进入 TIMED_WAITING,synchronized 拿不到锁进入 BLOCKED。
java
Thread t = new Thread(() -> {
    System.out.println("状态: " + Thread.currentThread().getState());
});
System.out.println(t.getState()); // NEW
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // TERMINATED

t.join();  // 等待线程执行完毕
Thread.sleep(1000); // 当前线程睡1秒
join 方法让当前线程等待调用 join 的那个线程执行完毕。

03. synchronized 同步锁

多个线程同时改同一个数据会出问题,synchronized 保证同一时刻只有一个线程执行同步代码块。可以修饰方法(锁的是 this 对象)或代码块(锁指定对象)。静态方法上的 synchronized 锁的是 Class 对象。锁的范围越小越好,只锁关键代码,不要锁整个方法。
java
public class Counter {
    private int count = 0;
    
    public synchronized void increment() { count++; }
    
    // 等价于
    public void increment2() {
        synchronized (this) {
            count++;
        }
    }
    
    // 静态方法锁 Class 对象
    public static synchronized void staticMethod() { }
}
synchronized 是可重入锁,同一个线程可以多次获取同一把锁不会死锁。

04. 线程池 ExecutorService

频繁创建销毁线程开销大,线程池就是预先创建一批线程等着干活,用完不销毁放回池子。Executors 工具类可以快速创建各种线程池:newFixedThreadPool 定长线程池、newCachedThreadPool 无限扩大的缓存池、newScheduledThreadPool 定时任务池。生产环境不推荐用 Executors,用 ThreadPoolExecutor 手动调参。
java
ExecutorService pool = Executors.newFixedThreadPool(4);

// 提交任务
pool.execute(() -> System.out.println("无返回值的任务"));
Future<Integer> future = pool.submit(() -> { return 42; });

// 关闭线程池
pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);

// 推荐的生产环境写法
new ThreadPoolExecutor(4, 8, 60L, TimeUnit.SECONDS, 
    new LinkedBlockingQueue<>(100));
生产环境不要用无界队列,请求多时会 OOM。newFixedThreadPool 用 LinkedBlockingQueue 无界队列,很危险。

05. volatile 和原子类

volatile 保证多线程间变量的可见性,一个线程改了值另一个线程马上能看到,但不保证原子性。count++ 这种非原子操作 volatile 也救不了。如果想用无锁的方案做计数,用 AtomicInteger 之类的原子类,底层 CAS 实现,性能比 synchronized 好很多。
java
// volatile 只保证可见性
volatile boolean running = true;

// 原子类保证线程安全
AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet();  // 原子的 count++
int value = atomicCount.get();

// CAS 原理简述
atomicCount.compareAndSet(5, 10); // 如果当前值=5,改为10
简单计数场景用 AtomicInteger,不需要 synchronized,性能好很多。

知识测验

1/4正确 0

start() 和 run() 的区别?

下一节

数据库操作

下一节