Java基础全套教程(九)—— 多线程和并发编程详解

在Java企业级开发中,单线程程序仅能串行执行任务,无法充分利用服务器CPU多核性能,面对高并发、多任务、异步处理场景(如接口批量请求、日志异步打印、订单异步处理、定时任务调度、文件批量解析)会出现性能瓶颈、响应超时等问题。

多线程与并发编程是Java高阶核心、面试必考重难点,也是后端开发进阶的必备技能。本章节将从零深度拆解多线程全套体系,包含核心概念、线程生命周期、线程创建方式、线程同步、锁机制、死锁原理与解决、线程通信、生产者消费者模式,全程搭配全新实战案例、底层原理剖析、企业避坑规范,贴合真实业务开发场景。

本章学习目标

  1. 彻底吃透程序、进程、线程的核心区别与关联,厘清并发、并行、串行的底层差异;

  2. 熟练掌握Java线程四大创建方式、核心优劣与适用场景,摒弃老旧写法,使用企业规范代码;

  3. 精通线程五大生命周期状态、状态切换底层机制,掌握线程核心API的使用原理;

  4. 深刻理解线程安全问题成因,熟练运用synchronized锁的三种使用方式与底层原理;

  5. 掌握多线程死锁的产生条件、排查思路与企业级解决方案;

  6. 吃透线程通信机制,掌握wait/notify/notifyAll核心方法的使用规范与坑点;

  7. 精通生产者消费者模式底层逻辑,理解线程协作核心思想,适配异步业务场景;

  8. 建立多线程开发规范,规避线程不安全、锁冗余、死锁、线程资源泄露等高频问题。


9.1 多线程核心基础概念

想要精通并发编程,首先必须厘清程序、进程、线程三者的层级关系,同时区分串行、并发、并行三大执行模式,这是所有多线程编程的基础,也是面试高频基础考点。

9.1.1 程序、进程、线程详解

1. 程序(Program)

程序是静态的代码集合,是一组有序的指令和数据的封装,对应磁盘上的可执行文件(如.exe、jar包),长期存储在磁盘中,不占用内存资源,不具备动态执行能力。简单来说,我们编写的Java代码、编译后的class文件、可运行jar包,都属于程序范畴。

2. 进程(Process)

进程是程序的一次动态执行过程,是操作系统分配系统资源(内存、CPU、文件句柄)的最小单位。程序启动运行后,就会转化为一个独立的进程,拥有独立的内存空间,进程之间相互隔离、互不干扰。

现代操作系统均支持多进程并发执行,例如一台电脑可以同时运行浏览器、编译器、微信、音乐播放器等多个程序,每个运行中的程序都是一个独立进程,操作系统通过资源调度实现多进程共存。

3. 线程(Thread)

线程是进程内部的最小执行单元,是操作系统CPU调度的基本单位,被包含在进程之中。一个进程默认包含一个主线程,同时可以创建多个子线程,同一进程下的所有线程共享进程的堆内存、方法区、系统资源,仅拥有独立的栈内存。

多线程的核心价值:将一个进程的复杂任务拆分多个子任务,由多个线程并行执行,充分利用CPU多核资源,大幅提升程序执行效率。

9.1.2 进程与线程核心区别(面试必背)

为了清晰区分二者,结合企业面试考点,总结核心差异如下:

  1. 资源归属不同:进程是资源分配最小单位,拥有独立内存;线程是调度执行最小单位,共享进程资源;

  2. 隔离性不同:进程之间完全隔离,互不影响;同一进程的线程共享资源,相互影响;

  3. 开销不同:进程创建、销毁、切换开销极大;线程开销极小,轻量化执行;

  4. 通信方式不同:进程间通信复杂(管道、套接字);线程间通信简单(共享变量、wait/notify);

  5. 生命周期不同:进程消亡则内部所有线程全部消亡;单个线程异常结束,不会影响主进程和其他线程。

9.1.3 串行、并发、并行核心区别

多线程编程的核心就是实现任务的高效执行,三种执行模式是理解并发的关键,三者无重叠、无混淆:

1. 串行(Serial)

所有任务按顺序依次执行,同一时间只有一个任务在运行,上一个任务执行完毕后,下一个任务才会开始,执行效率最低,单线程程序默认串行执行。

2. 并发(Concurrency)

单个CPU核心通过时间片轮询机制,快速交替执行多个线程任务。由于CPU切换速度极快,宏观上看似多个任务同时执行,微观上仍是串行执行。适用于任务数大于CPU核心数的场景,是Java多线程最常用的执行模式。

3. 并行(Parallelism)

多个CPU核心同时执行多个任务,真正意义上的同时运行,无任务切换开销,执行效率最高。仅适用于任务数小于等于CPU核心数的场景。


9.2 Java线程核心基础

9.2.1 主线程与子线程

1. 主线程(Main Thread)

Java程序启动时,JVM会自动创建一条主线程,专门执行main()方法代码,是程序的入口线程,也是所有子线程的父线程。

主线程核心特性:是程序最先执行的线程;不会等待子线程执行完毕,主线程结束后,程序不会立即终止,需等待所有非守护子线程执行完成;负责初始化程序环境、调度子线程任务。

2. 子线程

开发者在主线程中手动创建、启动的线程统称为子线程,用于分担主线程任务,实现多任务并行处理,提升程序运行效率。

9.2.2 线程核心常用API

Thread类是java.lang包下的核心线程类,提供了大量线程操作通用方法,是多线程编程的基础,核心API如下:

  1. start():启动线程,将线程置入就绪状态,由JVM调度执行run()方法,仅可调用一次;

  2. run():线程的核心执行体,线程调度成功后执行该方法内的业务代码;

3.Thread.sleep(long millis):静态方法,让当前线程休眠指定毫秒,进入阻塞状态,不释放锁资源;

  1. currentThread():静态方法,获取当前正在执行的线程对象;

  2. getName()/setName():获取/设置线程名称,方便日志排查与线程定位;

  3. isAlive():判断线程是否处于活跃状态(就绪、运行、阻塞均为活跃);

  4. yield():静态方法,让出当前CPU时间片,线程重回就绪状态,重新参与CPU调度;

  5. join():等待目标线程执行完毕,当前线程再继续执行,实现线程顺序执行。


9.3 Java线程四大创建方式(企业级精讲)

Java提供四种线程创建方式,不同方式适配不同业务场景,其中前两种为基础方式,后两种为企业开发首选,本节提供全新实战案例,规避冗余代码,贴合开发规范。

9.3.1 方式一:继承Thread类

自定义线程类继承Thread类,重写run()方法,封装线程执行逻辑,通过实例化对象调用start()启动线程。

核心优劣:写法简单,但是存在单继承局限性,自定义类无法再继承其他业务父类,企业开发极少使用。

/**
 * 继承Thread类实现多线程
 * 场景:简单独立任务、无父类继承需求
 */
public class ThreadExtendDemo extends Thread {
    // 重写线程执行体
    @Override
    public void run() {
        // 获取当前线程名称,执行自定义任务
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 执行任务,次数:" + i);
            try {
                // 线程休眠500ms,模拟业务耗时
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // 创建两个独立线程
        ThreadExtendDemo task1 = new ThreadExtendDemo();
        ThreadExtendDemo task2 = new ThreadExtendDemo();
        // 设置线程名称,便于排查
        task1.setName("任务线程-1");
        task2.setName("任务线程-2");
        // 启动线程
        task1.start();
        task2.start();
    }
}

9.3.2 方式二:实现Runnable接口

自定义类实现Runnable接口,重写run()方法,将任务逻辑与线程对象解耦,突破单继承限制,是基础开发通用方式。

核心优势:支持多实现、无继承限制、任务可复用、多个线程可共享同一个任务对象。

/**
 * 实现Runnable接口创建线程
 * 场景:通用业务任务、需要保留类继承能力的场景
 */
public class RunnableImplDemo implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 正在执行业务逻辑,次数:" + i);
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // 单个任务对象,被多个线程共享
        RunnableImplDemo businessTask = new RunnableImplDemo();
        new Thread(businessTask, "业务线程-A").start();
        new Thread(businessTask, "业务线程-B").start();
    }
}

9.3.3 方式三:Callable+FutureTask(有返回值线程)

前两种方式线程执行后无返回值、无法抛出异常,Callable接口是高阶线程创建方式,支持线程任务返回结果、捕获执行异常,适配需要获取任务执行结果的业务场景。

核心优势:有返回值、可抛出异常、支持结果获取、可取消任务,企业异步查询、批量计算场景首选。

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

/**
 * Callable有返回值线程创建
 * 场景:异步计算、接口异步查询、需要任务返回结果的场景
 */
public class CallableTaskDemo implements Callable<Integer> {
    // 线程任务,支持返回值和异常抛出
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + " 累加计算:" + i);
            Thread.sleep(300);
        }
        return sum;
    }

    public static void main(String[] args) {
        // 封装Callable任务
        FutureTask<Integer> futureTask = new FutureTask<>(new CallableTaskDemo());
        // 启动线程
        new Thread(futureTask, "计算线程").start();

        try {
            // 获取线程执行返回结果,会阻塞主线程,直到任务执行完毕
            Integer result = futureTask.get();
            System.out.println("线程计算最终结果:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

9.3.4 方式四:线程池创建(企业终极首选)

手动创建、销毁线程开销极大,频繁创建线程会造成资源浪费、程序卡顿。线程池可复用线程、控制并发数量、统一管理线程生命周期,是企业生产环境唯一推荐使用的线程创建方式

核心优势:线程复用、减少资源开销、控制并发量、避免线程泛滥、任务统一调度管理。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池创建线程(企业规范写法)
 * 场景:所有生产环境多任务场景、批量任务、异步任务
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建固定容量线程池,核心线程数3
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        // 批量提交10个任务,线程池自动调度执行
        for (int i = 1; i <= 10; i++) {
            int taskNum = i;
            threadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行第" + taskNum + "号任务");
                try {
                    Thread.sleep(400);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池
        threadPool.shutdown();
    }
}

9.3.5 四种创建方式核心选型总结

  1. 继承Thread类:仅用于测试、简单demo,生产环境禁用;

  2. 实现Runnable接口:适用于无返回值、简单异步任务,少量临时任务使用;

  3. Callable+FutureTask:适用于需要获取任务返回结果、捕获执行异常的异步任务;

  4. 线程池:生产环境所有多线程场景首选,统一规范、性能最优。


9.4 线程生命周期与状态切换

线程从创建到销毁的完整过程,称为线程生命周期。Java线程在运行过程中会经历五大核心状态,状态的切换是多线程调度、线程安全、死锁问题的底层核心,是面试高频重难点。

9.4.1 线程五大状态详解

1. 新生状态(New)

通过new关键字创建线程对象后,线程进入新生状态。此时仅完成对象初始化,分配栈内存,未调用start()方法,无CPU执行权限,属于纯静态状态。

2. 就绪状态(Runnable)

调用start()方法后,线程进入就绪状态。该状态下线程具备所有执行条件,等待JVM线程调度器分配CPU时间片,是进入运行状态的前置状态。

除了新建线程启动,阻塞解除、yield()方法执行、CPU时间片切换后,线程都会重回就绪状态。

3. 运行状态(Running)

就绪状态的线程获取到CPU时间片后,进入运行状态,执行run()/call()方法的业务代码。该状态是线程唯一执行任务的状态,时间片耗尽后会退回就绪状态。

4. 阻塞状态(Blocked)

线程运行过程中,因等待资源、休眠、同步锁、IO阻塞等原因,主动暂停执行,释放CPU时间片,进入阻塞状态。阻塞状态下线程不会参与CPU调度,阻塞条件解除后,自动重回就绪状态。

常见阻塞场景:sleep()休眠、wait()等待、同步锁等待、阻塞IO操作。

5. 死亡状态(Terminated)

线程任务执行完毕、异常终止、主动终止后,进入死亡状态,线程生命周期结束,释放所有线程资源,无法再次启动。

9.4.2 线程状态切换核心规则

  1. 新生状态只能单向切换为就绪状态,无法直接运行或阻塞;

  2. 运行状态可切换为就绪、阻塞、死亡三种状态;

  3. 阻塞状态只能单向切换为就绪状态,无法直接运行;

  4. 线程死亡后彻底销毁,不可重复调用start()启动。


9.5 线程安全与同步机制(核心重难点)

9.5.1 线程安全问题成因

多线程场景下,多个线程同时操作同一个共享资源,由于线程抢占式调度、CPU时间片随机切换,会导致数据读写错乱、数据覆盖、数据不一致等问题,即为线程不安全问题。

核心产生条件:多线程环境、共享资源、非原子操作,三者同时满足必然出现线程安全问题。

9.5.2 synchronized同步锁详解

synchronized是Java原生内置的同步锁机制,可保证代码块/方法的原子性、可见性、有序性,彻底解决多线程共享资源安全问题,是企业开发最常用的线程同步方案。

synchronized共有三种使用方式,锁的范围从小到大,适配不同业务场景。

1. 同步代码块(锁对象)

锁定指定对象,仅锁住核心业务代码,锁范围最小、性能最优,企业开发首选。同一对象的同步代码块,同一时间仅允许一个线程执行。

/**
 * 同步代码块(对象锁)解决线程安全问题
 * 场景:仅部分代码操作共享资源,缩小锁范围,提升性能
 */
public class SyncObjectLockDemo {
    // 共享资源:票数
    private static int ticket = 10;
    // 自定义锁对象(唯一锁)
    private final static Object LOCK = new Object();

    public static void main(String[] args) {
        // 三个线程同时抢票
        new Thread(() -> sellTicket(), "窗口1").start();
        new Thread(() -> sellTicket(), "窗口2").start();
        new Thread(() -> sellTicket(), "窗口3").start();
    }

    // 售票业务
    private static void sellTicket() {
        while (true) {
            // 同步代码块:锁定唯一对象
            synchronized (LOCK) {
                if (ticket <= 0) {
                    System.out.println(Thread.currentThread().getName() + ":售票结束,无剩余票数");
                    break;
                }
                // 模拟售票耗时
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName() + " 售票成功,剩余票数:" + ticket);
            }
        }
    }
}

2. 同步实例方法(锁当前对象this)

修饰普通成员方法,默认锁定当前实例对象this,同一对象的所有同步实例方法互斥,适用于对象级别的资源同步场景。

/**
 * 同步实例方法(this对象锁)
 * 场景:实例方法操作对象内共享资源
 */
public class SyncMethodLockDemo {
    private int count = 0;

    // 同步实例方法,锁当前对象this
    public synchronized void increment() {
        count++;
        System.out.println(Thread.currentThread().getName() + " 计数+1,当前值:" + count);
    }

    public static void main(String[] args) {
        SyncMethodLockDemo demo = new SyncMethodLockDemo();
        // 多线程调用同一对象同步方法
        new Thread(demo::increment, "线程-X").start();
        new Thread(demo::increment, "线程-Y").start();
    }
}

3. 同步静态方法(锁类Class对象)

修饰静态方法,锁定当前类的Class对象,全局唯一锁,所有该类的实例对象共享同一把锁,适用于静态共享资源同步场景。

/**
 * 同步静态方法(类锁)
 * 场景:静态共享资源、全局同步场景
 */
public class SyncStaticLockDemo {
    // 静态共享资源
    private static int score = 0;

    // 同步静态方法,锁当前类Class对象
    public static synchronized void addScore() {
        score += 10;
        System.out.println(Thread.currentThread().getName() + " 加分成功,当前总分:" + score);
    }

    public static void main(String[] args) {
        // 多个实例对象,共享类锁
        new Thread(SyncStaticLockDemo::addScore, "学生线程1").start();
        new Thread(SyncStaticLockDemo::addScore, "学生线程2").start();
    }
}

9.5.3 锁核心规则总结

  1. 对象锁:锁当前实例对象,不同实例对象之间锁互不干扰;

  2. 类锁:锁Class对象,全局唯一,所有实例对象共享锁,优先级最高;

  3. 同步代码块性能优于同步方法,企业开发优先缩小锁范围,避免锁冗余;

  4. 线程获取锁后执行代码,执行完毕自动释放锁,其他线程竞争获取锁。


9.6 死锁原理与企业级解决方案

死锁是多线程开发中高频疑难问题,指多个线程互相持有对方需要的锁资源,同时互相等待对方释放锁,导致所有线程永久阻塞、程序卡死,无法继续执行。

9.6.1 死锁四大必要条件(必考)

死锁产生必须同时满足四个条件,缺一不可,解决死锁只需破坏任意一个条件即可:

  1. 互斥条件:锁资源同一时间仅能被一个线程持有;

  2. 请求与保持条件:线程持有当前锁,同时请求获取其他锁资源;

  3. 不可剥夺条件:已持有的锁资源,不会被其他线程强制剥夺;

  4. 循环等待条件:多个线程形成循环锁等待链路。

9.6.2 死锁实战案例演示

/**
 * 死锁案例演示
 * 场景:两个线程互相持有对方所需锁资源,循环等待
 */
public class DeadLockDemo {
    // 定义两个全局锁资源
    private static final String LOCK_A = "资源锁A";
    private static final String LOCK_B = "资源锁B";

    public static void main(String[] args) {
        // 线程1:持有锁A,请求锁B
        new Thread(() -> {
            synchronized (LOCK_A) {
                System.out.println("线程1 获取到锁A,等待锁B");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 循环等待锁B,形成死锁
                synchronized (LOCK_B) {
                    System.out.println("线程1 同时获取锁A、锁B");
                }
            }
        }, "死锁线程-1").start();

        // 线程2:持有锁B,请求锁A
        new Thread(() -> {
            synchronized (LOCK_B) {
                System.out.println("线程2 获取到锁B,等待锁A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 循环等待锁A,形成死锁
                synchronized (LOCK_A) {
                    System.out.println("线程2 同时获取锁A、锁B");
                }
            }
        }, "死锁线程-2").start();
    }
}

9.6.3 死锁企业级解决方案

结合死锁四大条件,生产环境核心解决方案如下,优先级从高到低:

  1. 破坏循环等待条件(首选):统一所有线程的锁获取顺序,所有线程先获取锁A、再获取锁B,杜绝循环等待链路;

  2. 缩小锁范围:禁止在同一个同步代码块中嵌套多把锁,单个代码块只持有一把锁;

  3. 设置锁超时时间:线程等待锁超时后自动释放已持有资源,避免永久阻塞;

  4. 避免锁嵌套:业务代码分层设计,杜绝多层同步锁嵌套调用。

/**
 * 死锁解决方案:统一锁获取顺序
 * 核心:所有线程锁获取顺序一致,破坏循环等待条件
 */
public class DeadLockResolveDemo {
    private static final String LOCK_A = "资源锁A";
    private static final String LOCK_B = "资源锁B";

    public static void main(String[] args) {
        // 线程1:先A后B
        new Thread(() -> {
            synchronized (LOCK_A) {
                System.out.println("线程1 获取锁A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK_B) {
                    System.out.println("线程1 获取锁B,执行完毕");
                }
            }
        }, "线程-1").start();

        // 线程2:统一先A后B,杜绝循环等待
        new Thread(() -> {
            synchronized (LOCK_A) {
                System.out.println("线程2 获取锁A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK_B) {
                    System.out.println("线程2 获取锁B,执行完毕");
                }
            }
        }, "线程-2").start();
    }
}


9.7 线程通信机制(wait/notify)

多线程除了独立执行任务、竞争资源外,还需要线程间协作通信,实现任务联动、顺序执行、资源调度。Java通过Object类提供的wait()、notify()、notifyAll()三大方法实现线程通信。

9.7.1 核心通信方法详解

  1. wait():让当前持有锁的线程释放锁资源,进入无限等待阻塞状态,等待其他线程唤醒;

  2. notify():随机唤醒一个处于当前对象等待池的线程;

  3. notifyAll():唤醒当前对象等待池的所有等待线程;

强制规范:三大方法必须在同步代码块/同步方法中调用,且必须是当前锁对象调用,否则抛出异常。

核心区别:sleep()休眠不释放锁,wait()等待主动释放锁,避免其他线程阻塞。

9.7.2 线程通信核心场景

线程通信核心用于多线程任务协作,解决线程忙等、资源抢占混乱问题,最经典的落地场景就是生产者消费者模式。


9.8 生产者消费者模式(并发核心设计模式)

生产者消费者模式是多线程并发编程的核心设计模式,用于解决生产者线程与消费者线程的任务协作、资源解耦、忙闲不均问题,广泛应用于消息队列、异步处理、日志落地、任务调度等企业场景。

9.8.1 模式核心角色

1.生产者线程:负责生产数据、生成任务,将数据存入缓冲区;

  1. 消费者线程:负责从缓冲区获取数据、处理任务;

  2. 缓冲区:线程共享的中间容器,解耦生产者与消费者,平衡生产消费速度。

9.8.2 企业级实战实现

本次采用任务队列作为缓冲区,通过wait/notify实现线程精准通信,解决生产溢出、消费为空问题,代码简洁规范、贴合生产环境。

import java.util.LinkedList;
import java.util.Queue;

/**
 * 生产者消费者模式实战(企业规范版)
 * 核心:线程通信、资源解耦、平衡生产消费速度
 */
public class ProducerConsumerDemo {
    // 定义任务缓冲区队列,最大容量5
    private static final Queue<String> TASK_QUEUE = new LinkedList<>();
    private static final int MAX_SIZE = 5;
    // 全局锁对象
    private static final Object LOCK = new Object();

    // 生产者线程:生产任务存入队列
    static class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 1; i <= 10; i++) {
                synchronized (LOCK) {
                    // 缓冲区满,生产者等待
                    while (TASK_QUEUE.size() >= MAX_SIZE) {
                        try {
                            LOCK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 生产任务
                    String task = "业务任务-" + i;
                    TASK_QUEUE.add(task);
                    System.out.println("生产者完成生产:" + task + ",当前队列任务数:" + TASK_QUEUE.size());
                    // 唤醒消费者线程
                    LOCK.notifyAll();
                }
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 消费者线程:从队列获取任务消费
    static class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (LOCK) {
                    // 缓冲区为空,消费者等待
                    while (TASK_QUEUE.isEmpty()) {
                        try {
                            LOCK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 消费任务
                    String task = TASK_QUEUE.poll();
                    System.out.println("消费者完成处理:" + task + ",剩余任务数:" + TASK_QUEUE.size());
                    // 唤醒生产者线程
                    LOCK.notifyAll();
                }
                try {
                    Thread.sleep(600);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        // 启动生产者、消费者线程
        new Thread(new Producer(), "生产者线程").start();
        new Thread(new Consumer(), "消费者线程").start();
    }
}

9.8.3 模式核心价值与总结

  1. 解耦线程:生产者与消费者不直接交互,通过缓冲区通信,代码解耦、拓展性更强;

  2. 平衡效率:解决生产快、消费慢或生产慢、消费快的忙闲不均问题,提升整体吞吐量;

  3. 线程协作:通过wait/notify实现精准线程通信,避免空循环、资源空耗;

  4. 高并发适配:是消息队列、线程池、异步框架的底层核心思想,中高级开发必备。


9.9 本章高频坑点与开发规范汇总

1. 线程创建坑点

  • 禁止生产环境手动频繁创建线程,必须使用线程池统一管理;

  • start()方法仅可调用一次,重复调用抛出线程状态异常;

  • 区分run()与start():直接调用run()只是普通方法执行,不会创建新线程。

2. 锁机制坑点

  • 同步锁尽量缩小范围,避免全局大锁导致程序串行、性能降级;

  • 区分对象锁与类锁,不同锁类型作用范围不同,避免锁失效导致线程不安全;

  • 禁止锁嵌套,极易引发死锁问题。

3. 线程通信坑点

  • wait/notify/notifyAll必须在同步代码块中执行,否则抛出非法监视器异常;

  • 必须使用while循环判断等待条件,避免虚假唤醒问题;

  • sleep()不释放锁,wait()释放锁,二者核心区别必须厘清。

4. 死锁避坑规范

  • 统一多锁获取顺序,杜绝循环等待;

  • 单个代码块只持有一把锁,禁止多层锁嵌套;

  • 复杂并发场景优先使用成熟工具类,避免手写锁逻辑。


9.10 本章核心知识点终极总结

  1. 程序是静态代码,进程是资源分配最小单位,线程是CPU调度最小单位,同一进程线程共享资源、相互协作;

  2. 并发是CPU时间片交替执行,并行是多核同时执行,串行是顺序执行,多线程核心提升并发处理能力;

  3. 线程四种创建方式各有优劣,线程池是生产环境唯一规范写法,支持线程复用、资源管控;

  4. 线程包含新生、就绪、运行、阻塞、死亡五大状态,状态切换是线程调度的底层核心;

  5. synchronized通过对象锁、类锁实现线程同步,解决多线程共享资源安全问题,优先使用小范围同步代码块;

  6. 死锁需满足四大条件,生产环境通过统一锁顺序、杜绝锁嵌套彻底规避;

  7. wait/notify实现线程通信,生产者消费者模式是线程协作的核心落地场景,是高并发异步编程的基础;

  8. 多线程开发核心规范:慎用手动线程、缩小锁范围、规避死锁、合理使用线程通信,兼顾性能与线程安全。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐