面试题答案
一键面试Java中锁优化技术
- 偏向锁
- 适用场景:适用于只有一个线程频繁访问同步块的场景。在这种场景下,偏向锁可以减少不必要的锁竞争,提高性能。
- 工作原理:当一个线程访问同步块并获取锁时,会在对象头中记录当前线程的ID。之后该线程再次访问同步块时,只需检查对象头中记录的线程ID是否与自己一致,若一致则无需进行CAS操作来获取锁,从而节省开销。若有其他线程尝试获取锁,则偏向锁撤销,升级为轻量级锁。
- 轻量级锁
- 适用场景:适用于多个线程在短期内交替访问同步块的场景。
- 工作原理:当线程进入同步块时,如果同步对象没有被锁定(锁标志位为01),虚拟机首先在当前线程的栈帧中创建一个锁记录空间,用于存储锁对象目前的Mark Word的拷贝(官方称为Displaced Mark Word)。然后,线程尝试使用CAS操作将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁;如果失败,表示其他线程竞争锁,轻量级锁膨胀为重量级锁。
- 自旋锁
- 适用场景:适用于线程持有锁的时间较短,且CPU资源不紧张的场景。
- 工作原理:当一个线程尝试获取锁时,如果锁已经被其他线程持有,该线程不会立即阻塞,而是在原地空转若干次(自旋),期望在这段时间内持有锁的线程能够释放锁,从而避免线程上下文切换带来的开销。自旋次数通常由JVM参数控制,若自旋一定次数后仍未获取到锁,则该线程进入阻塞状态。
AQS框架
- 设计思想:AQS框架是基于FIFO队列的同步器框架,采用模板方法模式。它通过一个FIFO队列来管理等待获取同步状态的线程,同时利用一个int类型的变量来表示同步状态。子类通过继承AQS并实现其抽象方法来管理同步状态,从而实现不同类型的同步器。
- 核心组件
- 同步队列:即FIFO队列,用于存储等待获取同步状态的线程。每个节点包含前驱节点、后继节点和线程引用。
- 同步状态(state):一个int类型的变量,用于表示同步状态。不同的同步器对state的含义和操作方式不同,例如ReentrantLock中,state表示锁的持有次数。
- 在实现锁和同步器中的作用
- 实现锁:以ReentrantLock为例,它内部的Sync类继承自AQS。获取锁时,调用AQS的acquire方法,该方法会尝试获取同步状态(tryAcquire方法,由子类实现),若获取失败则将当前线程封装成节点加入同步队列等待。释放锁时,调用AQS的release方法,尝试释放同步状态(tryRelease方法,由子类实现),若释放成功则唤醒同步队列中的头节点线程。
- 实现同步器:如CountDownLatch、Semaphore等同步器也是基于AQS实现。CountDownLatch通过AQS的state表示计数,线程调用await方法时,若state不为0则进入同步队列等待,调用countDown方法时,state减1,当state为0时唤醒所有等待线程。Semaphore通过AQS的state表示信号量数量,获取信号量时尝试减少state,释放信号量时增加state。