第二单元

亮子 2025-07-27 21:39:59 253 0 0 0

1、Synchronized 和 Lock 的区别?

特性 Synchronized Lock(如ReentrantLock)
锁类型 JVM内置锁(关键字) JDK接口实现(如ReentrantLock
获取/释放 自动获取和释放(进入/退出代码块) 必须手动lock()unlock()(配合try-finally)
灵活性 不灵活(仅非公平锁) 可公平/非公平、可尝试获取、可超时
中断响应 不支持线程中断 支持lockInterruptibly()
条件队列 单一等待队列 支持多个Condition(精准唤醒线程)

2、ReentrantLock的实现原理?

首先,ReentrantLock是一种可重入的排它锁,主要用来解决多线程对共享资源竞争的
问题。
它的核心特性有几个:
- 它支持可重入,也就是获得锁的线程在释放锁之前再次去竞争同一把锁的时候,不
需要加锁就可以直接访问。
- 它支持公平和非公平特性
- 它提供了阻塞竞争锁和非阻塞竞争锁的两种方法,分别是lock()和tryLock()。
- 锁的竞争,ReentrantLock是通过互斥变量,使用CAS机制来实现的。
- 没有竞争到锁的线程,使用了AbstractQueuedSynchronizer这样一个队列同步器来存储,底层是通过双向链表来实现的。当锁被释放之后,会从AQS队列里面的头部唤醒下一个等待锁的线程。
- 公平和非公平的特性,主要是体现在竞争锁的时候,是否需要判断AQS队列存在等待中的线程。

最后,关于锁的重入特性,在AQS里面有一个成员变量来保存当前获得锁的线程,当同一个线程下次再来竞争锁的时候,就不会去走锁竞争的逻辑,而是直接增加重入次数。

3、锁的状态分类都有哪些?

  • 偏向锁:偏向于第一个获取锁的线程,适用于锁竞争不激烈的场景。
  • 轻量级锁:通过 CAS 操作实现无阻塞的锁竞争,适用于竞争不激烈的场景。
  • 重量级锁:当多个线程频繁争抢锁时,JVM 会升级为重量级锁,涉及线程的阻塞和唤醒,开销较大。

4、什么是可重入锁?

可重入锁是允许同一线程多次获取同一把锁的同步机制。核心在于锁内部通过计数器记录获取次数:首次获取时计数器置 1,重入时递增,释放时递减至 0 才彻底释放锁。

5、默认方法和静态方法的作用?

在 Java 中,默认方法(Default Methods) 和 静态方法(Static Methods) 是接口(Interface)的两种扩展特性,它们的作用分别是:
1. 默认方法(Default Methods)
- 作用:
允许在接口中定义具有方法体的方法,为接口提供默认实现,同时不破坏实现类的兼容性。
- 核心优势:
在不强制所有实现类重写方法的情况下,向现有接口添加新功能(如 Java 8 的 Collection 接口添加 forEach 方法)。类可以实现多个接口并继承它们的默认方法(需处理方法冲突)。
2. 静态方法(Static Methods)
- 作用:
为接口提供工具类方法,无需创建实例即可调用,增强接口的功能性。
- 核心优势:
将与接口相关的静态工具方法集中在接口中,避免额外的工具类(如 Collections → Collection)。使代码结构更清晰,工具方法与接口语义绑定。
一句话概括
默认方法:让接口可以提供默认实现,允许接口在不破坏现有实现类的前提下扩展功能;
静态方法:让接口能够包含工具类方法,无需实例化即可调用,使接口兼具行为定义和工具封装能力。

6、什么是反射?

反射是一种允许程序在运行时检查和操作类、接口、字段和方法的机制。通过反射,可以动态地获取类的信息、创建对象、调用方法、访问字段等。

7、反射的优缺点?

Java 反射的优点:
(1).增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作。
(2).提高代码的复用率,比如动态代理,就是用到了反射来实现。
(3).可以在运行时轻松获取任意一个类的方法、属性,还能通过反射进行动态调用。

Java反射的缺点:
(1).反射会涉及到动态类型解析,所以JVM无法对这些代码进行优化,导致性能要比非反射调用更低。
(2).使用反射以后,代码的可读性会下降。
(3).反射可以绕过一些限制访问的属性或者方法,可能会导致破坏了代码本身的抽象性。

8、线程池参数如何合理配置?

  1. CPU 密集型:尽量使用较小的线程池,一般为CPU核心数+1。
  2. IO 密集型:
    (1)可以使用较大的线程池,一般为CPU核心数 * 2。(常用)
    (2)线程等待时间与线程 CPU 时间之比 +1)* CPU 数目。
    (3)混合型:可以将任务分为 CPU 密集型和 IO 密集型,然后分别使用不同的线程池去处理。

9、创建线程有几种方式?

(1).继承 Thread 类并重写 run 方法创建线程,实现简单但不可以继承其他类
(2).实现 Runnable 接口并重写 run 方法。避免了单继承局限性,编程更加灵活,实现解耦。
(3).实现 Callable 接口并重写 call 方法,创建线程。可以获取线程执行结果的返回值,并且可以抛出异常。
(4).使用线程池创建(使用 java.util.concurrent.Executor 接口)

10、线程池的核心参数有哪些?

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:空闲的线程存活时间
  • unit:时间单位,空闲线程存活时间单位
  • workQueue:阻塞队列,用于保存任务的阻塞队列
  • threadFactory:创建线程的工程类
  • handler:饱和策略(拒绝策略)

11、线程池的工作原理?(重点)

线程池任务提交流程:
(1).核心线程创建阶段
当线程池接收到新任务时,首先检查当前运行的线程数量。
(2).若线程数量少于corePoolSize(核心线程数),无论其他线程是否处于空闲状态,都会立即创建一个新的核心线程来处理该任务。
(3).任务队列缓存阶段
若当前线程数量已达到或超过corePoolSize(核心线程数),新任务将被放入workQueue(工作队列)中等待处理。
(4).临时线程创建阶段
若任务队列已满(如队列达到最大容量),且当前线程数量仍少于maximumPoolSize(最大线程数),则会创建临时线程(非核心线程)来处理新任务。
临时线程在处理完任务后,若在keepAliveTime(线程空闲时间)内没有新任务,将被销毁。
(5).拒绝策略触发阶段
若线程数量已达到maximumPoolSize(最大线程数)且任务队列已满,此时新提交的任务将触发拒绝策略。

12、四种拒绝策略的区别?(重点)

线程池的拒绝策略:
(1).AbortPolicy(抛出一个异常,默认的)
(2).DiscardPolicy( 直接丢弃任务 )
(3).DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
(4).CallerRunsPolicy(交给线程池调用所在的线程进行处理)