多线程编程笔记
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 1 条评论

多线程编程笔记

ASN__
2026-04-03 / 0 评论 / 8 阅读 / 正在检测是否收录...

Java 多线程编程笔记

一、多线程概述

1.1 什么是多线程

  • Multi‑threading:一个程序内同时运行多个线程,并发执行不同任务。
  • 线程:CPU 调度的最小单位。比进程更轻量(创建/切换开销小)。

1.2 多线程的优势

  • 提高持续响应性(UI 不卡顿)
  • 充分利用多核 CPU
  • 改善用户体验
  • 提高系统吞吐量

1.3 多线程面临的问题

问题说明
线程安全数据竞争、共享资源并发访问、原子性被破坏
死锁线程相互等待资源,程序永久阻塞
内存一致性缓存、指令重排、可见性问题
调试困难并发 Bug 不可复现、时序依赖、执行路径复杂

二、Java 多线程模型

  • JVM 线程模型:线程映射(通常 1:1 映射到 OS 线程)
  • 线程调度:由操作系统调度器决定(抢占式 + 时间片轮转)
  • Java 内存模型(JMM):定义变量的可见性、有序性、原子性规则

三、线程创建方式(4 种)

方式实现特点
继承 Thread 类class MyThread extends Thread { run() }简单,但受单继承限制
实现 Runnableclass MyRunnable implements Runnable { run() }避免单继承,可共享任务实例
实现 Callableclass MyCallable implements Callable<T> { call() }有返回值、可抛异常
线程池Executors.newFixedThreadPool(n)生产首选,资源复用

示例(Lambda 简化):

new Thread(() -> System.out.println("run")).start();

3.1 Callable + Future 工作流程(“点菜模型”)

  1. 你点菜 → 提交 Callable 任务
  2. 厨师做菜 → 线程池异步执行
  3. 服务员给你叫号器 → 返回 Future 对象
  4. 你玩手机 → 主线程不阻塞
  5. 想吃饭了,去取餐 → 调用 future.get()
  6. 没做好就等(阻塞),做好了直接拿走
Future<Integer> future = executor.submit(() -> 100);
Integer result = future.get();   // 阻塞直到结果返回

四、线程基础操作 & 常用方法

4.1 核心方法清单

方法作用状态变化
start()启动线程NEW → RUNNABLE
Thread.sleep(millis)休眠RUNNABLE → TIMED_WAITING
join()等待该线程结束RUNNABLE → WAITING
join(timeout)等待超时RUNNABLE → TIMED_WAITING
yield()让出 CPU(仍在就绪队列)RUNNABLE(不改变状态)
Thread.currentThread()获取当前线程-
setName() / getName()设置/获取线程名-
setDaemon(true)设为守护线程必须在 start() 前调用

4.2 线程属性

  • 优先级setPriority(1~10),仅建议,不保证顺序。
  • 守护线程:JVM 退出时自动终止(如 GC 线程)。

4.3 异常处理(重点)

  • 普通 try-catch 抓不住子线程里的异常。
  • 使用 UncaughtExceptionHandler
Thread thread = new Thread(() -> { throw new RuntimeException("崩了"); });
thread.setUncaughtExceptionHandler((t, e) -> {
    System.out.println("线程 " + t.getName() + " 异常:" + e.getMessage());
});
thread.start();

五、线程生命周期(完整 6 种状态)

NEW → RUNNABLE → BLOCKED / WAITING / TIMED_WAITING → TERMINATED
状态触发条件
NEWnew Thread(),尚未 start()
RUNNABLEstart() 后,等待 CPU 调度
BLOCKED进入 synchronized 但锁被占用
WAITINGwait()join() 无超时、LockSupport.park()
TIMED_WAITINGsleep(time)wait(time)join(time)
TERMINATEDrun() 执行完毕或异常退出

六、线程调度机制

  • 抢占式调度:高优先级可抢占低优先级(但不保证)。
  • 时间片轮转:同优先级线程轮流获得时间片。
  • 公平调度:JVM 尽量避免线程饥饿。

七、线程监控工具

工具用途
jstack命令行生成线程堆栈快照(定位死锁)
jconsole图形化监控(线程、内存、类加载)
VisualVM功能最强大的可视化工具

八、线程间协作(wait / notify / notifyAll)

  • 必须在 synchronized 块内调用,否则 IllegalMonitorStateException
  • wait():释放锁,进入 WAITING,等待 notify/notifyAll
  • notify():随机唤醒一个等待线程。
  • notifyAll():唤醒所有等待线程。
synchronized (lock) {
    while (条件不满足) lock.wait();
    // 条件满足,执行操作
    lock.notifyAll();
}

九、同步机制详解(从简单到复杂)

9.1 synchronized(内置锁,无脑首选)

  • 自动加锁/解锁,不会忘记释放。
  • 锁某个对象 → 最小粒度,性能最好。
  • 锁当前对象 this → 整个方法串行。
  • 锁 Class 对象 → 全局锁。
// 同步代码块(推荐)
private final Object lock = new Object();
public void deduct() {
    synchronized(lock) { /* 临界区 */ }
}

// 同步方法
public synchronized void add() { count++; }

// 静态同步方法(类锁)
public static synchronized void globalMethod() 

9.2 ReentrantLock(显式锁,更灵活)

  • 需要手动 lock() / unlock()必须放在 finally 中
  • 高级功能:

    • tryLock(timeout):超时放弃,避免死锁。
    • lockInterruptibly():可中断抢锁。
    • new ReentrantLock(true):公平锁(排队不插队)。
    • Condition:精准唤醒指定线程。
private final Lock lock = new ReentrantLock();
public void update() {
    lock.lock();
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}

9.3 volatile(轻量级同步)

  • 只保证:可见性 + 禁止指令重排。
  • 不保证:原子性(不能用于 count++)。
  • 适用场景:布尔状态标记、单例双重检查中的 instance
private volatile boolean stop = false;
public void run() {
    while (!stop) { /* 执行任务 */ }
}
public void stopTask() { stop = true; }

9.4 原子类(无锁,CAS)

  • AtomicIntegerAtomicLongAtomicReference 等。
  • 高并发计数用 LongAdder(分段累加,性能更高)。
private AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();  // 原子+1

9.5 线程安全集合(必记两个)

  • ConcurrentHashMap:分段锁 + CAS,高并发 Map。
  • CopyOnWriteArrayList:读多写少场景,写时复制数组,不阻塞读。
Map<String, Integer> map = new ConcurrentHashMap<>();
List<String> list = new CopyOnWriteArrayList<>();

9.6 ThreadLocal(线程隔离)

  • 每个线程拥有自己的变量副本,不竞争。
  • 必须 remove(),否则内存泄漏(尤其在线程池中)。
private ThreadLocal<String> userLocal = new ThreadLocal<>();
userLocal.set("张三");
String user = userLocal.get();
userLocal.remove();   // 用完必须清

9.7 同步机制选择总结(背诵)

场景推荐方案
简单逻辑同步(库存、判断)synchronized
需要超时、中断、公平锁ReentrantLock
线程开关、状态标记volatile
计数、累加、ID 生成AtomicInteger / LongAdder
多线程操作 Map / ListConcurrentHashMap / CopyOnWriteArrayList
单例中存线程私有数据ThreadLocal

禁忌

  • ❌ 不要用 volatilei++
  • ❌ 不要在单例 Bean 中写普通成员变量(若多线程修改)
  • ❌ 不要用 HashMap / ArrayList 做并发写入
  • ❌ 不要只锁一半逻辑(如只锁修改,不锁判断)

十、死锁

10.1 死锁的四个必要条件(缺一不可)

  1. 互斥:资源只能被一个线程占用。
  2. 请求与保持:拿着自己的资源,还请求别人的资源。
  3. 不可剥夺:资源不能被强行抢走。
  4. 循环等待:线程间形成等待闭环。

10.2 避免死锁的方法

  • 破坏任意一个条件(如规定锁的顺序、使用 tryLock 超时放弃)。

十一、优雅停止线程

  • 废弃stop()suspend()resume()(不安全)。
  • 推荐方式

    • 中断机制thread.interrupt() 设置中断标志,线程内检查 Thread.currentThread().isInterrupted() 自行停止。
    • 标志位volatile boolean running,线程定期检查。
// 标志位方式
public class MyTask implements Runnable {
    private volatile boolean running = true;
    public void stop() { running = false; }
    public void run() {
        while (running) { /* 任务 */ }
        // 清理资源
    }
}

十二、线程间通信(5 种实战方式)

方式说明常用场景
共享变量(volatile / 原子类)一个线程改,另一个读状态标记、计数器
wait()/notify()配合 synchronized生产者‑消费者
Condition(配合 ReentrantLock多路等待,精准唤醒复杂协调
Semaphore控制同时访问的线程数限流、连接池
CountDownLatch / CyclicBarrier线程协调工具主等子、互相等待

12.1 Semaphore 示例

Semaphore semaphore = new Semaphore(3); // 最多3个线程同时访问
semaphore.acquire();   // 获取许可(阻塞)
semaphore.release();   // 释放许可

12.2 CountDownLatch(倒计时门栓)

CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        // 执行任务
        latch.countDown();
    }).start();
}
latch.await();  // 主线程等待3个任务完成

12.3 CyclicBarrier(循环栅栏)

CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("三人齐了,出发"));
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        barrier.await(); // 互相等待
    }).start();
}

十三、线程池(企业开发核心)

13.1 为什么需要线程池

  • 资源复用(避免频繁创建/销毁线程)
  • 响应速度快(任务来了直接执行)
  • 可管理(控制并发数、监控)

13.2 ThreadPoolExecutor 7 大核心参数(必背)

new ThreadPoolExecutor(
    corePoolSize,      // 核心线程数(常驻)
    maximumPoolSize,   // 最大线程数(核心+临时)
    keepAliveTime,     // 临时线程空闲存活时间
    TimeUnit unit,     // 时间单位
    BlockingQueue<Runnable> workQueue,  // 任务队列
    ThreadFactory threadFactory,        // 线程工厂(一般用默认)
    RejectedExecutionHandler handler    // 拒绝策略
);

13.3 执行流程(5 步,必背)

  1. 提交任务 → 核心线程未满 → 新建核心线程执行
  2. 核心线程满 → 任务加入阻塞队列
  3. 队列满 → 新建临时线程执行
  4. 总线程数 = 最大线程数 → 执行拒绝策略
  5. 临时线程空闲超时 → 销毁(回到核心线程数)

13.4 4 种拒绝策略(背)

策略行为比喻
AbortPolicy(默认)RejectedExecutionException直接赶走客人,还吵架
CallerRunsPolicy调用者线程自己执行任务让老板自己服务客人
DiscardPolicy悄悄丢弃任务,不报错悄悄赶走客人,不吭声
DiscardOldestPolicy丢弃队列中最老的任务,然后重提赶走排队最久的,让新人进

13.5 Executors 提供的 4 种常用线程池(开发规范禁止使用,需手动创建

工厂方法特点适用场景
newFixedThreadPool(n)固定线程数,无界队列重负载服务器
newCachedThreadPool()动态线程数,空闲60s回收大量短期异步任务
newSingleThreadExecutor()单线程,顺序执行任务需顺序处理
newScheduledThreadPool(n)支持定时/延迟执行定时任务
规范:不要用 Executors 创建,因为 FixedThreadPool 使用无界队列可能 OOM,CachedThreadPool 可能创建过多线程。必须使用 new ThreadPoolExecutor 自定义

13.6 线程数设置公式(背)

  • CPU 密集型(计算为主):线程数 = CPU 核心数 + 1
  • IO 密集型(网络、磁盘):线程数 = CPU 核心数 × 2 (或更多)

十四、定时任务线程池(ScheduledThreadPoolExecutor)

14.1 三种定时任务类型

方法作用特点
schedule(Runnable, delay, unit)延迟执行一次-
scheduleAtFixedRate(...)固定频率执行不关心上次任务是否完成,可能叠加
scheduleWithFixedDelay(...)固定延迟执行必须等上次完成,再等延迟时间
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
scheduler.scheduleAtFixedRate(() -> System.out.println("ping"), 0, 2, TimeUnit.SECONDS);

14.2 对比 Timer 的优势

  • 多线程,任务互不干扰(Timer 单线程,一个任务异常会影响全部)
  • 支持线程池复用

十五、线程池优雅关闭(餐厅下班逻辑)

  1. 挂出「停止接客」牌子 → shutdown()(不再接收新任务)
  2. 让客人慢慢吃完 → awaitTermination(timeout, unit) 等待现有任务完成
  3. 等了很久还没吃完 → 直接清场 → shutdownNow()(强制中断所有任务)
executor.shutdown();
try {
    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
        executor.shutdownNow();
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
}

十六、性能调优与监控

16.1 关键指标

  • 活跃线程数:< 最大线程数的 80%
  • 队列长度:< 队列容量的 70%
  • 任务完成数:持续增长,增长停滞说明处理异常
  • 拒绝任务数:应为 0,频繁拒绝需优化配置

16.2 监控工具

  • JMX(ThreadPoolExecutor 暴露的 getActiveCount() 等)
  • Micrometer(配合 Prometheus + Grafana)
  • jstack 检查死锁

十七、快速记忆口诀

  • 创建线程四种法:继承、实现、Callable、池。
  • 同步锁的选择:简单用 syn,灵活用 Re,开关用 volatile,计数用原子类。
  • 线程池七参数:核心最大时单队工拒。
  • 关闭优雅三步走:shutdown → 等待 → shutdownNow。
  • 死锁四条件:互斥请求不剥夺,循环等待一把锁。

0

评论

博主关闭了所有页面的评论