2023-5-2面试题学习
1、内存的可见性你了解吗,讲述一下?
内存可见性是指多个线程访问同一共享变量时,在一个线程修改了该变量值后,下一个线程能立即看到这种变化的能力。
如果一个变量在多个线程间共享,那么为了避免出现数据不一致的情况,线程执行时会把变量存储在各自的CPU缓存中,而不是直接从内存中获取,这个时候可能会导致可见性问题,因为一个线程修改了变量的值,但是另一个线程并不知道这个变量的值被修改了,导致数据不一致的情况。
为了解决这个问题,在Java中提供了volatile关键字来保证内存可见性。如果一个变量被声明为volatile,那么每次访问这个变量都会直接从内存中读取,并且每次修改也会立刻写入到内存中。这确保了所有的线程都能看到变量的最新值,从而避免出现内存可见性问题。
也可以进行加锁synchronized或者lock锁解决这个问题。
2、 讲一下面向对象的三大特性,并说出自己理解
面向对象的三大特性是:继承、封装和多态。
封装:是把成员方法和成员变量都放在一个类中,其目的是隐藏内部的细节,防止外部干扰和误操作,提高代码的安全性。
继承:指的是通过继承已经存在的类所拥有的成员变量和方法,而形成新的类。可以提高代码的复用性。
多态:指的是对同一种操作(也可以说是方法),不同对象可以进行不同的行为。
3、C++多态的原理
C++中的多态有两种方式:运行时多态和编译时多态。
运行时多态是通过虚函数来实现的,当一个类定义了虚函数,它的子类如果没有重写该虚函数,则默认使用父类的实现,当父类指针指向子类对象的时候,
Animal a = new Dog();
调用该虚函数会按照子类实现来执行,从而实现多态特性。
虚函数表是一个类的静态成员,它存储了该类所有虚函数的地址。当一个类被实例化时,会
在其对象中生成一个虚函数指针指向该类的虚函数表,这样就可以在运行时动态地确定调用的虚函数。
举个例子,假设有一个基类Animal和它的两个派生类Dog和Cat,其中Animal定义了一个虚函数makeSound()
,而Dog和Cat分别实现了该虚函数。那么,在编译时,编译器会为Animal、Dog和Cat中的虚函数makeSound()
生成一个相应的虚函数表,并将其存储在静态内存中。
当创建一个Animal类型的对象时,会在对象中生成一个虚函数指针vptr,该指针指向Animal的虚函数表。
同样地,当创建一个Dog类型或Cat类型的对象时,也会在对象中生成一个虚函数指针vptr,只不过该指
针分别指向Dog和Cat的虚函数表。
当程序调用某个对象的虚函数makeSound()时,实际上是通过该对象的虚函数指针vptr去访问该对象所属类的
虚函数表,从而找到该虚函数的地址,最终调用该虚函数。这种方式可以在程序运行时动态地确定调用的函
数,实现了多态性。
总结一下,虚函数表的工作原理可以简单归纳为:在程序运行时动态确定虚函数的地址,并通过该地址调用
正确的虚函数,实现多态性。
4 进程和线程的区别是什么?
我们将计算机系统看成一个工厂,那么CPU就是工人,以单核CPU为例,那么这个工厂只有一个工人。
进程就是这个工厂的产品生成线,我们可以有产品A线,B线等等。
线程就是产品线上的几道工序。
那么进程要工作的时候,需要有工人(也就是CPU)来处理工序(线程),同时只有工序是不够的,肯定需要有原料等输入,然后对原料进行加工。
这也就解释了进程是资源分配和调度的基本单位,而线程是CPU执行的基本单位。而且线程在进程上执行,那么多个线程就共享进程的资源。
5 记录对字节跳动面试的学习
字节跳动后端面经分享——从一面到HR面 - 知乎 (zhihu.com)
- 自我介绍
省略1分钟...
select、poll、epoll?
深入浅出理解select、poll、epoll的实现 - 知乎 (zhihu.com)
这三个都是多路复用方面的技术。多路复用指的是:复用一个线程处理多个socket。
1.1 select
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。当用户系统调用select时,select会将需要监控的readfds集合拷贝到内核空间(假设监控的仅仅是socket读),然后遍历自己监控的socket sk。如果发现某些sk是可以读,然后将可读socket的个数返回给用户。
poll和select很类似,都需要将监控集合拷贝到内核空间,造成性能问题。
1.2 epoll
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率
epoll采用三个函数:epoll_create,epoll_ctl,epoll_wait,来做上述select的事情,监控文件描述符,当文件描述符有事件时,向用户返回事件。
- epoll_create:创建一个epoll结构体(红黑树+链表),返回其句柄
- epoll_ctl:向 epoll 对象中添加/修改/删除要管理的连接
- epoll_wait:等待其管理的连接上的 IO 事件
epoll_create:创建一个红黑树,和一个就绪描述符链表。
epoll_ctl:epoll的事件注册函数,它不同于select,是想红黑树上面祖册要监听的事件类型。
epoll_wait:等待事件的产生,检查就绪描述符链表,是否为空不为空则返回就绪链表中FD的个数
1.3 epoll的两种触发模式?
- level 模式:该模式就是只要还有没有处理的事件就会一直通知
- edge 模式:该模式是当状态发生变化时才会通知
2 TCP三次握手过程,有什么状态,状态机如何变化?
客户端从:closed --> syn_sent-->established
服务器从:listen --> syn_revd-->established
四次挥手:
客户端:established-->final_wait-->time_wait-->closed
服务端:established-->close_wait-->last_ack-->closed
3 什么是 TIME_WAIT 状态,为什么需要 TIME_WAIT 状态?时间是多久?
2MSL时间是从客户端接收到FIN后发送ACK开始计时的。如果在这个时间段内,服务器没有收到ACK应答报文段,会重发FIN报文段,如果客户端收到了FIN报文段,那么2MSL的时间将会被重置。如果在2MSL时间段内,没有收到任何数据报,客户端则会进入CLOSE状态。
4 Linux 中一个进程的虚拟内存分布长什么样?
只读数据段(rodata):const修饰的全局变量是存放在常量段的
代码段:存放代码
数据段:存放全局变量和静态变量(已经初始化和未初始化的)
堆:动态内存的分配
内存映射段:常被用来加载共享库(动态库)
栈:存放函数中局部变量
5 为什么要用虚拟内存?
- 将主存当作辅存的高速缓存,经常活动的东西放在主存中,就像 GTA5 几十 GB 大的东西都放主存中是放不下的,因此可以高效利用主存。
- 每个进程地址空间都一样,方便管理
- 进程间的隔离,避免进程破坏其他进程的地址空间
6 虚拟地址映射为物理地址的过程?
cpu获得虚拟内存,然后交给MMU(一般是通过页表进行转换)进行地址翻译成真实的物理内存。 TLB与MMU_mmu tlb_wagsyang的博客-CSDN博客
7 使用线程有哪些好处与坏处?
好处:上下文切换代价小,通信方便
坏处:注意死锁,注意对共享资源的访问。
8 进程有哪些同步的机制?
临界区、互斥、信号量、事件
9 什么是稳定排序?
利用关键词排序后,关键词相同的元素之间的相互顺序不变的排序算法
10 手撕
189.数组循环右移。将一个长度为 n 的数组,循环右移 k 位,要求时间复杂度为 O(n) 空间复杂度为 O(1) 。
思路:先整体reverse,然后前k位reverse,然后后n-k位reverse。
class Solution {
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
public void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start += 1;
end -= 1;
}
}
}