Java 多线程与高并发
多线程核心认知
- 程序:静态代码文件
- 进程:运行中的程序,拥有独立内存空间
- 线程:进程内的执行单元,共享堆、方法区,独有程序计数器、虚拟机栈、本地方法栈
- 多线程目的:提高CPU利用率、提高响应速度、异步处理任务
- 多线程风险:线程安全、死锁、上下文切换开销、并发问题
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
- NEW:新建未 start
- RUNNABLE:可运行(包含正在运行+等待CPU)
- BLOCKED:等待synchronized锁
- WAITING:无限等待(wait/join/park)
- TIMED_WAITING:限时等待(sleep(timeout))
- 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. 线程安全问题核心原因
- 多线程并发
- 共享数据
- 多条指令操作共享数据(原子性问题)
解决思路:同步、加锁、原子类、ThreadLocal、不可变对象
5. Synchronized 重量级详解
5.1 作用
- 可重入、独占、非公平锁
- 保证:原子性、可见性、有序性
5.2 三种使用位置
- 修饰实例方法:锁当前对象 this
- 修饰静态方法:锁当前类 Class对象
- 修饰代码块:锁自定义对象(推荐,粒度更细)
5.3 底层原理
- 基于对象MarkWord + Monitor(管程)
- JDK1.6 后引入锁升级机制
5.4 锁升级流程(面试必考)
偏向锁 → 轻量级锁(自旋锁) → 重量级锁
- 偏向锁:默认开启,只有一个线程用,无竞争
- 轻量级锁:竞争少,CAS自旋,不阻塞
- 重量级锁:竞争激烈,内核态阻塞,效率低
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 关键字(轻量级同步)
作用
- 保证可见性:一个线程改,其他线程立即可见
- 禁止指令重排序(有序性)
- 不保证原子性
使用场景
- 状态标志位:
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 执行流程
- 任务来了 → 核心线程未满 → 创建核心线程
- 核心满了 → 进入阻塞队列
- 队列满了 → 创建非核心线程
- 总线程达到max → 执行拒绝策略
10.4 常用阻塞队列
ArrayBlockingQueue有界LinkedBlockingQueue无界(慎用,OOM)SynchronousQueue不存储,直接提交
10.5 拒绝策略
AbortPolicy:抛异常(默认)CallerRunsPolicy:调用者执行DiscardPolicy:丢弃DiscardOldestPolicy:丢弃最老
10.6 Executors 工具类(企业禁用)
newFixedThreadPool:队列无界 → OOMnewCachedThreadPool:最大线程无限 → 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 无锁,保证原子性
AtomicInteger、AtomicBoolean、LongAdder
11.5 CAS 原理
- Compare And Swap
- 底层 Unsafe 类 CPU 原语
- 缺点:ABA 问题 → 用
AtomicStampedReference解决
12. 死锁(Deadlock)
产生 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读多写少CopyOnWriteArraySetConcurrentHashMap高性能并发MapConcurrentSkipListMap有序并发Map
15. 多线程高频面试题(100%全覆盖)
- 线程创建方式?Callable vs Runnable?
- start() 和 run() 区别?
- sleep() 和 wait() 区别?
- synchronized 三种用法?锁升级?
- volatile 作用?能否保证原子性?
- ThreadLocal 原理与内存泄漏?
- 线程池参数、执行流程、为什么禁用Executors?
- CAS 原理、ABA 问题、解决方案?
- 死锁条件与避免方案?
- CountDownLatch vs CyclicBarrier?
- ReentrantLock vs synchronized?
- 读写锁原理与适用场景?
- 线程间通信方式?
- 为什么HashMap线程不安全?
- ConcurrentHashMap 1.7 vs 1.8 原理?