Java多线程初阶完整总结|线程、锁、volatile、等待通知、常见案例

一、认识线程

1. 什么是线程

线程就是一条独立执行流 ,多个线程可以同时执行多份代码。 进程是资源分配的最小单位;线程是CPU调度的最小单位

  • 一个进程至少包含1个主线程;
  • 同一个进程内多个线程共享堆、方法区内存;每个线程私有虚拟机栈、本地方法栈、程序计数器;
  • 进程之间内存相互隔离;一个线程崩溃,会导致整个进程崩溃。

为什么用线程:

  1. 充分利用多核CPU算力;
  2. IO等待时可以执行其他任务,提升程序整体效率;
  3. 相比进程,线程创建、销毁、调度开销更小。

2. Java创建线程的4种写法

  1. 继承Thread类 ,重写run(),调用start()启动线程
java 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}
// 使用
MyThread t = new MyThread();
t.start();
  1. 实现Runnable接口,传入Thread构造器
java 复制代码
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("runnable执行");
    }
}
Thread t = new Thread(new MyRunnable());
t.start();
  1. 匿名内部类(Thread)
java 复制代码
Thread t = new Thread(){
    @Override
    public void run() {}
};
  1. Lambda表达式(推荐,代码简洁)
java 复制代码
Thread t = new Thread(() -> {
    System.out.println("lambda线程");
});
t.start();

⚠️注意:run()只是普通方法,start()才会向操作系统申请创建真正的线程。

3. Thread常用属性与方法

API 说明
getId() 线程唯一id
getName() 获取线程名字
getState() 获取线程状态
getPriority() 获取优先级,优先级高只是更容易被调度,不代表一定先执行
isDaemon() 是否后台守护线程,JVM等待所有非后台线程结束才退出
isAlive() 线程是否存活:不是NEW、不是TERMINATED即为存活
interrupt() 中断线程,设置中断标记;如果线程阻塞(sleep/wait)会抛出InterruptedException并清除标记
currentThread() 静态方法,获取当前正在执行的线程对象
sleep(ms) 休眠线程,不会释放锁;实际休眠时间≥设置时间,受操作系统调度影响
join() 等待其他线程执行完毕,支持设置最大等待时间

中断线程两种方式:

  1. 自定义volatile布尔标记位,循环判断标记退出;
  2. interrupt() + isInterrupted() / Thread.interrupted()interrupted():读取中断标记同时清除标记isInterrupted()只读取,不清除标记。

二、线程的6种状态与流转

线程状态是枚举Thread.State,一共6种:

  1. NEW :线程对象创建,还没调用start()
  2. RUNNABLE:就绪/运行中,代表线程可以被CPU调度执行(不一定正在跑CPU)
  3. BLOCKED :等待获取synchronized锁,锁被别的线程占有,阻塞排队
  4. WAITING :无限等待,等待其他线程唤醒(wait()join()park()
  5. TIMED_WAITING :限时等待,超时自动唤醒(sleep()wait(time)
  6. TERMINATED:线程执行完毕

区分:

  • BLOCKED:抢锁失败,等锁;
  • WAITING / TIMED_WAITING:主动放弃CPU,等待其他线程通知或者超时。

三、线程安全

定义:多线程环境下,程序运行结果和单线程预期结果一致,就是线程安全。

线程不安全的4个根源

  1. 线程抢占式随机调度:操作系统随时切换线程,执行顺序不可预测;
  2. 修改共享变量:多个线程同时读写同一个内存变量;
  3. 不具备原子性 :一条Java代码可能对应多条CPU指令,执行中途会被打断。 例如count++分为三步:读内存 → CPU计算 → 写回内存,多线程并发会出现数据丢失;
  4. 内存可见性问题:JMM Java内存模型,线程拥有自己的工作内存(CPU缓存/寄存器),修改先写副本,不一定立刻刷入主内存,其他线程看不到最新值;
  5. 指令重排序:编译器、CPU为性能对指令进行重排,单线程逻辑不变,多线程下会出现异常。

Java内存模型 JMM

  • 主内存:真正物理内存,存放共享变量;
  • 工作内存:每个线程私有,对应CPU寄存器、高速缓存,是变量副本。 线程读写共享变量,都是操作工作内存,再同步主内存,会带来可见性bug。

四、synchronized 监视器锁

synchronized是Java内置的悲观锁,基于操作系统mutex实现。

三大特性

  1. 互斥(原子性):同一时间只允许一个线程进入同步块。进入=加锁,退出=解锁;竞争失败线程进入阻塞队列BLOCKED。
  2. 可重入:同一个线程可以重复获取同一把锁;内部维护锁持有者+计数器,计数器归0才释放锁,避免自己把自己锁死。
  3. 保证可见性、禁止指令重排序

四种写法

  1. 锁普通对象代码块:synchronized(对象){}
  2. 锁当前对象this:synchronized(this){}
  3. 修饰普通方法:锁当前实例对象
java 复制代码
public synchronized void test(){}
  1. 修饰静态方法:锁类对象(.class
java 复制代码
public static synchronized void test(){}

关键点:只有竞争同一对象锁,才会阻塞;不同对象互不干扰

JDK线程安全/不安全类

  • ❌线程不安全:ArrayListHashMapHashSetStringBuilder
  • ✅线程安全:StringBufferVectorHashTable(不推荐)、ConcurrentHashMap String不可变,天生线程安全。

五、volatile关键字

作用:保证变量内存可见性,禁止指令重排序;不保证原子性。

  1. 写volatile变量:修改工作内存副本,强制刷新到主内存;
  2. 读volatile变量:强制从主内存读取最新值,不使用缓存副本。

注意:volatile int count不能解决count++线程不安全,因为++不是原子操作。

使用场景:多线程下一个线程写、多个线程读的标记变量。

六、wait / notify / notifyAll

属于Object的方法,用来做线程之间的协作通信,控制线程执行顺序。

wait()

  1. 当前线程释放锁,进入对象等待队列;
  2. 等待被唤醒、超时、被interrupt;
  3. 必须写在synchronized同步块内部,否则抛异常。

notify()

唤醒该对象等待队列中随机一个线程;不会立刻释放锁,退出同步代码块之后锁才释放。

notifyAll()

唤醒该对象等待队列全部等待线程;唤醒后所有线程需要重新竞争锁。

wait vs sleep对比

wait sleep
归属 Object方法 Thread静态方法
必须在synchronized内调用,调用后释放锁 不需要锁,休眠不释放锁
用途 线程间通信协作 单纯让线程休眠一段时间

最佳实践:wait()要写在while循环里面,防止虚假唤醒。

七、多线程经典案例

案例1:单例模式

  1. 饿汉模式:类加载直接创建实例,天然线程安全
java 复制代码
class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}
  1. 懒汉模式(双重检查锁DCL)
java 复制代码
class Singleton{
    // volatile禁止指令重排序,防止半初始化对象
    private static volatile Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){ //外层判断减少锁竞争
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile作用:阻止new Singleton()指令重排,避免其他线程拿到未初始化完成的对象。

案例2:阻塞队列 & 生产者消费者模型

阻塞队列特性:

  • 队列满时,入队put()阻塞;
  • 队列空时,出队take()阻塞。

生产者‑消费者模型作用:

  1. 解耦:生产者、消费者不直接交互,通过队列通信;
  2. 削峰填谷:缓冲流量,平衡生产和消费速度,防止瞬时流量打垮服务。

标准实现:LinkedBlockingQueueput()入队,take()阻塞取出。

简易实现思路:数组存储元素,synchronized + wait/notifyAllput满就wait;take空就wait。

案例3:定时器Timer

  • JDK提供Timerschedule(task,delay)延迟执行任务。
  • 简易定时器实现:优先队列存放定时任务,后台线程循环扫描队首任务,时间到执行任务。

案例4:线程池

频繁创建销毁线程开销高,线程池复用已有线程。 核心组成:核心线程数、最大线程数、阻塞队列、空闲存活时间、拒绝策略。

Executors工具快速创建线程池:

  • newFixedThreadPool(n)固定线程数
  • newCachedThreadPool()动态扩容
  • newSingleThreadExecutor()单线程池

四种拒绝策略:

  1. AbortPolicy:直接抛异常(默认)
  2. CallerRunsPolicy:调用者线程自己执行任务
  3. DiscardOldestPolicy:丢弃队列最老任务
  4. DiscardPolicy:直接丢弃新任务,不抛异常

八、保证线程安全的整体思路

  1. 不使用共享变量;
  2. 共享变量只读,使用不可变对象;
  3. 修改共享变量时:保证原子性、可见性、有序性
    • 原子性:synchronized、锁、CAS;
    • 可见性:volatile、锁;
    • 有序性:volatile、锁禁止重排序。

结尾小结

多线程核心难点在于理解:抢占调度带来执行顺序不可控,引出原子性、可见性、重排序三类问题

  • synchronized悲观锁,保证原子+可见+有序,会阻塞;
  • volatile只解决可见性与重排序,不能保证原子性;
  • wait‑notify做线程通信;
  • 业务优先使用线程池,不要手动频繁new Thread。
相关推荐
爱喝可乐的中登23 分钟前
SpringBoot 云边协同|智慧地铁 ISCS 改造实战第 10 篇:全网权限与数据隔离重构|线路‑车站‑专业三级 RBAC、边缘数据权限下沉
java·spring boot·重构
H_老邪33 分钟前
JVM 遇到的问题-1.1
jvm
夜雨声烦丿33 分钟前
从需求到页面:日期计算器应用的 ArkTS 原生实现
开发语言·javascript·华为·harmonyos
落魄实习生36 分钟前
Spring AI Alibaba入门-生态集成
java·人工智能·spring
重生之我是Java开发战士43 分钟前
【Java EE】认识Linux与项目部署
java·linux·java-ee
LXMXHJ1 小时前
springboot项目测试
java·spring boot·后端·测试
程序员雪球1 小时前
本地IDEA打断点debug容器
java·开发语言
码匠许师傅1 小时前
【设计模式精讲】2. 设计原则基石:SOLID 与几条关键原则
java·设计模式·log4j
yychen_java1 小时前
二:Multi-Agent 协作架构与 MCP 协议实战:Java 企业级 AI 智能体进阶指南
java·人工智能·架构