Java面试题
文章目录
-
-
- 1、抽象类和接口的区别
- 2、重载和重写的区别
- 3、==和equals的区别
- 4、讲一讲什么是map、set、list
- 5、HashMap原理
- 6、什么是 JSP?它与 Servlet 有什么区别
- 7、synchronized 关键字
- 8、HashTable与HashMap的区别
- 9、ArrayList和LinkedList的区别
- 10、Session 和 Cookie 的区别
- 11、String、StringBuffer、StringBuilder的区别
- 12、hashCode和equals
- 13、JSP 四种作用域
- 14、深拷贝和浅拷贝
- 15、Bean 的生命周期
- 16、什么是反射
- 17、你平时是怎么使用线程池的
- 18、进程和线程的区别,进程间如何通信
- 19、什么是线程上下文切换
- 20、什么是死锁
- 21、JDK8的新特性
- 22、栈和堆
- 23、内存溢出和内存泄漏
- 24、垃圾回收机制
- 25、线程池
- 26、**sleep() 和 wait() 有什么区别**
- 27、final, finally, finalize的区别
- 28、什么是索引
- 29、线程的生命周期
- 30、常见的索引类型
- 31、常见的索引结构
- 32、MySQL常见存储类型
- 33、依赖注入
- 34、MySQL为何使用 B+ 树而非 B 树
- 35、什么情况下不适合使用索引
- 36、SQL优化
- 37、MyISAM 和 InnoDB 的区别
- 38、事务的四个特性
- 39、事务可能产生的问题
- 40、事务的隔离级别
- 41、乐观锁和悲观锁
- 42、什么是范式
- 43、IOC
- 44、AOP
- 45、SpringMVC执行流程
- 46、springboot自动配置原理
- 47、Spring 中使用的设计模式
- 48、Spring常用注解
- 49、Redis的数据类型
- 50、缓存穿透
- 51、缓存击穿
- 52、缓存雪崩
- 53、Redis的优点是什么
- 54、RDB和AOF
- 55、主从复制
- 56、哨兵模式
- 57、Redis为什么这么快
- 58、Spring Boot 的启动过程
- 59、浏览器输入地址后做了什么
- 60、三次握手和四次挥手
- 61、Linux常用命令
- 62、Nginx常见的负载均衡策略方式
- 63、Nginx常用命令
- 64、JSP九大内置对象
- 65、分布式和微服务的区别
- 66、Servlet生命周期
- 67、CAP原则
- 68、BASE理论
- 69、什么是RPC
- 70、Dubbo支持哪些负载均衡策略
- 71、SpringCloud有哪些常用组件,作用是什么?
- 72、SpringCloud和Dubbo有哪些区别
- 73、Get和Post的请求的区别
- 74、什么是 Docker
- 75、如何设计数据库表中的索引
- 76、docker技术的三大核心概念
- 77、docker常用命令
- 78、Docker容器有几种状态
- 79、Nginx是什么
- 80、Git常用命令
- 81、常见的Vue指令
- 82、Vue组件的生命周期
- 83、Vue双向绑定
- 84、设计模式的分类
- 85、单例模式
- 86、工厂模式
- 87、观察者模式
- 88、代理模式
- 89、面试准备
-
1、抽象类和接口的区别
抽象类和接口都是面向对象编程的概念,用于实现多态性和组织代码。它们的主要区别在于:
- 定义方式:
- 抽象类:用
abstract
声明,可包含抽象方法和实例成员。 - 接口:用
interface
声明,仅包含抽象方法和常量。
- 抽象类:用
- 继承和实现:
- 抽象类:单继承,可提供部分实现。
- 接口:多实现,提供方法契约。
- 构造方法:
- 抽象类:可有构造方法。
- 接口:无构造方法。
- 成员类型:
- 抽象类:实例成员、抽象方法、具体方法。
- 接口:仅抽象方法和常量。
- 设计目的:
- 抽象类:共享代码和行为。
- 接口:定义契约、强调协作。
2、重载和重写的区别
重载(Overloading)和重写(Overriding)是面向对象编程中两个重要的概念,它们涉及方法的多态性和继承。
-
重载(Overloading):
- 重载指的是在同一个类中定义多个方法,它们具有相同的名称但参数列表不同(参数个数、类型或顺序)。重载方法可以有不同的返回类型,但不能仅仅通过返回类型的不同来区分。重载方法在编译时会根据传入的参数选择匹配的方法进行调用。
-
重写(Overriding):
- 重写是指在子类中实现一个与父类中具有相同名称、参数列表和返回类型的方法。重写方法必须有相同的方法签名。重写方法允许子类提供自己的实现,从而覆盖父类的方法。重写用于实现运行时多态性,即通过基类引用调用子类方法。
3、==和equals的区别
-
==
操作符:==
操作符用于比较两个对象的引用是否相同,即它们是否指向同一个内存地址。在基本数据类型(如int
、float
)的比较中,==
比较它们的值是否相等。
-
equals
方法:equals
方法是在Object
类中定义的,它的默认行为是比较两个对象的引用是否相同,即与==
的效果一样。然而,很多类会覆盖(override)equals
方法,以实现自己的对象比较逻辑。例如,String
、Integer
等类都重写了equals
方法,实现了基于内容的对象比较。
总结:
==
比较对象比较的是对象的引用(内存地址)是否相同,比较基本数据类型比较的是值是否相等。equals
方法通常比较对象内容是否相同,可以根据自己的逻辑进行重写。- 自定义类的
equals
方法重写时,一般需要同时重写hashCode
方法。
4、讲一讲什么是map、set、list
Map(映射):
- Map 是一种键值对的集合,每个键唯一地对应一个值。它通过键来快速查找对应的值,类似于字典或电话簿。在 Java 中,常见的 Map 实现类有 HashMap、LinkedHashMap、TreeMap 等。Map 是通过键来索引值,因此它可以用于建立键到值的映射关系,例如存储学生的学号和姓名。
Set(集合):
- Set 是一种不允许重复元素的集合,它可以用于存储一组唯一的元素。Set 不关心元素的顺序,只关心元素的唯一性。在 Java 中,常见的 Set 实现类有 HashSet、LinkedHashSet、TreeSet 等。Set 可以用于存储需要去重的数据,例如存储用户的唯一标识。
List(列表):
- List 是一种有序的集合,允许存储重复元素。它通过索引来访问元素,类似于数组。在 Java 中,常见的 List 实现类有 ArrayList、LinkedList、Vector 等。List 可以用于存储一组有序的数据,例如存储待办事项列表。
总结时,强调:
- Map 是键值对的集合,通过唯一的键来查找值。
- Set 是不允许重复元素的集合,用于存储唯一的元素。
- List 是有序集合,可以存储重复元素,通过索引来访问元素。
5、HashMap原理
- 存储方式: HashMap 以键值对(key-value)的形式存储数据。每个键对应一个值,可以通过键快速查找对应的值。
- 哈希表结构: HashMap 内部使用一个数组来存储数据,每个数组元素称为 “桶”(bucket)。每个桶可以存储一个或多个键值对。当添加一个键值对时,HashMap 会通过哈希函数计算键的哈希码,然后将键值对存放到对应的桶中。
- 哈希冲突: 不同的键可能会产生相同的哈希码,这就是哈希冲突。HashMap 使用数组+链表(Java 8 之前)或数组+红黑树(Java 8 及以后)来解决哈希冲突问题。当多个键映射到同一个桶时,它们会存储在链表或红黑树中,以便快速查找和插入。
- 哈希函数: 哈希函数是用于将键映射到桶的索引位置。HashMap 使用键的哈希码与桶数组长度取模来计算桶的索引。合适的哈希函数可以减少冲突,提高性能。
- 扩容和负载因子: 当存储的键值对数量超过一定阈值(负载因子)时,HashMap 会自动进行扩容,即创建一个更大的桶数组,并将原有的键值对重新分配到新的桶中。这可以保持哈希表的均匀分布,提高性能。
- 性能: HashMap 提供了常数时间复杂度的增、删、查操作(平均情况下),但在极端情况下,如哈希冲突严重时,性能可能会降低。通过合理选择哈希函数和负载因子,可以减少冲突,提高性能。
总结时,强调 HashMap 的基本原理是使用哈希表来实现键值对的存储,涉及哈希函数、解决冲突的机制以及自动扩容等。这些特性使得 HashMap 成为一种高效的数据结构,适用于快速查找和插入数据。
6、什么是 JSP?它与 Servlet 有什么区别
JSP(JavaServer Pages)是一种在 Java Web 应用中生成动态内容的技术。它允许我们在 HTML 页面中嵌入 Java 代码,以便在服务器端生成动态内容。与 Servlet 相比,JSP 更适合用于生成页面内容,因为我们可以在页面中直接编写 HTML,而不需要在 Java 代码中嵌入大量的 HTML。
区别在于:
- Servlet 是以 Java 代码为主,我们需要在 Java 类中编写 HTML 和逻辑代码。而 JSP 允许我们在 HTML 页面中插入 Java 代码,更适合于页面生成。
- JSP 最终会被编译成 Servlet 类,然后由容器执行。Servlet 是独立的 Java 类,不需要和 HTML 混合。
7、synchronized 关键字
synchronized
是 Java 中的一个关键字,用于实现线程的同步和互斥,以确保多个线程在访问共享资源时的安全性。它可以应用于方法或代码块,具体范围涵盖以下几个方面:
- 方法级别的同步: 当你将
synchronized
关键字应用于一个方法时,这个方法就被称为同步方法。这意味着一次只能有一个线程可以执行这个方法,其他线程需要等待直到这个方法执行完成。同步方法适用于需要保证整个方法的执行过程是原子性的情况。 - 代码块级别的同步: 你还可以使用
synchronized
来创建同步代码块。在同步代码块中,只有一个线程可以进入同步块内部,执行其中的代码,其他线程需要等待。同步代码块的作用是限制对指定的共享资源的访问,以保证多个线程之间不会产生并发问题。 - 对象锁和类锁: 在方法级别的同步中,当你将
synchronized
关键字应用于实例方法时,它使用的是对象锁(也称为实例锁)。这意味着同一对象的不同实例可以并发执行该方法。如果将synchronized
应用于静态方法,它使用的是类锁,确保同一类的不同实例之间的同步。 - 避免竞态条件: 竞态条件是多线程环境下出现的并发问题,可能导致数据不一致性和程序的不可预测行为。使用
synchronized
可以有效避免竞态条件,确保在同一时间只有一个线程可以访问共享资源。 - 互斥性和可见性:
synchronized
不仅提供了互斥性,即保证同一时间只有一个线程能够访问同步代码块或方法,还提供了可见性,即一个线程修改了共享资源的值,其他线程能够立即看到修改后的值。 - 性能考虑: 尽管
synchronized
可以确保线程安全,但过多地使用可能会引入性能问题。因为每次获取和释放锁都会有一定的开销,频繁的锁竞争可能导致性能下降。因此,在设计并发程序时,需要权衡锁的使用以及锁的粒度。
8、HashTable与HashMap的区别
线程安全性:
HashTable
是线程安全的,内部的操作都是同步的,这意味着多个线程可以同时访问和修改HashTable
实例,而不会导致数据不一致。然而,这种同步会影响性能。HashMap
默认情况下是非线程安全的。在多线程环境中,如果多个线程同时对HashMap
进行操作,可能会导致不可预测的结果。如果需要在多线程环境中使用,应该选择ConcurrentHashMap
。
性能:
- 由于
HashTable
内部使用同步来保证线程安全,性能通常会比较低下,特别是在高并发情况下。 HashMap
在没有同步的情况下性能更好,因为它不会为了线程安全而付出同步开销。但是在多线程环境中,必须采取额外的措施来保证线程安全。
空值(null):
HashTable
不允许键或值为null
,任何试图存储null
值的操作都会抛出异常。HashMap
允许一个键为null
,以及多个值为null
的情况。
迭代器:
HashTable
的迭代器是同步的,因此在遍历时不需要额外的同步机制。但迭代器本身也有性能开销。HashMap
的迭代器是不同步的,如果在迭代过程中对HashMap
进行修改,可能会抛出ConcurrentModificationException
异常。
总结时,强调 HashTable
是线程安全的、对空值敏感且性能较差的集合类,而 HashMap
是非线程安全、对空值更灵活且性能较好的集合类。在多线程环境下,应该优先考虑使用 ConcurrentHashMap
,而在单线程环境下,根据需求选择 HashTable
或 HashMap
。
9、ArrayList和LinkedList的区别
ArrayList:
- 内部实现:
ArrayList
使用动态数组来存储元素。当元素数量超过当前数组容量时,会自动进行扩容。 - 随机访问:由于底层使用数组,
ArrayList
支持通过索引来随机访问元素,因此在随机访问时具有较好的性能。 - 插入和删除:在列表末尾进行插入和删除操作是高效的,因为只需要调整数组的长度。但在中间插入或删除元素时,需要进行数组元素的移动,可能会导致性能下降。
LinkedList:
- 内部实现:
LinkedList
使用双向链表来存储元素。每个元素都包含指向前一个和后一个元素的引用。 - 随机访问:由于需要从头部或尾部开始遍历链表,
LinkedList
的随机访问性能较差。 - 插入和删除:在链表中插入和删除元素时,由于只需要调整相邻节点的引用,所以在中间插入和删除操作时比
ArrayList
更高效。 - 内存占用:由于每个元素都需要存储前后引用,
LinkedList
的内存占用通常会比ArrayList
更高。
总结时,强调:
ArrayList
适用于随机访问较多、插入和删除操作较少的场景。LinkedList
适用于频繁的插入和删除操作,但不太适合需要随机访问的场景。
10、Session 和 Cookie 的区别
- 存储位置:
Cookie
:存储在客户端浏览器的小文本文件中。Session
:存储在服务器端的内存或持久化存储中。
- 存储容量:
Cookie
:通常每个域名下的浏览器可以存储多个Cookie
,但每个Cookie
的存储容量有限制(通常为几KB)。Session
:在服务器端存储,通常没有存储容量限制,但过多的Session
数据会占用服务器内存。
- 安全性:
Cookie
:存储在客户端,因此容易受到信息泄露和篡改的风险。可以通过设置HttpOnly
和Secure
属性来增加安全性。Session
:存储在服务器端,相对来说更安全,但仍然需要注意防止Session
劫持等风险。
- 跨域支持:
Cookie
:可以设置为跨域共享,但受到同源策略的限制。Session
:不易在不同域名之间共享,因为存储在服务器端。
- 失效时间:
Cookie
:可以设置失效时间,可以是会话级(浏览器关闭后失效)或持久性的。Session
:通常在用户关闭浏览器或超过一定时间没有活动后失效。
- 用途:
Cookie
:常用于存储少量临时数据,如用户偏好设置、购物车信息等。Session
:通常用于存储用户的登录状态、购物车内容、用户数据等。
11、String、StringBuffer、StringBuilder的区别
String:
- 不可变性:
String
是不可变的,一旦创建就不能被修改。每次对String
进行修改操作时,实际上是创建了一个新的String
对象,旧的对象会被丢弃。这会导致频繁的对象创建和销毁,影响性能。 - 线程安全:由于不可变性,
String
在多线程环境下是线程安全的。多个线程可以同时共享一个String
实例,不会出现并发问题。
StringBuffer:
- 可变性:
StringBuffer
是可变的,允许修改已经存在的字符序列,而不会创建新的对象。这样可以减少对象的创建和销毁,提高性能。 - 线程安全:
StringBuffer
是线程安全的,内部的操作都是同步的。这意味着多个线程可以同时访问和修改StringBuffer
实例,而不会导致数据不一致。但这种同步会影响性能。
StringBuilder:
- 可变性:
StringBuilder
也是可变的,与StringBuffer
类似,允许修改已经存在的字符序列,而不会创建新的对象。但与StringBuffer
不同,StringBuilder
并不是线程安全的。 - 线程安全:
StringBuilder
在多线程环境下是不安全的,不同线程同时访问和修改同一个StringBuilder
实例可能会导致数据不一致。因此,StringBuilder
更适用于单线程环境下的操作,它不会带来额外的同步开销。
总结时,强调:
String
适用于不需要频繁修改的字符串,但会导致对象创建和销毁。StringBuffer
适用于多线程环境下需要频繁修改的字符串。StringBuilder
适用于单线程环境下需要频繁修改的字符串,可以提供更好的性能。
12、hashCode和equals
hashCode:
- 作用:
hashCode
是一个方法,用于计算对象的哈希码值。哈希码是一个整数,通常用于在哈希表等数据结构中确定对象在内存中的存储位置,从而加速查找操作。 - 特点:相同的对象调用
hashCode
方法应该返回相同的哈希码值,但不同的对象可能会返回相同的哈希码值(哈希冲突)。因此,哈希码不能作为确定对象相等性的绝对标准。 - 实现:为了保证哈希码的正确性,如果
equals
方法返回true
,则两个对象的hashCode
方法应该返回相同的值。但是反过来不一定成立:hashCode
相同并不意味着两个对象一定相等。
equals:
- 作用:
equals
是一个方法,用于比较对象的内容是否相等。默认情况下,equals
方法会比较对象的引用是否相等,即是否指向同一个内存地址。 - 特点:
equals
应该满足以下特点:- 自反性:
x.equals(x)
应该返回true
。 - 对称性:如果
x.equals(y)
返回true
,那么y.equals(x)
也应该返回true
。 - 传递性:如果
x.equals(y)
和y.equals(z)
都返回true
,那么x.equals(z)
也应该返回true
。 - 一致性:多次调用
x.equals(y)
应该返回相同的结果,只要对象没有被修改。 - 对
null
的处理:x.equals(null)
应该返回false
。
- 自反性:
- 实现:如果重写了
equals
方法,通常也需要重写hashCode
方法,以确保两个方法的一致性。当两个对象通过equals
方法比较为相等时,它们的hashCode
方法应该返回相同的值。
在面试中,强调 hashCode
用于快速查找对象的存储位置,而 equals
用于比较对象的内容是否相等。重写这两个方法时,应该遵循它们的约定和特性。
13、JSP 四种作用域
- Page 作用域: 页面作用域是最小的作用域,其数据只在当前 JSP 页面中有效。数据存储在
pageContext
对象中,通过${pageScope}
或${pageScope.variable}
访问。适用于在同一个页面的不同部分共享数据,但不适用于多个请求之间的数据共享。 - Request 作用域: 请求作用域的数据在同一个请求中有效,不同的页面间无法共享。数据存储在
request
对象中,通过${requestScope}
或${requestScope.variable}
访问。适用于在同一个请求中的不同页面之间传递数据。 - Session 作用域: 会话作用域的数据在用户的整个会话期间有效,即用户在浏览器关闭之前都可以访问。数据存储在
session
对象中,通过${sessionScope}
或${sessionScope.variable}
访问。适用于需要在用户会话期间保持状态的数据,如登录状态、购物车内容等。 - Application 作用域: 应用程序作用域的数据在整个应用程序的生命周期内都有效,所有用户都可以访问。数据存储在
application
对象中,通过${applicationScope}
或${applicationScope.variable}
访问。适用于共享应用程序级别的数据,如全局配置信息、计数器等。
14、深拷贝和浅拷贝
浅拷贝:
- 复制引用:浅拷贝创建一个新对象,然后将原始对象的成员变量的引用复制到新对象中。新旧对象共享同一份数据。
- 嵌套对象:如果原始对象中包含其他对象(如数组、集合、对象等),浅拷贝只会复制它们的引用,而不会创建新的嵌套对象。
- 修改影响:因为新旧对象共享同一份数据,当一个对象修改了共享的数据时,另一个对象也会受到影响。
深拷贝:
- 复制数据:深拷贝创建一个新对象,并递归地复制原始对象及其嵌套对象的数据,而不是简单的复制引用。
- 独立对象:深拷贝会为新对象及其嵌套对象创建独立的副本,使得新旧对象之间的数据彼此独立。
- 修改不影响:因为新旧对象的数据是独立的,一个对象的修改不会影响另一个对象。
15、Bean 的生命周期
- 实例化: 首先,Spring 容器根据配置或注解,在内存中创建 Bean 的实例。这个阶段只是创建了一个 Java 对象,还没有进行任何属性的赋值。
- 属性赋值: 在实例化后,Spring 容器会根据配置文件或注解,将相应的属性值注入到 Bean 实例中。这可以通过构造函数注入、Setter 方法注入或字段注入来实现。
- 初始化: 在属性注入完成后,如果 Bean 类实现了
InitializingBean
接口,或者在配置文件中声明了初始化方法,Spring 将会调用 Bean 的初始化方法。这里可以进行一些初始化操作,例如建立数据库连接、加载资源等。 - 使用: 当 Bean 初始化完成后,它可以被其他组件引用,进行实际的业务逻辑处理。
- 销毁: 当 Spring 容器关闭时,如果 Bean 类实现了
DisposableBean
接口,或者在配置文件中声明了销毁方法,Spring 将会调用 Bean 的销毁方法。在这里可以进行一些资源的释放和清理工作,例如关闭数据库连接、释放文件资源等。
需要注意的是,虽然 Spring 容器会管理 Bean 的生命周期,但单个 Bean 的具体生命周期可能会因为不同的配置或注解而有所不同。在 Spring 容器启动和关闭过程中,会根据不同的阶段调用相应的方法,使得开发者能够在适当的时机进行初始化和销毁操作。
16、什么是反射
基本概念:
- 获取类信息:反射允许您在运行时获取类的信息,包括类名、父类、接口、构造函数、方法、字段等。
- 操作对象:通过反射,您可以在运行时创建对象、调用对象的方法、获取和设置对象的字段。
优点:
- 灵活性:反射使您可以在运行时动态地加载和使用类,而不需要在编译时硬编码类名。
- 泛化工具:许多框架和工具(如各种注解、依赖注入框架等)使用反射来实现通用和可配置的行为。
缺点:
- 性能开销:反射通常会比直接调用代码更慢,因为它涉及到动态查找和解析类、方法等信息。
- 类型安全:由于反射是动态的,编译器无法在编译时捕获所有可能的错误,可能导致在运行时出现类型错误。
应用场景:
-
要操作权限不够的类属性和方法时
-
实现自定义注解时
-
动态加载第三方jar包时
-
按需加载类
-
节省编译和初始化时间
获取class对象的方法:
- class.forName(类路径)
- 类.class()
- 对象的getClass()
17、你平时是怎么使用线程池的
- 任务类型和线程池选择:首先,我会根据任务的性质选择合适的线程池类型。例如,对于需要按序执行的任务,我会选择
SingleThreadExecutor
,对于大量短期任务,我会选用CachedThreadPool
。如果有固定数量的长期任务,我会选择FixedThreadPool
。 - 线程池大小设置:我会根据系统的硬件配置、任务的性质以及预期的并发量来调整线程池的大小。如果线程数量过多,可能会导致资源竞争和性能下降,如果线程数量过少,可能会导致任务等待过长时间。
- 任务提交:在将任务提交到线程池之前,我会确保任务的粒度适当,不会过大或过小。然后,我会使用线程池的
submit
或execute
方法将任务提交到线程池中。 - 处理异常:在任务执行过程中,可能会出现异常。我会在任务中捕获异常,然后根据需要进行日志记录、重试或者其他处理。
- 任务完成处理:一旦任务完成,线程池会自动将线程放回池中,我会确保在任务执行结束后释放资源,以避免潜在的资源泄漏问题。
- 监控与调优:我会通过监控线程池的状态,例如线程池大小、活动线程数、任务队列长度等,来确保线程池的健康运行。如果出现性能问题,我会根据情况考虑调整线程池的参数。
- 合理关闭线程池:当我不再需要线程池时,我会调用线程池的
shutdown
或shutdownNow
方法来合理关闭线程池,确保所有线程能够完成任务并停止。
通过合理的线程池使用,我能够优化系统的资源利用率,提高任务执行效率,并确保多线程编程的稳定性和可维护性。
18、进程和线程的区别,进程间如何通信
-
进程和线程的区别:
- 进程:进程是程序的一次执行,是一个独立的执行单元,拥有独立的内存空间和系统资源。不同进程之间相互独立,通信需要使用特定的机制。
- 线程:线程是进程内的执行单元,多个线程共享同一进程的内存空间和资源。线程之间的切换更加轻量级,但也更容易出现并发问题。
-
进程间通信:
进程间通信(Inter-Process Communication,IPC)是指不同进程之间进行数据交换和通信的机制。常见的进程间通信方式包括:
- 管道和命名管道:用于有亲缘关系的进程间通信,一般是父子进程或兄弟进程。
- 消息队列:通过消息队列传递消息,可以实现多个进程之间的通信。
- 共享内存:多个进程共享一段内存区域,从而实现数据共享。
- 套接字(Socket):适用于不同主机上的进程通信,可以实现跨网络的通信。
- 信号量和锁:用于控制多个进程对共享资源的访问,以避免竞争条件。
- RPC(远程过程调用):允许进程在远程执行另一个进程的函数,实现远程通信。
这些机制都有自己的特点和适用场景,具体选择取决于需要实现的功能和需求。
总结时,您可以强调:
- 进程是独立的执行单元,线程是进程内的执行单元。
- 进程间通信是为了实现不同进程之间的数据交换和通信,有多种机制可供选择,取决于需求。
19、什么是线程上下文切换
线程上下文切换是在多线程环境下,CPU 从一个线程切换到另一个线程时所涉及的过程。在这个过程中,操作系统保存当前线程的状态(也称为上下文),然后加载另一个线程的状态,以便让它继续执行。
这个过程涉及到以下几个步骤:
- 保存当前线程状态:操作系统会保存当前线程的寄存器状态、程序计数器值、堆栈指针等信息,以便在切换回来时能够恢复执行。
- 加载新线程状态:操作系统会从另一个线程的上下文中恢复寄存器状态、程序计数器值、堆栈指针等信息。
- 切换执行:一旦新线程的状态被加载,CPU 会开始执行新线程的代码,继续运行。
线程上下文切换是一个开销较大的操作,因为涉及到保存和恢复线程状态,而且会消耗一定的处理器时间。在多线程程序中,频繁的上下文切换可能会影响程序的性能,特别是在使用多核 CPU 时。