juc知识点打卡《一》
知识点一:synchronized关键字 —— Java内置锁的深度剖析
这是并发编程的基石,几乎必问。面试官不仅想知道你怎么用,更想知道它如何工作以及如何优化。
1. 核心作用与用法
synchronized 是Java关键字,用于实现线程同步,保证同一时刻只有一个线程能进入被保护的代码块或方法,解决原子性和内存可见性问题。
- 修饰实例方法:锁是当前对象实例 (
this)。 - 修饰静态方法:锁是当前类的Class对象。
- 修饰代码块:需显式指定锁对象(如
synchronized(obj))。
2. 底层原理与锁升级(面试核心)
synchronized 的锁信息存储在对象头的 Mark Word 中。为了在不同竞争场景下平衡性能,JVM设计了锁升级机制,这是高频考点:
- 无锁:初始状态。
- 偏向锁:假设只有一个线程使用锁。当该线程首次访问时,JVM会将线程ID记录到Mark Word中,之后该线程再进入时无需任何同步操作(如CAS),直接进入。适用于几乎没有竞争的场景。
- 轻量级锁:当有另一个线程来竞争锁时,偏向锁升级为轻量级锁。线程会在自己的栈帧中创建锁记录(Lock Record),并通过 CAS操作 尝试将对象头的Mark Word替换为指向锁记录的指针。如果成功,则获取锁;如果失败,表示有竞争,会自旋(循环尝试)一定次数。
- 重量级锁:如果轻量级锁竞争失败(自旋超过阈值或等待线程数过多),会升级为重量级锁。此时,未获取到锁的线程会进入阻塞状态,并被放入一个等待队列,由操作系统进行调度。这是一个真正的互斥锁,涉及用户态到内核态的切换,开销最大。
3. 与Lock接口的对比
- 实现层面:
synchronized是JVM层面的内置锁,自动管理锁的获取和释放;Lock是JDK API层面的接口,需要手动lock()和unlock()。 - 功能灵活性:
Lock更灵活,支持尝试非阻塞获取锁 (tryLock)、可中断锁 (lockInterruptibly)、超时获取锁、公平锁等。synchronized是非公平的。 - 性能:在低竞争情况下,
synchronized经过锁升级优化后性能很好;高竞争时,Lock通常能提供更稳定的吞吐量。
总结:理解 synchronized 的锁升级过程,是回答其性能优劣、适用场景以及与 Lock 区别的关键。
知识点二:线程池 (ThreadPoolExecutor) —— 并发资源管理的核心
线程池是工程实践中使用最广泛的并发组件,其原理和配置是面试的重中之重。
1. 为什么要用线程池?
- 降低资源消耗:通过线程复用,减少频繁创建和销毁线程的开销。
- 提高响应速度:任务到达时,无需等待线程创建即可执行。
- 提高线程可管理性:可以统一管理、监控和调优线程资源。
2. 核心参数与工作流程(必考流程)
ThreadPoolExecutor,其工作流程由以下几个核心参数决定:
- corePoolSize:核心线程数。即使空闲也不会被回收(除非
allowCoreThreadTimeOut为true)。 - maximumPoolSize:最大线程数。
- workQueue:任务队列。常用的有
LinkedBlockingQueue(无界)、ArrayBlockingQueue(有界)、SynchronousQueue(不存储任务)。 - RejectedExecutionHandler:拒绝策略。当线程池和队列都满了,如何处理新任务。内置策略有:
AbortPolicy(默认):直接抛出RejectedExecutionException。CallerRunsPolicy:由提交任务的线程自己执行。DiscardPolicy:默默丢弃新任务。DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试提交新任务。
工作流程(四步决策法):
- 新任务提交。
- 如果 当前线程数 < corePoolSize,立即创建新线程执行。
- 否则,尝试将任务放入 workQueue。
- 如果队列已满,且 当前线程数 < maximumPoolSize,创建新线程(临时线程)执行。
- 如果线程数已达最大值且队列已满,则触发 拒绝策略。
3. 生命周期与优雅关闭
- RUNNING:接受新任务,处理队列任务。
- SHUTDOWN:不再接受新任务,但会处理完队列中已存在的任务。
- STOP:不再接受新任务,也不处理队列任务,并中断正在执行的任务。
- TIDYING/TERMINATED:过渡和终止状态。
- 关闭方法:
shutdown():平滑关闭,进入SHUTDOWN状态。shutdownNow():立即关闭,尝试中断所有工作线程,返回未执行的任务列表,进入STOP状态。- 通常配合
awaitTermination等待线程池真正终止。
总结:线程池的调优(参数设置)和对其工作流程的理解,是衡量开发者是否具备处理高并发场景能力的重要标准。
知识点三:Java内存模型 (JMM) 与 volatile、happen-before —— 理解并发的“道”
这是并发编程中最抽象也最本质的部分,用于解释多线程环境下可见性和有序性问题。理解JMM是解决诡异并发Bug的基础。
1. 核心问题:为什么需要JMM?
2. 关键概念:happen-before
- 程序顺序规则:单线程内,书写在前面的操作happen-before后面的操作。
- volatile变量规则:对一个volatile变量的写操作happen-before后续对这个变量的读操作。
- 锁规则(synchronized/monitor):对一个锁的解锁happen-before后续对这个锁的加锁。
- 线程启动规则:
Thread.start()happen-before 新线程的任何操作。 - 线程终止规则:线程中的所有操作都happen-before其他线程检测到该线程已终止。
- 传递性:如果A happen-before B,且B happen-before C,则A happen-before C。
3. volatile关键字的双重语义
volatile 是轻量级的同步机制,它保证了:
- 可见性:对一个volatile变量的写,会立即刷新到主内存;读一个volatile变量时,会从主内存读取最新值。
- 禁止指令重排序:通过内存屏障实现。
- (但不保证原子性):例如
count++这种复合操作,volatile无法保证线程安全。
4. final域的happen-before语义
final域的写(在构造方法内完成),happen-before于后续对该final域所在对象的引用被任意线程获取。这能有效防止构造方法溢出问题(其他线程看到未初始化完全的对象)。
总结:JMM、happen-before和volatile是理解 synchronized、Lock、原子类等所有高级并发工具如何保证线程安全的理论基础。面试中常通过“DCL单例为什么要加volatile”、“如何保证一个线程的修改对另一个线程可见”等问题来考察。
浙公网安备 33010602011771号