JUC面经

1. 并发编程有几大问题?

  • 从计算机发展史出发,CPU范围速度快,内存速度慢,为了缓解这个速度不一致问题,有了三级缓存,一级缓存二级缓存是CPU核心私有的,而三级缓存是共享的。问题来了,当并发环境下,当某一个CPU核心去执行某个线程任务时,把内存数据读到缓存进行修改,而另一个线程去读取就不是读取到对应修改后的数据,这就是可见性问题(一般通过Valite进行修饰,使共享资源修改对其他线程可见)
  • CPU或编译器可能会对指令进行重排序,导致执行顺序和代码书写顺序不一致,这就是有序问题
  • 原子问题说的是同一个操作要么全部执行,不然就回滚

2. synchronized是什么?

  • 在 Java 中,synchronized 关键字是用来控制线程同步的,就是在多线程的环境下,控制 synchronized 代码段不被多个线程同时执行。当然其只是高级语言的关键字,在JVM翻译过程后其实转化为两个JVM字节码指令monitorenter和monitorexit。
  • 作用场景可以是修饰类、方法、变量;
  • 在使用 synchronized 的时候,Java 内存模型会插入内存屏障,确保可见性和有序性。
  • 一个 synchronized 的对象在 JVM 中关联一个 Monitor 对象

在偏向锁、轻量级锁状态下,锁信息直接保存在 Mark Word 里;
当升级为 重量级锁(即 monitor 锁)时,Mark Word 中会保存一个指向 Monitor 的指针!

3. synchronized早期效率低下原因:

  • 在 Java 早期版本中,synchronized属于重量级锁,效率低下,
  • 因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。
  • 如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。

4. 锁升级产生的缘由?

  • 低并发环境下,锁竞争少情况下,不进行阻塞,减少上下文切换,进而提高效率。
  • 以电商平台为例,其实对资源的锁的竞争并没有那么频繁,往往只有那么1,2个时间点,所以通过对应的偏向锁,轻量级锁、重量级锁进行升级,可以相对应的减少这个线程上下文切换的损失

5. 锁升级过程是什么,为什么可以那样设置锁升级?

其实背后的原理还是跟“对象头“有关,因为就是我的JVM会在每一个Java对象头部维护一段元数据,即markword。里面存储的就是标志位、线程ID等情况。故当一个线程访问该对象资源的时候,那么对应的会在这个对象资源的头部修改与该线程的一些信息。

  • 无锁状态:
    • 当一个线程访问共享资源时,没有任何锁被使用,资源可以被多个线程自由访问。
  • 偏向锁:
    • 当一个线程第一次访问同步代码块时,会获取一个偏向锁。此时,锁会偏向于该线程,其他线程访问时不会竞争该锁,避免了不必要的同步开销。
    • 同时在对应的markwok里面记录对应的线程id
  • 轻量级锁:(考虑的是竞争锁的线程不多,而且线程持有锁的时间不长的一个情景)
    • 当偏向锁的线程再次访问同一锁时,不需要进行任何操作。如果有其他线程尝试访问这个锁,偏向锁会被撤销,转为轻量级锁。
    • 这时,JVM会在线程自己的栈中创建一个锁记录,然后就是竞争的那个线程使用 CAS(Compare-And-Swap)操作尝试获取锁。适应性自旋,有一定的次数,不可能一直自选,会导致cpu空转浪费性能。

CAS:一个原子操作,用于比较当前的数值是否等于预期值,如果相等就更新为新值,否则不做任何修改。

  • 重量级锁:
    • 如果轻量级锁自选一定操作后还是没有获得该锁,或者说当有更多的线程去竞争这个资源时,那么锁会升级为重量级锁。这时,所有尝试获取该锁的线程都会被挂起到锁池,直到锁被释放。这时这个重量级锁就会调用操作系统底层的multex lock来实现,并且有对应的上下文切换。

6. 自旋锁就一定是性能好的吗,为什么还要有这个重量级锁?

  • 对于锁的持有时间较长的操作,自旋锁可能会导致大量的 CPU 时间浪费,而重量级锁则可以避免这种情况。
  • 在锁竞争激烈的环境中,重量级锁可以更有效地管理线程,避免无效的 CPU 自旋。
  • 在高并发场景下,自旋锁可以提高性能,但在锁竞争严重或持锁时间长的场景下,重量级锁更优。

7. 为什么要有这个锁监视器呢?

  • 锁监视器是 Java 中用于管理对象锁的机制,每个对象都有一个与之关联的锁监视器,里面有一些字段,如记录持有锁的线程,重入计数的计数器,用来记录锁的一个重入次数(只有当这个为0才释放锁),还有锁池和等待池等。
  • 锁监视器(Monitor)并不直接存储在 Java 对象中,而是在锁升级为重量级锁时,JVM 动态为该对象关联一个 Monitor 对象,并把指针存在对象头的 Mark Word 中。Monitor 是 JVM 管理线程同步的关键机制,存储在 JVM 的内部 Monitor 对象池中。
  • Monitor 是由 JVM 创建的,本质是 C++ 实现的数据结构

8. 锁池和等待池这两个的本质区别是什么?为什么要有这两个,这两个不都是阻塞情况吗?

  • 这两个存放集合的用法都不一样
  • 锁池专注于管理可用锁的状态,确保锁的获取和释放高效进行。
  • 等待池专注于管理被阻塞的线程,确保这些线程能够在锁释放时及时被唤醒。

9. AQS了解过吧,谈谈这个是什么?

抽象队列同步器(AQS)是一个用于构建同步器的抽象类,核心由两部分组成:

  • 一个 volatile int state 字段,用于表示同步状态(是否被锁、计数等);

  • 一个基于双向链表的 FIFO 等待队列,用于管理获取同步状态失败的线程,这些线程会被封装为 Node 节点加入队列,等待后续唤醒。

  • 大致流程:

线程尝试获取锁(CAS其字段state)
   ↓
若获取失败,构造 Node 节点入队列(CAS)
   ↓
LockSupport.park() 挂起线程
   ↓
持有锁的线程 unlock()
   ↓
AQS 调用 unpark() 唤醒队列中的下一个线程从而实现并行的安全性

10.线程池主要是干嘛的?

  • 我的理解是线程池是为了能够复用线程资源而产生的,并且可以对于线程进行一个统一的管理。

  • 线程池的类型有4种,分别是FixedThreadPool、CachedThreadPool、SingleThreadExecutor、ScheduledThreadPool;

  • 拒绝策略啥的,自定义(要么抛出异常,要么丢弃任务队列老的,或者不做处理)

11.线程可以那些方法进行创建的?然后就是一般这个线程是有什么状态的?

答:
创建方法:

  • 继承Thread类;实现Runnable接口;实现Callable接口(带返回值);通过线程池(Executor框架)。
    状态类型:
  • 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、等待(Waiting)、超时等待(Timed Waiting)、终止(Terminated)。

12.你说说同步器有什么,分别有什么作用的?

1.ReentrantLock(可重入锁)

  • 等价于 synchronized,但更灵活
  • 支持公平/非公平(说的是按顺序来分配对应的锁)、可中断、限时等待等自定义锁行为
    2.ReentrantReadWriteLock(读写锁)
  • 多个读线程可以并发读,写线程必须独占
  • 适用于读多写少的场景,比如缓存系统
    3.Semaphore(信号量)
  • 控制资源访问的最大线程数(限流)
  • 例如:最多只能有 10 个线程访问数据库连接
    4.CountDownLatch(倒计时闭锁)
  • 主线程等待多个子线程执行完才继续
  • 一次性的,不能重用
    5.CyclicBarrier(循环栅栏)
  • 所有线程达到屏障点后才放行
  • 可重用,例如分批处理
    6.Exchanger(线程数据交换器)
  • 两个线程之间进行配对数据交换
  • 多用于双缓冲算法等协同处理
    7.StampedLock(带戳的锁)
  • 支持乐观读、悲观读和写三种模式
  • 相比 ReentrantReadWriteLock 性能更好(尤其在读多场景)

13.信号量和线程池任务队列有什么区别?

  • 信号量主要是用来控制并发的访问的数量。
  • 线程池的任务队列只是为了控制在多任务调度的时候,能够以顺序进行执行,用于管理并执行任务

14.Synchronized与AQS中的同步器的区别是什么,哪些场景用的地方不一样?在早期优化后为什么还要有这个AQS的同步器呢

  • Synchronized这个是关键字,而AQS里面的同步器如 Reentrant Lock这些是类,他们本质就不一样。
  • 其次这个ReentrantLock等同步器他们可以在高并发的情况下设置更好的细腻操作,如条件队列,超时,中段等。而Synchronized确没有那么细腻,它只适用于一些代码块的简单互斥情况。至于其他的同步器每一个都有各自的作用去处理锁行为。
  • 同时我的这个Synchronized是在低并发环境下的较AQS中同步器快,而AQS在高并发反而是好的。

虽然 synchronizedAQS 最终都使用队列、都可能导致上下文切换,但 synchronized 是 JVM 的内建锁机制,黑盒子;而 AQS 是 Java 层的可编程同步器框架,白盒子,控制粒度和能力远超 synchronized。
JVM层面是不可控的,而我的AQS确实可以通过公平,超时等进行条件控制

15.可重入锁和这个轻量锁不都是CAS吗?那有什么区别这两个.

实现场景不同:

  • 可重入锁(ReentrantLock)使用 CAS:
    • ReentrantLock 是基于 AQS 实现的。
    • 当线程尝试获取锁时,会用 CAS 来更新 state 字段(标志锁的持有状态)。
    • 如果 CAS 成功:说明锁未被占用,当前线程获取成功;
    • 如果失败:说明锁已被其他线程持有,当前线程进入 AQS 等待队列(后续可能阻塞、park 等)。
  • 轻量级锁使用CAS:
    • 过 CAS 将当前线程的栈帧 Lock Record 绑定到对象头中的 Mark Word 上
    • 并且当升级后当前的Mark Word 中会保存一个指向 Monitor 的指针

16.CAS操作是什么?

  • 是一个原子操作,用于在低并发的情况下对资源进行获取。
  • AB两线程在获取一个资源,如果资源没有变更,那么他们之间会轮询进行获取,但是当一个获取资源并且更新成功,那么后续的另一个线程就CAS失败,那么锁就变为重量锁。只有当释放后,别的线程才能获取。
  • 主要的是数据的一致性问题。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值