一、线程与进程的本质区别
- 进程:操作系统资源分配的基本单位,每个进程有独立的内存空间(如Chrome的每个标签页是一个独立进程)。
- 线程:CPU调度的基本单位,共享进程的资源(如内存、文件句柄)。一个进程至少包含一个线程(主线程),但线程切换开销远小于进程。
关键区别:
- 资源开销:线程共享进程资源,创建和切换成本更低;进程独立分配资源,开销更大。
- 通信方式:线程间通过共享变量直接通信(如wait/notify);进程间需通过管道、消息队列等机制。
- 稳定性:线程崩溃可能导致整个进程崩溃;进程崩溃不影响其他进程。
面试高频题:
- “为什么Java多线程性能优于多进程?”
答案要点:线程共享内存,减少上下文切换开销;适合高并发场景(如Web服务器)。
二、线程池的核心参数与配置策略
ThreadPoolExecutor参数详解:
- corePoolSize:核心线程数,即使空闲也保留。
- maximumPoolSize:最大线程数,当队列满时触发扩容。
- keepAliveTime:非核心线程存活时间,任务密集时可适当调大。
- workQueue:任务队列(如LinkedBlockingQueue),决定任务等待策略。
- RejectedExecutionHandler:拒绝策略(如AbortPolicy抛出异常)。
典型场景与配置建议:
- 短时高并发任务(如秒杀系统):使用CachedThreadPool,动态扩展线程数;设置较小的keepAliveTime(如60秒),避免线程闲置浪费。
- 长时稳定任务(如定时任务):使用ScheduledThreadPool,固定核心线程数;队列选择DelayQueue,实现延迟执行。
面试高频题:
- “为什么线程池要区分核心线程和非核心线程?”
答案要点:核心线程保障基础吞吐量,非核心线程应对突发流量,避免资源浪费。
三、线程同步与锁机制
synchronized vs ReentrantLock:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 可中断 | ❌ | ✅(支持tryLock()) |
| 公平锁 | ❌ | ✅(可配置公平锁) |
| 读写锁 | ❌ | ✅(ReentrantReadWriteLock) |
| 性能 | 低(JDK1.6后优化) | 高(适合复杂锁场景) |
锁优化策略:
- 锁粒度:缩小锁作用范围(如只锁共享资源段),减少竞争。
- 锁分离:读写分离(如ConcurrentHashMap分段锁),提升并发性能。
- 锁消除:JVM自动识别无需同步的代码(如StringBuffer局部对象),优化性能。
面试高频题:
- “如何避免死锁?”
答案要点:按固定顺序获取锁、使用tryLock()设置超时、减少锁嵌套。
四、死锁的四个必要条件与排查方法
死锁的四个必要条件:
- 互斥:资源只能被一个线程占用。
- 请求与保持:线程持有资源并请求其他资源。
- 不可抢占:资源不能被强制剥夺。
- 循环等待:线程间形成资源环。
排查与解决方法:
- 工具检测:jstack:分析线程堆栈,定位死锁线程。VisualVM:可视化监控线程状态。
- 日志分析:记录线程获取/释放锁的顺序,发现循环依赖。
- 代码优化:按固定顺序加锁:如所有线程先锁lockA再锁lockB。超时机制:使用tryLock()设置超时时间,避免无限等待。
面试高频题:
- “请模拟一个死锁场景并说明如何修复。”
答案要点:死锁代码:两个线程分别持有lockA和lockB,并尝试获取对方锁。修复方案:统一锁顺序,或使用tryLock()替代lock()。
五、线程间通信与协作
经典场景:生产者-消费者模型
- 问题:生产者向缓冲区写入数据,消费者读取数据,需避免缓冲区溢出或空读。
- 解决方案:阻塞队列(如BlockingQueue):自动处理等待与唤醒逻辑。wait/notify:通过条件判断实现线程协作。
面试高频题:
- “如何实现三个线程按顺序执行(T1→T2→T3)?”
答案要点:join方法:T1执行完后调用T2.join(),T2执行完后调用T3.join()。CountDownLatch:使用计数器控制线程启动顺序。
六、volatile关键字与内存可见性
volatile的三大特性:
- 可见性:修改后立即刷新到主内存,确保其他线程读取最新值。
- 禁止指令重排:通过内存屏障(Memory Barrier)防止编译器优化。
- 非原子性:仅保证可见性,不保证复合操作的原子性(如i++)。
适用场景:
- 状态标志:如volatile boolean flag用于线程终止信号。
- 单写多读:如缓存更新标志。
面试高频题:
- “volatile能替代synchronized吗?”
答案要点:不能。volatile仅解决可见性与有序性,无法保证原子性(如i++需配合CAS)。
七、Java内存模型(JMM)与happen-before规则
happen-before规则:
- 程序顺序规则:同一个线程内,前操作happen-before后操作。
- 监视器锁规则:解锁happen-before加锁。
- volatile变量规则:写happen-before读。
- 线程启动规则:Thread.start()happen-before线程执行。
- 线程终止规则:线程执行happen-beforeThread.join()返回。
面试高频题:
- “为什么需要happen-before规则?”
答案要点:在编译器、CPU指令重排的背景下,happen-before规则定义了操作的可见性与顺序性,避免并发错误。
八、并发工具类与高阶应用
常用工具类及场景:
- CountDownLatch:倒计时锁,用于等待多个线程完成。场景:主线程等待所有子线程执行完毕再继续。
- CyclicBarrier:循环屏障,用于多线程协同启动。场景:多个线程准备就绪后同时开始计算。
- Semaphore:信号量,用于限制资源访问数量。场景:数据库连接池控制最大连接数。
- Phaser:灵活阶段划分,适用于动态线程协作。场景:分布式任务分阶段执行。
面试高频题:
- “如何用Semaphore实现限流?”
答案要点:初始化Semaphore(10),每个请求调用acquire()获取许可,释放后调用release()。
九、多线程开发的常见陷阱
- 线程泄漏:原因:线程池未关闭或任务未正确终止。解决方案:使用ExecutorService.shutdown()优雅关闭线程池。
- 上下文丢失:原因:线程间传递数据未使用ThreadLocal。解决方案:在异步任务中传递ThreadLocal变量或使用TransmittableThreadLocal。
- 资源竞争:原因:多线程同时修改共享资源。解决方案:使用原子类(如AtomicInteger)或锁机制。
面试高频题:
- “如何优化高并发下的性能瓶颈?”
答案要点:锁优化:减少锁粒度,使用CAS无锁算法。异步化:将耗时操作异步执行(如使用CompletableFuture)。缓存:通过本地缓存(如Guava Cache)减少数据库访问。
高频考点记忆口诀
- 线程池:核心队列与拒绝,参数配置需谨慎。
- 死锁排查:互斥保持不可抢,循环等待是根源。
- 同步机制:synchronized粗粒度,ReentrantLock更灵活。
- 内存模型:volatile见可见,happen-before保顺序。