Java 多线程

Java 从设计之初就把"并发"作为一等公民——Thread 类、synchronizedjava.util.concurrent 包,让你能轻松写出多线程程序。这是 Java 区别于其他语言的一大杀手锏。本章带你掌握多线程的核心。

1. 进程与线程

先搞清两个概念:

多线程的好处:同时做多件事(下载视频时也能聊天)、充分利用多核 CPU(并行计算)、UI 不卡顿(后台任务跑在子线程)。

2. 创建线程的两种方式

Java 创建线程有三种方式,推荐用 Runnable / Lambda(灵活,可继承其他类):

// 方式一:继承 Thread 类
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            // Thread.currentThread().getName() 获取当前线程名
            System.out.println(getName() + " 运行: " + i);
            try {
                Thread.sleep(500);     // 睡眠 500ms
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

// 方式二:实现 Runnable 接口(推荐,可继承其他类)
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " 跑: " + i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        // 启动方式一
        MyThread t1 = new MyThread();
        t1.setName("T1");
        t1.start();                  // ⚠️ 必须调 start(),不是 run()!

        // 启动方式二
        Thread t2 = new Thread(new MyRunnable(), "T2");
        t2.start();

        // 方式三:lambda(最简洁,Runnable 是函数式接口)
        Thread t3 = new Thread(() -> System.out.println("lambda 线程在跑"), "T3");
        t3.start();

        // main 线程等待 t1 跑完
        t1.join();
        System.out.println("main 结束");
    }
}

关键点:

3. 线程的生命周期

线程有 6 种状态(Thread.State 枚举):

4. synchronized:加锁保证原子性

多线程共享数据时,同步是核心难题。两个线程同时执行 count++ 可能丢失更新——因为 ++ 不是原子操作(读取+加1+写回三步,中间可能被打断)。synchronized 保证同一时刻只有一个线程能执行被锁的代码:

class Counter {
    private int count = 0;

    // ❌ 非线程安全:多个线程同时 ++ 会丢失更新
    // public void increment() { count++; }

    // ✅ 方法加 synchronized,同一时刻只有一个线程能进
    public synchronized void increment() {
        count++;
    }

    // ✅ 或只锁关键代码块(性能更好)
    public void incrementBlock() {
        synchronized (this) {
            count++;
        }
    }

    public int getCount() { return count; }
}

public class SyncDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        // 启动 1000 个线程,每个 +1000 次
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < 1000; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }

        // 等所有线程结束
        for (Thread t : threads) t.join();

        // 期望 1000000,无锁情况下会小于这个值(丢失更新)
        System.out.println("最终: " + counter.getCount());
    }
}

synchronized 的两种用法:

5. volatile:保证可见性

volatile 解决的是可见性问题——一个线程改了变量,其他线程立即可见(避免读缓存)。但它不保证原子性,volatile int i; i++ 依然线程不安全:

class Flag {
    // volatile:保证可见性(一个线程改了,其他线程立即可见)
    // 但不保证原子性(对 ++ 操作无效)
    public volatile boolean running = true;
}

public class VolatileDemo {
    public static void main(String[] args) throws InterruptedException {
        Flag flag = new Flag();

        Thread worker = new Thread(() -> {
            int count = 0;
            // 不加 volatile,worker 可能一直读缓存,看不到 main 改的值
            while (flag.running) {
                count++;
            }
            System.out.println("worker 停了, 跑了 " + count + " 次");
        });
        worker.start();

        Thread.sleep(100);          // 让 worker 跑一会
        flag.running = false;       // 通知 worker 停
        worker.join();
    }
}

volatile 的两个作用:

6. 线程池:生产环境的标配

直接 new Thread() 在生产环境几乎看不到——线程池才是标配。它复用线程、控制并发数、管理任务队列:

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) throws Exception {
        // ❌ 直接 new Thread 的缺点:无法控制并发数、创建销毁开销大
        // ✅ 推荐用线程池:复用线程、控制并发数、管理任务队列

        // 1. Executors 工厂方法(简单但生产环境慎用,固定参数更安全)
        // ExecutorService pool = Executors.newFixedThreadPool(4);

        // 2. ThreadPoolExecutor 显式配置(推荐)
        ExecutorService pool = new ThreadPoolExecutor(
            4,                          // 核心线程数
            8,                          // 最大线程数
            60L, TimeUnit.SECONDS,      // 空闲存活时间
            new LinkedBlockingQueue<>(100),  // 任务队列
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy()   // 拒绝策略
        );

        // 提交 Runnable 任务(无返回值)
        for (int i = 0; i < 5; i++) {
            final int n = i;
            pool.submit(() -> {
                System.out.println("任务 " + n + " 在 " + Thread.currentThread().getName());
            });
        }

        // 提交 Callable 任务(有返回值)
        Future<Integer> future = pool.submit(() -> {
            Thread.sleep(500);
            return 42;
        });
        Integer result = future.get();       // 阻塞等待结果
        System.out.println("Callable 结果: " + result);

        // ⚠️ 关闭线程池!否则 JVM 不退出
        pool.shutdown();                     // 不接受新任务,已提交的跑完
        // pool.shutdownNow();               // 尝试中断所有正在执行的任务
        pool.awaitTermination(60, TimeUnit.SECONDS);
    }
}

线程池的关键参数:

《阿里巴巴 Java 开发手册》建议:不要用 Executors.newFixedThreadPool 等工厂方法(可能 OOM),应该用 ThreadPoolExecutor 显式指定参数。

7. wait / notify:线程间协作

经典场景生产者-消费者:生产者生产数据放入队列,消费者取数据。队列满时生产者等,队列空时消费者等。wait() 让线程等待,notify() 唤醒:

import java.util.concurrent.*;

// 经典:生产者-消费者
class BoundedBuffer<T> {
    private final Object lock = new Object();
    private final java.util.LinkedList<T> queue = new java.util.LinkedList<>();
    private final int capacity;

    public BoundedBuffer(int capacity) { this.capacity = capacity; }

    public void put(T item) throws InterruptedException {
        synchronized (lock) {
            // ⚠️ 必须用 while 不能 if(防止虚假唤醒)
            while (queue.size() == capacity) {
                lock.wait();         // 队列满,等待消费者消费
            }
            queue.add(item);
            lock.notifyAll();        // 通知消费者
        }
    }

    public T take() throws InterruptedException {
        synchronized (lock) {
            while (queue.isEmpty()) {
                lock.wait();         // 队列空,等待生产者生产
            }
            T item = queue.removeFirst();
            lock.notifyAll();        // 通知生产者
            return item;
        }
    }
}

// 现代替代方案:用 BlockingQueue,无需手写 wait/notify
// BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// queue.put("item");   // 满了自动阻塞
// queue.take();        // 空了自动阻塞

几个易错点:

8. java.util.concurrent:并发工具箱

Java 5 引入的 j.u.c 包提供了一堆高级并发工具,新项目应该优先用,不要手写 synchronized:

9. Java 21 虚拟线程(预览)

Java 21 LTS 正式 GA 了虚拟线程(Virtual Thread)——这是 Java 并发的革命性升级。它让你可以开几百万个线程而不耗内存:

小结

这一章你掌握了 Java 多线程的核心:Thread/Runnable、生命周期、synchronized、volatile、线程池、wait/notify、j.u.c 工具箱、虚拟线程。多线程是 Java 的难点也是优势——理解了它,你才能写出高效、稳定的并发程序。

恭喜!到这里你已经学完了整个 Java 入门系列 18 篇。从最简单的 Hello World,到面向对象、集合、异常、IO、多线程,你已经具备了独立写 Java 程序的能力。下一步建议:学 Spring Boot(后端开发)、Maven / Gradle(依赖管理)、MySQL + MyBatis(数据库)、JUnit(测试),进入实战!

← 上一篇 Java 输入输出

返回 Java 教程目录

✈️💬