一、引言

互联网高并发业务需高效利用硬件资源,多线程通过任务拆分、并行执行突破单线程性能限制。Java 原生支持多线程,依托JUC工具包简化并发开发,但并发场景易出现线程不安全、死锁等问题。本文聚焦核心知识点,结合代码示例讲解实战用法与避坑方案。

二、Java多线程核心基础

2.1 线程与进程的区别

进程是操作系统资源分配最小单元,独立占用内存、文件资源,进程间隔离、通信成本高;线程是进程内CPU调度最小单元,轻量级进程,共享进程堆、方法区资源,仅栈内存私有,通信效率高、开销小。多线程核心价值:低开销实现任务并行,最大化利用CPU多核。

2.2 Java线程的核心特性

  • 抢占式执行:操作系统随机分配CPU时间片,线程执行顺序不可控。

  • 资源共享性:线程共享进程公共资源,是并发高效的核心,也是资源竞争的根源。

  • 线程独立性:单线程异常不影响整体进程及其他线程。

  • 并发三大特性:原子性、可见性、有序性,是线程安全的核心保障。

2.3 线程完整生命周期

Java线程共6种状态,由Thread.State定义,状态转换贯穿线程完整生命周期,核心场景及规则如下:

  1. NEW 新建:线程对象初始化完成,未调用start(),未绑定系统线程。

  2. RUNNABLE 可运行:调用start()后,就绪等待CPU或正在执行。

  3. BLOCKED 阻塞:竞争synchronized锁失败,等待获取锁。

  4. WAITING 无限等待:调用wait()、join()等,主动休眠,需手动唤醒。

  5. TIMED_WAITING 限时等待:sleep()、wait(long)等,超时自动唤醒。

  6. TERMINATED 终止:任务执行完毕或异常退出,线程生命周期结束。

核心规则:线程仅能从NEW→RUNNABLE,不可重复启动;终止状态为最终状态,无法恢复。

三、Java多线程四种实现方式

Java多线程有4种主流实现方式,适配不同业务场景,以下附极简可运行代码示例。

3.1 继承Thread类(基础方式)

继承Thread类,重写run()方法,无返回值、简单易用,受单继承限制。

// 自定义线程类
class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行任务
        System.out.println("Thread线程执行:" + Thread.currentThread().getName());
    }
}
// 测试
public class ThreadDemo {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程
    }
}

3.2 实现Runnable接口(常用基础方式)

实现Runnable接口,规避单继承限制,任务与线程解耦,无返回值,通用基础方案。

// 实现Runnable接口
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable线程执行:" + Thread.currentThread().getName());
    }
}
// 测试
public class RunnableDemo {
    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();
    }
}

3.3 实现Callable接口(带返回值)

JDK1.5新增,支持泛型返回值、可抛异常,通过FutureTask获取异步执行结果。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

// 实现Callable接口
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable线程执行,计算1+2");
        return 3; // 带返回值
    }
}
// 测试
public class CallableDemo {
    public static void main(String[] args) throws Exception {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
        new Thread(futureTask).start();
        // 获取线程执行结果
        System.out.println("执行结果:" + futureTask.get());
    }
}

3.4 线程池创建线程(企业级最优方案)

手动创建线程开销大、不可管控,线程池可复用线程、限制并发数、避免OOM,是企业级标准方案。核心类ThreadPoolExecutor,生产环境禁止使用Executors默认线程池。

import java.util.concurrent.*;

// 自定义线程池(企业级推荐)
public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 初始化线程池七大核心参数
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2, // 核心线程数
                5, // 最大线程数
                10, // 空闲线程超时时间
                TimeUnit.SECONDS, // 时间单位
                new ArrayBlockingQueue<>(100), // 任务队列
                Executors.defaultThreadFactory(), // 线程工厂
                new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
        );

        // 提交任务
        for (int i = 0; i < 5; i++) {
            int num = i;
            threadPool.execute(() -> 
                System.out.println("线程池执行任务:" + num)
            );
        }
        threadPool.shutdown(); // 关闭线程池
    }
}

四、Java并发编程三大核心特性

并发线程安全的核心是保障原子性、可见性、有序性,三大特性失效会导致数据错乱、逻辑异常。

4.1 原子性

原子性:操作不可分割,要么全成功、要么全失败。i++等复合操作非原子操作,多线程下数据不安全。

保障方案:synchronized、ReentrantLock、JUC原子类。示例(AtomicInteger 无锁原子操作):

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    // 原子整型,保障自增原子性
    private static final AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        // 10个线程同时自增
        for (int i = 0; i < 10; i++) {
            new Thread(() -> count.incrementAndGet()).start();
        }
        Thread.sleep(1000);
        System.out.println("最终结果:" + count.get()); // 稳定输出10
    }
}

4.2 可见性

可见性:一个线程修改共享变量,其他线程可实时感知。线程私有工作内存与主内存数据不同步,会导致可见性问题。

保障方案:volatile、synchronized、Lock。示例(volatile 保证可见性):

public class VolatileDemo {
    // volatile 强制读写主内存,保证可见性
    private static volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) {}
            System.out.println("线程感知flag变更,退出循环");
        }).start();

        Thread.sleep(500);
        flag = false; // 修改共享变量
    }
}

4.3 有序性

有序性:代码执行顺序与编写顺序一致。JVM、CPU为优化性能会指令重排序,多线程下引发逻辑错乱。

保障方案:volatile(禁止重排序)、锁机制(保证串行执行)。

五、多线程核心问题与实战解决方案

5.1 线程安全问题

成因:多线程并发操作共享资源,三大特性失效导致数据不一致,是最常见线上并发问题。

核心解决方案

  • 悲观锁(写多读少):synchronized、ReentrantLock,串行执行保证安全。

  • 乐观锁(读多写少):CAS无锁机制,JUC原子类实现,性能更高。

  • 资源隔离:ThreadLocal实现线程私有资源,彻底规避竞争。

synchronized 线程安全示例:

public class SyncDemo {
    private static int count = 0;

    // 同步方法,保证原子性、可见性
    public static synchronized void add() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(SyncDemo::add).start();
        }
        Thread.sleep(1000);
        System.out.println("最终结果:" + count); // 稳定输出10
    }
}

5.2 死锁问题

定义:多线程互相持有对方所需锁,循环等待、永久阻塞,导致程序卡死。

四大必要条件:互斥、请求保持、不可剥夺、循环等待(同时满足触发死锁)。

死锁示例(经典双锁嵌套):

public class DeadLockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        // 线程1:先锁lock1,再锁lock2
        new Thread(() -> {
            synchronized (lock1) {
                System.out.println("线程1获取lock1");
                try { Thread.sleep(500); } catch (Exception e) {}
                synchronized (lock2) {
                    System.out.println("线程1获取lock2");
                }
            }
        }).start();

        // 线程2:先锁lock2,再锁lock1
        new Thread(() -> {
            synchronized (lock2) {
                System.out.println("线程2获取lock2");
                try { Thread.sleep(500); } catch (Exception e) {}
                synchronized (lock1) {
                    System.out.println("线程2获取lock1");
                }
            }
        }).start();
    }
}

规避方案:破坏循环等待(统一锁顺序)、一次性申请所有锁、使用tryLock超时锁、杜绝嵌套锁。

5.3 线程活跃性问题

活跃性问题包含死锁、线程饥饿、活锁,均会导致任务无法正常执行:

  • 线程饥饿:线程长期抢不到CPU/锁,一直等待。解决:使用公平锁、统一线程优先级。

  • 活锁:线程持续运行但无有效业务产出,CPU空转。解决:添加随机重试间隔。

更多推荐