当前位置: 首页 > article >正文

并发编程面试题三

1、并发编程三要素

  • 原子性:解决操作不可分割的问题。

  • 可见性:解决线程间数据同步的问题。

  • 有序性:解决指令执行顺序的问题。

2、如何保证三要素

  • 原子性

    • 使用原子类(如AtomicInteger)。

    • 使用锁(如synchronizedReentrantLock)。

  • 可见性

    • 使用volatile关键字。

    • 使用锁(如synchronizedReentrantLock)。

  • 有序性

    • 使用volatile关键字。

    • 使用锁(如synchronizedReentrantLock)。

    • 遵循happens-before规则。

3、常见的进程间的调度

  • 先来先服务调度算法:按照进程到达就绪队列的顺序进行调度,但可能导致短进程一直等待长进程
  • 短作业优先调度算法:优先调度预计运行时间最短的进程,可以减少平均等待时间,但可能导致长进程“饥饿”。
  • 时间片轮转调度算法:为每个进程分配一个固定的时间片(Time Slice),时间片用完后强制切换到下一个进程,但时间片设置过小会导致频繁上下文切换,降低效率。
  • 优先级调度算法:为每个进程分配一个优先级,优先调度优先级高的进程,可能导致低优先级进程“饥饿”。

4、常见的线程间的调度算法以及Java是哪种

线程调度是指系统为线程分配CPU使用权的过程,主要分两种。

(1)协同式线程调度(分时调度模式):线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。

(2)抢占式线程调度:每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。

Java线程调度就是抢占式调度,优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那就随机选择一个线程。两线程同时处于就绪runnable状态时,优先级越高的线程越容易被系统选择执行。

5、Java多线程常用的锁

悲观锁:当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候都会上锁,别的线程去拿数据的时候就会阻塞,比如synchronized。

乐观锁:每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新,比如CAS。

小结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景。

公平锁:按照请求锁的顺序来分配锁,先请求的线程先获得锁,比如ReentrantLock(底层是同步队列FIFO:First Input First Output来实现)。

非公平锁:不保证按请求顺序分配锁,允许插队,即新来的线程可能优先获取锁,存在有线程饿死,一直拿不到锁,比如synchronized、ReentrantLock。

小结:非公平锁性能高于公平锁,更能充分利用CPU的时间。

可重入锁:一个线程可以多次获取同一个锁而不被阻塞,直到它释放所有锁,比如synchronized、ReentrantLock。

不可重入锁:一个线程不能多次获取同一个锁,否则会导致死锁。

小结:可重入锁能一定程度的避免死锁。

自旋锁:线程在获取不到锁时,不会进入休眠状态,而是不断循环检查锁的状态,直到成功获取锁。

小结:不会发生线程状态的切换,一直处于用户态,减少了线程上下文切换的消耗,缺点是循环会消耗CPU。

共享锁:也叫S锁/读锁,允许多个线程同时读取共享资源,但不允许写操作(修改,增加,删除数据)。

互斥锁:也叫X锁/排它锁/写锁/独占锁/独享锁,同一时刻只有一个线程可以持有该锁,无论是读还是写操作。

死锁:两个或两个以上的线程在执行过程中,由于竞争资源而造成的一种阻塞的现象,若无外力作用,它们都将无法让程序进行下去。

下面三种是Jvm为了提高锁的获取与释放效率而做的优化,是针对Synchronized的锁升级。

偏向锁:一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,获取锁的代价更低。

轻量级锁:当锁是偏向锁的时候,被其他线程访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,但不会阻塞,且性能会高点。

重量级锁:当锁为轻量级锁的时候,其他线程虽然是自旋,但自旋不会一直循环下去,当自旋一定次数的时候且还没有获取到锁,就会进入阻塞,该锁升级为重量级锁,重量级锁会让其他申请的线程进入阻塞,性能也会降低。

6、死锁的4个必要条件

互斥条件:资源不能共享,只能由一个线程使用。

请求与保持条件:线程已经获得一些资源,但因请求其他资源发生阻塞,对已经获得的资源保持不释放。

不可抢占:有些资源是不可强占的,当某个线程获得这个资源后,系统不能强行回收,只能由线程使用完自己释放。

循环等待条件:多个线程形成环形链,每个都占用对方申请的下个资源。

只要发生死锁,上面的条件都成立;只要一个不满足,就不会发生死锁。

7、对synchronized的理解

synchronized是用来解决线程安全的问题,常用在 同步普通方法、静态方法、代码块中。它是非公平、可重入的。

每个对象有一个锁和一个等待队列,锁只能被一个线程持有,其他需要锁的线程需要阻塞等待。锁被释放后,对象会从队列中取出一个并唤醒,唤醒哪个线程是不确定的,不保证公平性。

8、CAS是什么

CAS全称是Compare And Swap,即比较再交换,CAS 操作涉及三个参数:

内存地址(V):要更新的变量所在的内存地址。

预期原值(A):当前线程认为该内存位置应该具有的值。

新值(B):如果内存位置的值等于预期值,则将其更新为新值。

CAS的工作流程:

读取当前值:线程首先读取内存位置 V 的当前值。

比较值:线程将读取到的值与预期值 A 进行比较。

更新值:如果当前值等于预期值 A,则将内存位置 V 更新为新值 B;否则,线程自旋,到下次循环才有可能机会执行。

9、CAS存在什么问题

自旋开销:当多个线程竞争同一个资源时,CAS 操作可能会频繁失败,导致线程不断重试(自旋)。这种忙等待会导致 CPU 资源浪费。

ABA问题:假设一个变量的值从 A 变成 B 再变回 A,CAS 操作可能无法检测到这种变化,从而导致错误的结果。

假设有三个线程 T1 和 T2 、T3对共享变量 V 进行操作:

  1. 初始时,V = A
  2. 线程 T1 读取 V 的值为 A,并准备将其更新为 B。
  3. 在 T1 更新之前,线程 T2 将 V 从 A 改为 B,线程 T3再将V改回 A。
  4. 当 T1 执行 CAS 操作时,发现 V 的值仍然是 A,于是成功将 V 更新为 B,但实际上中间发生了变化。

解决办法:给变量加一个版本号即可,在比较的时候不仅要比较当前变量的值,还需要比较当前变量的版本号。

 


http://www.kler.cn/a/592671.html

相关文章:

  • 2000-2016年各省地方财政营业税数据
  • 【人工智能】【Python】在Scikit-Learn中使用网格搜索对决策树调参
  • ROS合集(三)RTAB-Map + EuRoC 数据格式概述
  • 上取整,下取整,四舍五入
  • LS-NET-001-什么是承载网,核心网和接入网
  • 面试总结之 Glide自定义的三级缓存策略
  • 小程序开发与物联网技术的结合:未来趋势
  • 网络编程(客户端间通信)
  • 【2025】基于python+django的小区物业管理系统(源码、万字文档、图文修改、调试答疑)
  • 深入解析 TouchSocket 插件系统架构与实践
  • k8s--集群内的pod调用集群外的服务
  • 穿越是时空之门(java)
  • 《深度学习》—— YOLOv1
  • 突破时空边界:Java实时流处理中窗口操作与时间语义的深度重构
  • 汇编移位指令
  • BERT系列模型
  • 解决下载npm 缓存出现的问题
  • JAVA并发-volatile底层原理
  • opencv初步学习——图像处理2
  • Day67 | 灵神 | 二分查找:统计公平数对的数目