十二、java线程

5 阅读6分钟

Java 多线程与高并发


多线程核心认知

  1. 程序:静态代码文件
  2. 进程:运行中的程序,拥有独立内存空间
  3. 线程:进程内的执行单元,共享堆、方法区,独有程序计数器、虚拟机栈、本地方法栈
  4. 多线程目的:提高CPU利用率、提高响应速度、异步处理任务
  5. 多线程风险:线程安全、死锁、上下文切换开销、并发问题

1. 线程创建的 4 种方式(全掌握)

1.1 继承 Thread 类

class MyThread extends Thread {
    public void run() {}
}
new MyThread().start();
  • 缺点:Java单继承,无法再继承其他类

1.2 实现 Runnable 接口(推荐)

class MyRunnable implements Runnable {
    public void run() {}
}
new Thread(new MyRunnable()).start();
  • 优点:避免单继承限制,便于共享任务

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

class MyCallable implements Callable<T> {
    public T call() throws Exception {}
}
FutureTask<T> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
T result = task.get(); // 阻塞获取结果
  • 可以返回值抛异常

1.4 线程池(企业正式环境唯一方式)

ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new MyRunnable());
pool.shutdown();
  • 避免频繁创建销毁线程,可控、安全、高效

2. 线程生命周期(6 种状态,面试必考)

NEW → RUNNABLE → BLOCKED / WAITING / TIMED_WAITING → TERMINATED
  1. NEW:新建未 start
  2. RUNNABLE:可运行(包含正在运行+等待CPU)
  3. BLOCKED:等待synchronized锁
  4. WAITING:无限等待(wait/join/park)
  5. TIMED_WAITING:限时等待(sleep(timeout))
  6. TERMINATED:终止

3. 线程常用方法

3.1 start() vs run()

  • start():启动线程,JVM调用run()
  • run():只是普通方法调用,不开启新线程

3.2 sleep(long ms)

  • 让当前线程休眠,不释放锁
  • 进入 TIMED_WAITING

3.3 yield()

  • 让出CPU,回到RUNNABLE
  • 仍可能再次被选中

3.4 join()

  • 插入执行,当前线程等待该线程执行完
  • 主线程等待子线程完成

3.5 interrupt()

  • 中断线程(设置中断标志,不是强制杀死)
  • sleep/wait中被中断会抛InterruptedException

3.6 setDaemon(true)

  • 设置为守护线程
  • 主线程结束,守护线程自动结束
  • GC 就是典型守护线程

4. 线程安全问题核心原因

  1. 多线程并发
  2. 共享数据
  3. 多条指令操作共享数据(原子性问题)

解决思路:同步、加锁、原子类、ThreadLocal、不可变对象


5. Synchronized 重量级详解

5.1 作用

  • 可重入、独占、非公平锁
  • 保证:原子性、可见性、有序性

5.2 三种使用位置

  1. 修饰实例方法:锁当前对象 this
  2. 修饰静态方法:锁当前类 Class对象
  3. 修饰代码块:锁自定义对象(推荐,粒度更细)

5.3 底层原理

  • 基于对象MarkWord + Monitor(管程)
  • JDK1.6 后引入锁升级机制

5.4 锁升级流程(面试必考)

偏向锁 → 轻量级锁(自旋锁) → 重量级锁
  1. 偏向锁:默认开启,只有一个线程用,无竞争
  2. 轻量级锁:竞争少,CAS自旋,不阻塞
  3. 重量级锁:竞争激烈,内核态阻塞,效率低

5.5 特点

  • 不可中断
  • 非公平
  • 不支持超时
  • 简单但不够灵活

6. Lock 接口(JUC锁,更强大)

Lock lock = new ReentrantLock();
lock.lock();
try { } finally { lock.unlock(); }

6.1 ReentrantLock 可重入锁

  • 可重入、可公平/非公平、可中断、可超时
  • 支持 lockInterruptibly()tryLock(timeout)

6.2 ReentrantReadWriteLock 读写锁

  • 读锁共享(并发读)
  • 写锁独占
  • 适合读多写少,极大提升并发

6.3 StampedLock

  • 升级版读写锁,支持乐观读,性能更高

7. volatile 关键字(轻量级同步)

作用

  1. 保证可见性:一个线程改,其他线程立即可见
  2. 禁止指令重排序(有序性)
  3. 不保证原子性

使用场景

  • 状态标志位:volatile boolean flag = true;
  • 单例双重校验锁 DCL 必须加 volatile

企业坑

  • 以为 volatile 能替代锁 → 并发计算错误
  • i++ 这种复合操作 volatile 无效

8. ThreadLocal(线程本地变量)

  • 每个线程独立副本,线程安全
  • 不共享,无竞争
  • 底层:ThreadLocalMap(Entry弱引用)

企业巨坑:内存泄漏

  • key 是弱引用,value 强引用
  • 线程复用(线程池)→ value 一直存在
  • 必须手动 remove()
threadLocal.set(xxx);
try { } finally { threadLocal.remove(); }

9. 线程间通信(等待唤醒机制)

9.1 wait / notify / notifyAll

  • 必须在 synchronized 内使用
  • wait() 释放锁
  • notify() 随机唤醒一个
  • notifyAll() 唤醒全部(推荐,避免死锁)

9.2 Condition(Lock 配套)

Condition cond = lock.newCondition();
cond.await();
cond.signal();
cond.signalAll();
  • 支持多个等待队列,更精准

10. 线程池(企业开发唯一使用方式)

10.1 为什么要用

  • 避免频繁创建销毁线程
  • 控制并发数
  • 统一管理、定时、异步

10.2 ThreadPoolExecutor 7大参数(必考)

new ThreadPoolExecutor(
    corePoolSize,    // 核心线程
    maximumPoolSize, // 最大线程
    keepAliveTime,   // 非核心线程空闲时间
    unit,            // 时间单位
    workQueue,       // 阻塞队列
    threadFactory,   // 线程工厂
    handler          // 拒绝策略
);

10.3 执行流程

  1. 任务来了 → 核心线程未满 → 创建核心线程
  2. 核心满了 → 进入阻塞队列
  3. 队列满了 → 创建非核心线程
  4. 总线程达到max → 执行拒绝策略

10.4 常用阻塞队列

  • ArrayBlockingQueue 有界
  • LinkedBlockingQueue 无界(慎用,OOM)
  • SynchronousQueue 不存储,直接提交

10.5 拒绝策略

  1. AbortPolicy:抛异常(默认)
  2. CallerRunsPolicy:调用者执行
  3. DiscardPolicy:丢弃
  4. DiscardOldestPolicy:丢弃最老

10.6 Executors 工具类(企业禁用)

  • newFixedThreadPool:队列无界 → OOM
  • newCachedThreadPool:最大线程无限 → OOM
  • 必须手动创建 ThreadPoolExecutor

11. JUC 高频工具类

11.1 CountDownLatch 倒计时门栓

  • 主线程等待 N 个线程完成
CountDownLatch cdl = new CountDownLatch(5);
cdl.countDown();
cdl.await();

11.2 CyclicBarrier 循环栅栏

  • 一组线程互相等待,集齐后一起执行

11.3 Semaphore 信号量

  • 控制并发线程数量(限流)
  • acquire() 获取、release() 释放

11.4 AtomicXXX 原子类

  • 基于 CAS 无锁,保证原子性
  • AtomicIntegerAtomicBooleanLongAdder

11.5 CAS 原理

  • Compare And Swap
  • 底层 Unsafe 类 CPU 原语
  • 缺点:ABA 问题 → 用 AtomicStampedReference 解决

12. 死锁(Deadlock)

产生 4 个必要条件

  1. 互斥
  2. 不可抢占
  3. 持有并等待
  4. 循环等待

解决

  • 固定顺序加锁
  • 定时锁 tryLock(timeout)
  • 避免锁嵌套

13. 企业开发高频巨坑(必看)

坑1:用Executors创建线程池导致OOM

LinkedBlockingQueue 无界,任务堆积爆内存

坑2:ThreadLocal 不remove导致内存泄漏/数据错乱

尤其在线程池环境,线程复用,脏数据

坑3:synchronized 锁对象错误

锁 String、锁Integer缓存、锁null对象

坑4:volatile 保证原子性,用于i++

结果一定错误

坑5:notify() 少用,导致线程永久等待

一律用 notifyAll()

坑6:sleep() 释放锁(误解)

sleep 不释放锁,wait 才释放

坑7:多个锁交叉使用,引发死锁

坑8:线程池任务抛异常被吞,不打印日志

必须 try-catch 或用 Future 接收

坑9:SimpleDateFormat 静态共享,线程不安全

用 DateTimeFormatter 或 ThreadLocal

坑10:HashMap 并发扩容成环死循环CPU100%

高并发用 ConcurrentHashMap


14. 高并发安全集合(企业必用)

  • CopyOnWriteArrayList 读多写少
  • CopyOnWriteArraySet
  • ConcurrentHashMap 高性能并发Map
  • ConcurrentSkipListMap 有序并发Map

15. 多线程高频面试题(100%全覆盖)

  1. 线程创建方式?Callable vs Runnable?
  2. start() 和 run() 区别?
  3. sleep() 和 wait() 区别?
  4. synchronized 三种用法?锁升级?
  5. volatile 作用?能否保证原子性?
  6. ThreadLocal 原理与内存泄漏?
  7. 线程池参数、执行流程、为什么禁用Executors?
  8. CAS 原理、ABA 问题、解决方案?
  9. 死锁条件与避免方案?
  10. CountDownLatch vs CyclicBarrier?
  11. ReentrantLock vs synchronized?
  12. 读写锁原理与适用场景?
  13. 线程间通信方式?
  14. 为什么HashMap线程不安全?
  15. ConcurrentHashMap 1.7 vs 1.8 原理?