| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 多线程面试八股大总结 -> 正文阅读 |
|
[Java知识库]多线程面试八股大总结 |
目录 ?9)JUC(java.util.concurrent)的常见类 一、如何保证线程安全?1、使用没有共享资源的模型 2、使用共享资源只读不写的模型 1)不需要写共享资源 2)使用不可变对象 3、直面线程安全(重点) ? ? ? ? 1)保证原子性 ? ? ? ? 2)保证顺序 ? ? ? ? 3)? ?保证内存可见性 二、线程 vs 进程1、线程的优点1)创建一个新的线程的代价要比创建一个新的进程小 2)与进程间切换相比,线程间的切换需要操作系统做的操作小得多 3)线程占用资源比进程少 4)能充分利用多处理器的可并行数量 5)在等待 I/O 操作结束的同时,程序可以执行其他任务 6)计算密集型应用,将计算分解到多个线程中实现 2、进程和线程的区别1)进程是系统进行资源分配的最小单位,线程是程序执行的最小单位 2)进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈 3)由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行通信 4)线程的创建,切换及终止效率更高 三、常见的锁策略1、乐观锁 vs 悲观锁悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都上锁,这样别人想拿到这个数据就会阻塞直到拿到锁 乐观锁: 假设数据一般情况下不会发生冲突,所以在数据提交更新的时候,才会正式对数据是否产生并发进行检测,如果发现并冲突了,则让返回用户错误的信息,让用户决定处理 synchronized 初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换悲观锁策略 ?乐观锁的一个重要功能?就是要检测出数据是否发生冲突,我们可以引入一个”版本号“来解决 ? 2、读写锁多线程之间,数据的读取方式之间不会产生线程安全问题,但数据的写入方式互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗,所以读写锁因此而生。 读写锁(readers-writer lock) 在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。 一个线程对于数据的访问,主要存在两种操作:读数据和写数据 1)两个线程都只是读一个数据,此时并没有线程安全问题,直接并发的读取即可 2)两个线程都要写一个数据,此时会出现线程安全问题 3)一个线程读另外一个线程写,也有线程安全问题 读写锁就是把读操作和写操作区分对待。JAVA 标准库中提供了 ReentrantReadWriteLock 类,实现了读写锁 其中, ?读写锁 特别适合于”频繁读,不频繁写“的场景中 比如一个教务系统 synchronized 不是读写锁 3、重量级锁 vs 轻量级锁锁的核心特性”原子性“,这样的机制追根溯源是 CPU 这样的硬件设备提供的 1)CPU 提供了”原子操作指令“ 2)操作系统基于CPU的原子指令,实现了 mutex 互斥锁 3)JVM基于操作系统提供的互斥锁,实现了 synchronized 和 ReentrantLock 等关键字和类 ? 重量级锁:加锁机制重度依赖 OS 提供的 mutex ?1)大量的内核态用户态切换 2)很容易引发线程的调度 轻量级锁:加锁机制尽可能不用 mutex,而是尽量在用户态代码完成,实在搞不定再用mutex 1)少量的内核态用户态切换 2)不太容易引发线程调度 4、自旋锁(Spin Lock)?按之前的方式,线程在抢锁失败后进入阻塞状态,放弃CPU,需要很久才能被再次调度 但实际上,大部分情况下,虽然当前抢锁失败,但过不了多久,锁就会被释放。没必要放弃这个CPU,这个时候就可以使用自旋锁来处理这样的问题 自旋锁伪代码: while (抢锁(lock)== 失败) {} 如果获取锁失败,立即再尝试获取锁,无限循环,直到获取锁为止,第一次获取锁失败,第二次的尝试会在极短的时间内到来 一旦锁被其他线程释放,就能第一时间获取到锁, 自旋锁是一种典型的 轻量级锁 的实现方式 优点: 没有放弃CPU,不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁 缺点:如果锁被其他线程持有的时间比较久,那么就会持续的消耗CPU资源(而挂起等待的时候是不消耗CPU的) synchronized 中的轻量级锁策略大概率是通过自旋锁的方式实现的 5、公平锁 vs 非公平锁?1) 操作系统内部的线程调度可以视为随机的,如果不做任何额外的限制,锁就是非公平锁,如果要想实现公平锁,就需要依赖额外的数据结构,来记录线程们的先后顺序 2)公平锁和非公平锁没有好坏之分,关键看适用场景 Synchronized 是非公平锁 6、可重入锁 vs 不可重入锁可重入锁的字面意思是”可以重新进入的锁“,即允许同一个线程多次获取同一把锁。 比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫递归锁) Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的 而LInux提供的mutex是不可重入锁 面试葵花宝典1、你是怎么理解乐观锁和悲观锁的,具体怎么实现?2、介绍下读写锁?3、什么是自旋锁,为什么要使用自旋锁策略,缺点是什么?4、synchronized 是可重入锁么?5、CAS?1)什么是CAS?CAS:全称Compare and swap,字面意思:”比较并交换:,一个CAS涉及到以下操作: ?CAS伪代码 下面写的代码不是原子的,真实的 CAS 是一个原子的硬件指令完成的,这个伪代码只是辅助理解CAS 的工作流程 两种典型的不是“原子性”的代码 1)check and set (if 判定然后设定值) 2)read and update(i++) 当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号 CAS 可以视为一种乐观锁(或者理解成 CAS 是乐观锁的一种实现方式) 2)CAS 是怎么实现的?针对不同的操作系统,JVM用到了不同的CAS实现原理,简单来讲: 1)java 的 CAS 利用的是 unsafe 这个类提供的 CAS 操作 2)unsafe 的CAS 依赖了的是 JVM 针对不同的操作系统实现的 Atomic 简而言之,硬件给予支持,软件层面才能做到 3)CAS有哪些应用?1)实现原子类 这里边的 getAndIncrement 相当于 i++ 操作? 伪代码实现: ? ? 2)实现自旋锁 基于 CAS 实现更灵活的锁,获取到更多的控制权 4)CAS 的 ABA 问题1)什么是 ABA 问题? ABA 问题: 假设存在两个线程 t1 和 t2 ,有一个共享变量 num,初始值为 A 接下来,线程 t1 想使用 CAS 把值改为 Z,那么就需要 1、先读取 num 的值,记录到 oldNum 变量中 2、使用 CAS 判定当前 num 的值是否为 A,如果为 A,就修改为 Z ?2)ABA 问题引来的 BUG 大部分情况下,t2 线程这样的一个反复横跳改动,对于 t1 是否修改 num,是没有影响的,但是不排除一些特殊情况 3)解决 ABA 问题的方案 给要修改的值,引入版本号,在 CAS? 比较数据当前值和旧值之前,也要比较版本号是否符合预期。 ? 5)高频面试题1)讲解下你自己理解的 CAS 原理 CAS 全称 Compare and swap ,“比较并交换”,相当于通过一个原子的操作,同时完成“读取内存,比较是否相等,修改内存”这三个步骤,本质上需要 CPU 指令的支撑 2)ABA 问题怎么解决? 给需要修改的数据引入版本号,在 CAS 比较数据当前值和旧值的同时,也要比较版本号是否符合预期,如果发现版本号一致就真正执行修改操作,并且让版本号自增。如果发现版本号不一致(数据一定被修改过),操作失败。 6)Synchronized 原理1) 基本特点 ?结合上面的锁策略,我们可以总结出,Synchronized 具有以下特性 1、开始是乐观锁,如果频繁的锁冲突,就转换为悲观锁 2、开始是轻量级锁,如果锁被持有的时间过长,就转换为重量级锁 3、实现轻量级锁的时候大概率用到的自旋锁策略 4、是一种不公平锁 5、是一种可重入锁 6、不是读写锁 2)加锁工作过程 JVM 将 synchronized 锁分为 无锁,偏向锁,轻量级锁,重量级锁状态。会根据情况,依次进行升级 7) 偏向锁 -->? 轻量级锁 --> 重量级锁1、偏向锁 第一个尝试加锁的过程,优先进入偏向锁状态 2、轻量级锁 随着其他线程进入竞争,偏向锁状态被消除,进入轻量级锁状态(自适应的自旋锁) 此处的轻量级锁就是通过 CAS 来实现 3、重量级锁 如果竞争进一步激烈,自旋不能快速获取到锁的状态,就会膨胀为重量级锁 此处的重量级锁就是指内核提供的 mutex 7)锁消除 ?锁粗化?1、锁消除 编辑器 + JVM 判断锁是否可消除,如果可以,就直接消除 2、锁粗化? ?一段逻辑中如果多次出现加锁解锁,编译器 + JVM 会自动进行锁的粗化 由此可见,synchronized 的策略是比较复杂的,在背后做了很多事情,目的为了让程序员哪怕什么也不懂,也不至于写出特别慢的程序 8)Callable 接口1、Callable 的用法 Callable 是一个 interface,相当于把线程封装了一个“返回值”,方便程序员借助多线程的方式计算结果 ?
? 相关面试题 ?9)JUC(java.util.concurrent)的常见类ReentrantLock 可重入互斥锁,和 synchronized 定位类似,都是用来实现互斥效果,保证线程安全 ReentrantLock 和 synchronized 的区别 ?如何选择使用那个锁? 10)原子类?原子类内部用的是 CAS 实现,所以性能要比加锁实现 i++ 高很多。原子类有以下几个: 11)线程池虽然创建销毁线程比创建销毁进程更轻量,但是在频繁创建销毁线程的时候还是会比较低效 线程池就是为了解决频繁创建销毁线程的问题,如果某个线程不再使用了,并不是真正把线程释放,而是放到一个“池子”中,下次如果需要用到线程就直接从池子中取,不必通过系统来创建。 ? ? 12)信号量信号量,可以用来表示“可用资源的个数”,本质上就是一个计数器 ?
?13)CountDownLatch同时等待 N 个任务执行结束 14)相关面试题1、线程同步的方式 synochronized , ReentrantLock ,Semaphore 等都可以用于线程同步 2、为什么有了 synchronized 还需要 juc 下的 lock? 3、AtomicInteger 的实现原理是什么? ? 4、信号量听说过?之前都用在那些场景下? ? 5、解释一下 ThreadPoolExecutor 构造方法的参数含义 参考: ?15)线程安全的集合类原来的集合类,大部分都不是线程安全的 16)多线程环境使用 ArrayList?1、自己使用同步机制 (synchronized 或者 ReentrantLock) 2、Collections.synchronizedList(new ArrayList); ?3、使用 CopyOnWriteArrayList ?17)多线程环境使用队列18)多线程环境使用哈希表HashMap 本身不是线程安全的 ? ? 19)相关面试题1、ConcurrentHashMap 的读是否要加锁,为什么? 读操作没有加锁,目的是为了进一步降低锁冲突的概率,为了保证读到刚修改的数据,搭配了 volatile 关键字 2、介绍下 ConcurrentHashMap 的锁分段技术? 3、ConcurrentHashMap 在jdk1.8 做了那些优化? ?4、Hashtable 和 HashMap ,ConcurrentHashMap 之间的区别? 20)死锁??1、死锁是什么? 死锁的情形:多个线程同时被阻塞,他们中的一个或多个全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止 ? ?死锁是一种很严重地 BUG !导致一个程序卡死,无法正常工作 21)如何避免死锁?死锁产生地四个必要条件: 1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用 2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放 3、请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源地占有 4、循环等待:即存在一个等待队列:p1 占有 p2 的资源, p2 占有 p3 的资源,p3 占有 p1 的资源,这样就形成了一个等待环路。 ? ?破坏循环等待 最常用的一种死锁阻止技术就是”锁排序“,假设有 N 个线程尝试 同时获取 M 把锁,就可以针对 M 把锁进行编号(1,2,3....M) N 个线程尝试获取锁的时候,都按照固定的编号由小到大顺序来获取锁,这样就可以避免环路等待 22)其他常见问题1、谈谈 volatile 关键字的用法? ?2、java多线程如何实现数据共享? 3、java 创建线程池的接口是什么?参数 LinkedBlockingQueue 的作用是什么? 4、java 线程共有几种状态?状态之间如何切换? 5、在多线程下,如果对一个数进行叠加,该怎么做? 6、Servlet 是否是线程安全的? ?7、Thread 和 Runable 的区别和联系? ?8、多次 start 一个线程会怎么样? 9、有 synchronized 两个方法,两个线程分别同时用这个方法,请问会发生什么? 10、进程和线程的区别? |
|
|
上一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 19:22:29- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |