卷吧-2022年面试总结(面经篇)

以下面经是近一个月的面试经历总结, 包含: Java基础, 算法, 运维, 数据库, 框架, DDD, 中间件等多项技能. 如有补充或错处请评论区留言.


解题思路: 一个知识点有多种问法, 无非就是说明最核心的是什么.

  1. 能不能一句话概括是什么?
  2. 与之关联的知识点是什么?
  3. 是否有相似性和差异性?
  4. 应用场景是什么?

Java基础篇

Synchronized

常见问题

synchronized 1.4升级了什么
synchronized底层实现
锁升级机制

核心-锁

锁优化

synchronized是java中的关键字, 是一种同步锁, JDK1.5以前唯一的线程同步方式,可以作用在方法和代码块上. synchronized必须指定对象锁, 默认是this, 也可以自己指定锁对象.

但是synchronized是互斥同步性能非常差1, 为了解决互斥同步带来的负面作用, 虚拟机团队在JDK1.4.2引入了自旋锁2默认是关闭的,那么问题来了,自旋的线程依旧会浪费性能, 如果线程锁定资源的时间太长, 难道要一直等下去? 因此, 虚拟机规定了自旋次数默认是10次3. 那么问题又来了, 要是线程自旋10+次还是不一定能拿到锁,然后再挂起, 岂不是白自旋了. 反之, 如果自旋11次就能拿到了, 但是挂起来了, 岂不是更难受, 因此在JDK1.6又做了优化,JDK1.6默认开启自旋锁, 同时引入了自适应的自旋锁.

所谓的自适应自旋锁, 就是说自旋的时间不再固定了, 而是由前一次在同一个锁上的自旋时间锁的拥有者状态来决定. 就是说, 如果资源锁定时间比较短, 自旋几次就能成功, 并且自旋的线程还在运行着, JVM认为可以自旋, 多旋几次也没事比如100次; 如果资源锁定时间比较长, 自旋几乎拿不到锁, 就直接挂起了.


synchronized锁原理

通过javap命令对Synchronized代码块的字节码文件进行汇编解析, 如下

	public class Synchronized {
	    @Test
	    public void test() {
	        synchronized (Synchronized.class) {
	            System.out.println(11111);
	        }
	    }
	}

monitorsynchronized同步块, 会在被包裹代码的前后分别形成monitorenter和monitorexit两个字节码指令, monitor指令中有2个重要成员一是持有当前对象锁的线程, 另一个是记录锁定次数的计数器. 根据虚拟机规范要求, monitorenter在被执行时, 首先要尝试获取对象的锁, 若无锁则当前线程持有了对象锁, 如果有锁则把当前线程的锁定计数器+1. 相应的在执行monitorexit指令时,对计数器-1, 当计数器为0时锁就被释放了. 基于monitor指令的两个成员, 对synchronized而言是线程可重入的, 不会把自己锁死.


锁升级

无锁(锁消除)->偏向锁->轻量级锁->重量级锁
无锁
当使用了同步关键字synchronized, 但是在即时编译器(JIT)运行时并未检测到存在共享数据竞争的情况时, 会对锁进行消除, 也就是无锁. 锁消除的主要判定依据来源于逃逸分析4的数据支持, 比如, String是一个不可变的类(被final修饰过), 如果对多个字符串相加 “a” + “b” + “c”, 在JDK1.5以前会转化为StringBuffer对象连续append(), JDK1.5以后转化为StringBuilder对象连续append(). StringBuffer之所以线程安全是因为在每个append()方法上都加了synchronized关键字; 看如下代码:

	public String concatString(String...strings) {
        StringBuffer sbafer = new StringBuffer();
        for (int i = 0; i < strings.length; i++) {
            sbafer.append(strings[i]);
        }
        return sbafer.toString();
    }

当JIT运行时发现append操作只会被限制在concatString方法内部, sbafer的所有引用永远不会逃逸到concatString方法之外, sbafer相当于是私有的, 其他线程也不会访问到, 尽管有synchronized也会对其进行消除.
轻量级锁
偏向锁是JDK1.6中加入的新型锁机制, 相对于使用操作系统的互斥量而言是轻量级的, 轻量级锁并不是重量级锁的替代品, 轻量级锁的目的是在无锁情况下尽量减少重量级锁的使用来提升性能. java的对象头包含两部分数据, 一部分是MarkWord5, 另一部分是存储指向方法区对象类型数据的指针. 而MarkWord就是实现偏向锁和轻量级锁的关键. 对象头的布局如下:

存储内容标志位状态
对象哈希码, 对象分代年龄01未锁定
指向所记录的指针00轻量级锁定
指向重量级锁的指针10膨胀(重量级锁定)
空, 不需要记录信息11GC标记
偏向线程ID, 偏向时间戳, 对象分代年龄01可偏向

加锁过程
加锁过程
① 当程序进入同步代码块的时候, 如果对象没有锁定(01), 虚拟机首先会在当前线程的栈帧中建立一个Lock Record的锁记录空间(官方叫 Displaced Mark Word), 用于存储对MarkWord的副本.
② 然后虚拟机尝试将对象的MarkWord的指针指向LockRecord副本.
③ 通过CAS操作对指针进行更新, 将MarkWord指向Displaced Mark Word, 同时将owner记录锁对象和线程的关系.
④ 如果前3个操作都完成, 标识当前线程加锁成功, 将锁标志位标记为00(轻量级锁定), 进入同步块开始执行.
特殊说明:
如果最终MarkWord更新到LockRecord失败了有两种情况, 一是当前线程已经持有了锁, 另一种是被其他线程抢占了.
已经持有锁: 虚拟机会检查对象的MarkWord是否指向当前线程的栈帧, 如果是就可重入, 继续执行代码.
已经被抢占: 虚拟机发现当前锁对象已经被2个以上的线程同时争抢, 会发生锁膨胀(锁状态为10),将轻量级锁升级为重量级锁.

解锁过程
解锁过程其实就是加锁的反向操作, 也是通过CAS来进行操作的:
如果对象的MarkWord指向着线程的锁记录, 就尝试用CAS操作把LockRecord的MarkWord写回到对象头当中. 如果成功, 解锁成功; 如果替换失败,说明其他线程已经获取过该锁, 就要在释放锁(锁状态10)的同时, 唤醒其他被挂起的线程.

偏向锁
偏向锁也是JDK1.6中引入的一项锁优化默认是开启的, 目的是消除数据在无竞争情况下的同步原语, 进一步提高程序的性能. 轻量级锁采用CAS来减少互斥量所带来的性能损耗, 偏向锁则是将CAS也省了. 偏向锁是偏袒的意思, 意味着锁会偏袒某个线程, 可着某一个线程拿锁.

开启偏向锁MarkWord的变化
轻量级锁

当锁对象第一次被线程获取的时候, 虚拟机会把对象头中锁的标志位标记为01(偏向锁模式), 同时通过CAS操作将当前线程的线程ID记录到对象头的MarkWord中. 当下次线程在来的时候发现是同一个线程就会直接放行.直到有新的线程来获取锁会打破偏向锁模式升级为轻量级锁.

锁升级过程

锁升级过程
① JIT运行时首先会进行无锁优化, 如果可以被优化就以无锁方式运行.
② 如果开启了偏向锁, 采用偏向锁模式运行, 会在对象头中记录线程ID, 当执行完以后等待线程下次执行继续使用偏向锁. 直到有其他线程参与锁对象的竞争, 才会撤销偏向锁, 偏向锁升级取决于对象锁的状态, 如果是锁定状态, 则直接采用轻量级锁的MarkWord替换. 如果未锁定则将锁标志位标记为轻量级锁未锁定状态.
③ 当轻量级锁替换MarkWord失败, 并且有多个线程同时争抢锁对象, 则发生锁膨胀升级为重量级锁.


  1. 共享资源统一时刻只能被一个线程使用, 其他线程只能挂起等待, 而JVM的线程实际是内核态和用户态之间来回切换, 每个内核态线程都是与用户态线程一一对应<一对一模型>而且内核资源也是有限的 ↩︎

  2. 共享数据的锁状态只会持续很短的一段时间, 为了减少互斥同步带来的挂起开销, 而采取的一种手段, 就是让没有获得锁的线程自己循环几次(无限次), 直到能获得锁为止. ↩︎

  3. -XX:PreBlockSpin ↩︎

  4. 如果在一段代码中, 堆上的所有数据都不会套一出去从而被其他线程访问到, 那就可以把他们当做栈上的数据对待, 认为是线程私有的. ↩︎

  5. 存储对象资深的运行时数据, 如哈希码(hashCode), GC分代年龄以及锁的类型和状态等. ↩︎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

seeyoe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值