juc知识点打卡《一》

知识点一:synchronized关键字 —— Java内置锁的深度剖析

这是并发编程的基石,几乎必问。面试官不仅想知道你怎么用,更想知道它如何工作以及如何优化

 

 

1. 核心作用与用法

 
synchronized 是Java关键字,用于实现线程同步,保证同一时刻只有一个线程能进入被保护的代码块或方法,解决原子性内存可见性问题。
 

 

  • 修饰实例方法:锁是当前对象实例 (this)。
     
  • 修饰静态方法:锁是当前类的Class对象。
     
  • 修饰代码块:需显式指定锁对象(如 synchronized(obj))。
     

2. 底层原理与锁升级(面试核心)

 
synchronized 的锁信息存储在对象头的 Mark Word 中。为了在不同竞争场景下平衡性能,JVM设计了锁升级机制,这是高频考点:

 

  1. 无锁:初始状态。
     
  2. 偏向锁:假设只有一个线程使用锁。当该线程首次访问时,JVM会将线程ID记录到Mark Word中,之后该线程再进入时无需任何同步操作(如CAS),直接进入。适用于几乎没有竞争的场景
     
  3. 轻量级锁:当有另一个线程来竞争锁时,偏向锁升级为轻量级锁。线程会在自己的栈帧中创建锁记录(Lock Record),并通过 CAS操作 尝试将对象头的Mark Word替换为指向锁记录的指针。如果成功,则获取锁;如果失败,表示有竞争,会自旋(循环尝试)一定次数。
  4. 重量级锁:如果轻量级锁竞争失败(自旋超过阈值或等待线程数过多),会升级为重量级锁。此时,未获取到锁的线程会进入阻塞状态,并被放入一个等待队列,由操作系统进行调度。这是一个真正的互斥锁,涉及用户态到内核态的切换,开销最大。
     

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:丢弃队列中最老的任务,然后尝试提交新任务。
       

工作流程(四步决策法)

  1. 新任务提交。
     
  2. 如果 当前线程数 < corePoolSize,立即创建新线程执行。
     
  3. 否则,尝试将任务放入 workQueue
     
  4. 如果队列已满,且 当前线程数 < maximumPoolSize,创建新线程(临时线程)执行。
     
  5. 如果线程数已达最大值且队列已满,则触发 拒绝策略
     

3. 生命周期与优雅关闭

 

 

  • RUNNING:接受新任务,处理队列任务。
     
  • SHUTDOWN:不再接受新任务,但会处理完队列中已存在的任务。
     
  • STOP:不再接受新任务,也不处理队列任务,并中断正在执行的任务。
     
  • TIDYING/TERMINATED:过渡和终止状态。
     
  • 关闭方法
    • shutdown():平滑关闭,进入SHUTDOWN状态。
       
    • shutdownNow():立即关闭,尝试中断所有工作线程,返回未执行的任务列表,进入STOP状态。
       
    • 通常配合 awaitTermination 等待线程池真正终止。
       

总结:线程池的调优(参数设置)和对其工作流程的理解,是衡量开发者是否具备处理高并发场景能力的重要标准。

 

 


知识点三:Java内存模型 (JMM) 与 volatile、happen-before —— 理解并发的“道”

这是并发编程中最抽象也最本质的部分,用于解释多线程环境下可见性有序性问题。理解JMM是解决诡异并发Bug的基础。

1. 核心问题:为什么需要JMM?

 
现代计算机和编译器为了性能,会进行指令重排序,且CPU有多级缓存,导致线程对共享变量的修改不一定能立即被其他线程看到。JMM是一套规范,定义了程序中变量的访问规则,屏蔽了底层硬件和内存的差异。
 

 

2. 关键概念:happen-before

 
JMM向开发者提供了一套内存可见性的保证,即 happen-before 规则。如果操作A happen-before 操作B,那么A的结果对B可见。
 

 

  • 程序顺序规则:单线程内,书写在前面的操作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语义

 
这是JMM提供的一个安全初始化保证。对一个final域的写(在构造方法内完成),happen-before于后续对该final域所在对象的引用被任意线程获取。这能有效防止构造方法溢出问题(其他线程看到未初始化完全的对象)。
 

 

总结:JMM、happen-before和volatile是理解 synchronizedLock、原子类等所有高级并发工具如何保证线程安全的理论基础。面试中常通过“DCL单例为什么要加volatile”、“如何保证一个线程的修改对另一个线程可见”等问题来考察。

 

 

 
posted @ 2026-01-06 23:20  余明星  阅读(27)  评论(0)    收藏  举报