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在高并发反而是好的。
虽然
synchronized和AQS最终都使用队列、都可能导致上下文切换,但 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失败,那么锁就变为重量锁。只有当释放后,别的线程才能获取。
- 主要的是数据的一致性问题。
1655

被折叠的 条评论
为什么被折叠?



