字节面经总结

java基础(回答内容仅供参考)

1.用户自己写一个String类会发生什?
用户自定义的String类写了也没用,因加载某个类时,优先使用父类加载器加载需要使用的类。如果我们自定义了java.lang.String这个类,
加载该自定义的String类,该自定义String类使用的加载器是AppClassLoader,根据优先使用父类加载器原理,
AppClassLoader加载器的父类为ExtClassLoader,所以这时加载String使用的类加载器是ExtClassLoader,
但是类加载器ExtClassLoader在jre/lib/ext目录下没有找到String.class类。然后使用ExtClassLoader父类的加载器BootStrap,
父类加载器BootStrap在JRE/lib目录的rt.jar找到了String.class,将其加载到内存中。这就是类加载器的委托机制。
2.sleep()和wait()的区别
这两个方法来自不同的类分别是Thread和Object
最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法(锁代码块和方法锁)。
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

3. Object类⾥有哪些⽅法?
1.getClass方法

获得运行时类的类型。

2.hashcode方法

该方法用来返回其所在对象的物理地址(哈希码值),常会和equals方法同时重写

3.equals 方法

默认情况下(继承自Object类),equals和==是一样的(都是比较的对象的地址值)。但子类一般都要重写这个方法。(重写后一般是比较对象的属性,也就是值是否相等)

4.clone方法

是用来另存一个当前存在的对象,保护方法的

5.toString方法

toString()方法用于返回表示对象值的字符串(返回的是String对象)。

6.notify 方法

唤醒在此对象监视器上等待的单个线程

7.notifyAll 方法

唤醒在此对象监视器上等待的所有线程。

  1. wait 方法

导致当前的线程等待(使当前线程等待该对象的锁),直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。

9.finalize方法

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用
4. 讲⼀下equals()与hashcode(),什么时候重写,为什么重写,怎么重写?
去看别人的博客吧,写的很好。
**5.Java多态,如何实现?动态绑定 **
铺垫:
静态绑定和动态绑定的区别:
①静态绑定在编译时期,动态绑定在运行时期。

②静态绑定只用到类型信息,方法的解析根据引用变量的类型决定,而动态绑定则根据实际引用的的对象决定

③在java中,private static 和 final 方法都是静态绑定,只有虚方法才是动态绑定

④多态是通过动态绑定实现的。
动态绑定的实现:一个对象的多态方法的地址将被存储在该对象的方法表(method table)里面。在运行时期,调用多态方法的时候,JVM会在此表中搜索方法的名字,从而获取方法的地址。方法表里包含方法的名字和对应的地址(注意,这个地址是动态绑定的)。这个方法表对所有属于这个类的对象而言,都是一样的,所以它会存储在Class对象中(这里对象类型以Integer为例)(在其他的语言中,这样的表又叫做vtables,虚函数表)。需要说明的是,java语言中,如果没有添加任何关键字,则方法默认就是虚方法,任何子类都可以重写它。
1、方法调用
在Java中,方法调用有两类,动态方法调用与静态方法调用。
(1)静态方法调用是指对于类的静态方法的调用方式,是在编译时刻就已经确定好具体调用方法的情况,是静态绑定的。

(2)动态方法调用需要有方法调用所作用的对象,是在调用的时候才确定具体的调用方法,是动态绑定的。

我们这里所讲的多态就是后者—动态方法调用。
多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定
(1)Java的方法重载(类内部之间的多态):就是在类中可以创建多个方法,它们具有相同的名字,但可具有不同的参数列表、返回值类型。我们举个例子来解释,就是一对夫妇生了多胞胎,多胞胎之间外观相似,其实是不同的孩子。

(2)Java的方法重写(父类与子类之间的多态):子类可继承父类中的方法,但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。重写的参数列表和返回类型均不可修改。
多态的实现:
1.内部类之间的多态实现:方法重载
2.类之间的多态:方法重写
(1)继承方式实现多态
(2)接口方式实现多态
**6. Java如何保证多线程安全 **
1.读一致性
Java 中针对上述“读不安全”的问题提供了关键字 volatile 来解决问题,被 volatile 修饰的成员变量,在内容发生更改的时候,会通知所有线程去主内存更新最新的值,这样就解决了读不安全的问题,实现了读一致性。 但是,读一致性是无法解决写一致性的,虽然能够使得每个线程都能及时获取到最新的值,但是1.1中的写一致性问题还是会存在。 既然如此,Java 为啥还要提供 volatile 关键字呢?这并非多余的存在,在某些场景下只需要读一致性的话,这个关键字就能够满足需求而且性能相对还不错。
2.写一致性
Java 提供了三种方式来保证读写一致性,分别是互斥锁、自旋锁、线程隔离。
互斥锁只是一个锁概念,在其他场景也叫做独占锁、悲观锁等,其实就是一个意思。它是指线程之间是互斥的,某一个线程获取了某个资源的锁,那么其他线程就只能睡眠等待。
(1)互斥锁
在 Java 中互斥锁的实现一般叫做同步线程锁,关键字 synchronized,它锁住的范围是它修饰的作用域,锁住的对象是: 当前对象(对象锁) 或 类的全部对象(类锁) ——锁释放前,其他线程必将阻塞,保证锁住范围内的操作是原子性的,而且读取的数据不存在一致性问题。
对象锁:当它修饰方法、代码块时,将会锁住当前对象
类锁:修饰类、静态方法时,则是锁住类的所有对象
注意: 锁住的永远是对象,锁住的范围永远是 synchronized 关键字后面的花括号划定的代码域。
(2)自旋锁
自旋锁也只是一个锁概念,在其他场景也叫做乐观锁等。
自旋锁本质上是不加锁,而是通过对比旧数据来决定是否更新:
(3)线程隔离
既然自旋锁只是低并发的解决方案,那么遇到高并发要如何处理呢?答案是将成员变量设成线程隔离的,也就是说每个线程都各自使用自己的变量,互相自己是不相关的。这样自然也做到了多线程安全。但是这种做法是让所有线程都互相隔离的了,所以他们之间是不存在互相操作的。
7. I/O多路复⽤讲⼀下,epoll优势在哪,为什么,epoll⽔平触发与边缘触发

I/O多路复用,我觉得其实是一种机制,相比于之前的多进程,多线程来处理连接请求后的相关操作,I/O多路复用则可以一个进程或者一个线程去完成对这些I/O请求的操作,使得内核一旦发现进程指定的一个或多个IO条件就绪,它就通知进程。

它的优点就是:与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

IO复用应用的场合:

(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

在现实使用中,用的最多的就是I/O复用了,无非就是select,poll,epoll
很多人提到网络就说epoll,认为epoll效率是最高的。单纯的这么认为,其实有失偏颇。epoll固然高效,可是它是怎么做到高效的,它到底比select或poll优异在哪儿?
我们通过调用流程来简单分析下。
首先以select为例(poll类似),看下其调用过程

1.选择想要处理的套接字,通过接口FD_SET(fd, &set)加入到set中;

2.调用select(max+1, &set,,..)

3.对set中所有套接字调用FD_ISSET(fd,&set),查看fd上是否有事件发生

select存在的问题

单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;(在linux内核头文件中,有这样的定义:#define __FD_SETSIZE 1024)
内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;
select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;
select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。
epoll调用过程

1 .epoll_create 创建一个epoll对象,一般epollfd = epoll_create()

2 .epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件

比如epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入

  epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入

添加事件的时候,其实是向内核注册了一个回调函数。回调函数作用是,在相应的套接字上发生事件时,将其加入到epoll对象的时间就绪链表中,而这是在内核完成的。

3 epoll_wait(epollfd,...),获取就绪事件。即从就绪事件链表中取出所有的事件。

可以看到epoll比select高效的地方在于,其返回的就是所有已经发生事件的套接字,而不需要像select那样需要在用户态去判断每个套接字上是否有事件发生。

另外,在调用select时,内核需要去一一检测传入的套接字集合是否有事件,而调用epoll_wait时,只是将内核中的就绪数据取出而已

如果有n个连接,并且这n个连接都有事件发生,那么使用select与epoll其实并没有多少区别。对于select来说,用户态对每一个套接字的事件监测都是有效的。

但是select有一个问题是,每次去调用select之前,都要重置套接字set。如果连接数很大,每次FD_SET(fd, &set)调用接口,也会对性能造成不小的影响。而epoll中,只需调用一次epoll_ctl即可。

所以,在连接数很大,且活跃连接不多的情况下,使用epoll有明显的优势;而如果连接数较少,且连接基本都是活跃的,其实select的效果反而会更好。

posted @   蹇爱黄  阅读(174)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
点击右上角即可分享
微信分享提示