(1-20)计算机 Java后端 实习 and 秋招 面试高频问题汇总

写在前面

我们这一届的秋招已经结束了,回想过去几个月,从暑期实习到秋招,我总计经历了上百场面试,也阅读了牛客上数千篇面经,受益匪浅。虽然最终由于个人原因,很遗憾地拒绝了多家大厂的 ssp offer(本不想特意提这个,但怕不提学弟学妹可能不太感兴趣,哈哈),但这一切都是个人选择,此处也就不过多展开了。

为什么我想要做这样一份汇总呢?是因为在我经历秋招最艰难、最迷茫的时候,正是牛客上众多前辈和同学们分享的宝贵经验与经历,陪伴并帮助我度过了那些焦虑而迷茫的时刻。对于他们的无私分享,我心怀深深的感激,也希望能够尽自己的一份绵薄之力,继续帮助更多后来的人。在过去的两年里,我陆陆续续地积累了大约 30 万字的内容,涵盖了近 300 个后端高频面试问题(有些太过刁钻的我会做删除处理,不保证最终达到300个),如果之后时间允许,我会将它们逐步整理并分享出来。在可以预见的未来,如果没有意外的变动,这些内容都会免费地“开源”,因为我曾经无数次从别人的分享中获得过帮助,如果有机会,我愿意把这份温暖和善意传递下去

1.Java 中 3 种常见的 IO 模型

BIO

BIO 即 Blocking I/O;字面意思就可以看出它属于同步阻塞 IO。

如下图,应用程序发出一个 read 调用,内核空间需要经历准备数据的几个阶段,准备好之后返回数据给应用程序。期间如果另一个应用程序也需要 read 调用,那么它必须等待;这就是阻塞。

BIO 最大的特点就是一次只能处理一个调用,这在高并发的场景下肯定是不行的。

NIO

NIO 就是 Non-blocking I/O。字面翻译为非阻塞

非阻塞模型:关键词是 轮询 ,例如小明需要找人帮忙,于是找到张三,第一次张三在忙,第二次张三还在忙,此后小明的做法是每一个小时来一次,直到等到张三有空为止。该做法很不明智,具体体现在浪费了小明的时间,来来回回都是需要消耗处理器资源的。

I/O 多路复用模型

这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。

这个时候,I/O 多路复用模型 就上场了。

IO多路复用是指使用一个系统调用(如select、poll、epoll等)来同时监控多个文件描述符(如socket、文件、管道等),当其中某些文件描述符发生了可读、可写或异常事件时,系统调用返回,通知应用程序进行相应的IO操作。IO多路复用可以减少系统调用的次数,避免不必要的阻塞,提高并发性能。但是IO多路复用也有一些缺点,比如需要维护一个文件描述符集合,可能有一定的开销;对于大量的文件描述符,可能会超过系统的限制;对于某些事件,可能需要轮询才能发现。

非阻塞IO是指在进行IO操作时,如果数据没有准备好,不会阻塞当前进程或线程,而是立即返回一个错误码(如EWOULDBLOCK或EAGAIN),让应用程序可以继续执行其他任务。非阻塞IO可以避免等待数据的时间,提高CPU的利用率。但是非阻塞IO也有一些缺点,比如需要不断地轮询文件描述符的状态,可能会浪费CPU资源;对于每个文件描述符,只能进行一次IO操作,不能保证数据的完整性;对于某些情况,可能会发生假唤醒或惊群现象。

IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。

目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,是目前几乎在所有的操作系统上都有支持

AIO (Asynchronous I/O)

AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。

异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

2.HTTPS加密算法、原理、知道有哪些对称加密和非对称加密算法吗

HTTPS就是HTTP加上SSL/TLS加密处理

HTTPS 建立连接的过程中,一共发起两次请求,进行非对称和对称两次加密

1.客户端发送一个https的请求到服务端

2.服务端申请配置好数字证书,包含公钥和私钥

3.服务端将证书传送给客户端,证书中包含了很多信息,比如证书的颁发机构,过期时间,网址,公钥等

4.客户端解析证书,由客户端的TLS完成,首先会验证公钥是否有效,比如颁发机构,过期时间等。如果有异常,就会弹出警告信息,并结束通信。如果正常,则生成一个随机值(用于对称加密),然后用服务端的公钥对随机值进行非对称加密

5.客户端将加密后的随机值传送到服务端

6.服务端使用证书的私钥非对称解密得到客户端的随机值,用获取的随机值将传输的明文内容进行对称加密

7.服务端把对称加密后的数据传输到客户端

8.客户端通过随机值对称解密获取明文内容

非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。另外,在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以 HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密

加密算法我们整体可以分为:可逆加密和不可逆加密,可逆加密又可以分为:对称加密和非对称加密。

不可逆加密算法 MD5

对称加密算法是应用比较早的算法,在数据加密和解密的时用的都是同一个密钥,AES,对称加密算法的安全性相对较低

非对称加密算法有两个密钥,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。常见的非对称加密有RSA

3.SpringCloud组件了解哪些

springcloud五大组件:1、Eureka实现服务治理;2、Ribbon主要提供客户侧的软件负载均衡算法;3、Hystrix断路器,保护系统,控制故障范围;4、Zuul(spring cloud gateway),api网关,路由,负载均衡等多种作用;5、Config配置管理

1、Eureka

作用:实现服务治理(服务注册与发现

Eureka服务端用作服务注册中心。支持集群部署。

Eureka客户端是一个java客户端,用来处理服务注册与发现。

在应用启动时,Eureka客户端向服务端注册自己的服务信息,同时将服务端的服务信息缓存到本地。客户端会和服务端周期性的进行心跳交互,以更新服务租约和服务信息。

2、Ribbon

作用:Ribbon,主要提供客户侧的软件负载均衡算法。

简介:Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。

注意看上图,关键点就是将外界的rest调用,根据负载均衡策略转换为微服务调用。Ribbon有比较多的负载均衡策略,以后专门讲解。

3、Hystrix

作用:断路器,保护系统,控制故障范围。

简介:为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

4、Zuul

作用:api网关,路由,负载均衡等多种作用

spring-cloud-Gateway

是spring-cloud的一个子项目。而zuul则是netflix公司的项目,只是spring将zuul集成在spring-cloud中使用而已。因为zuul2.0连续跳票和zuul1的性能表现不是很理想,所以催生了spring团队开发了Gateway项目。

简介:类似nginx,反向代理的功能,不过netflix自己增加了一些配合其他组件的特性。

在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。

5、Config

作用:配置管理

简介:SpringCloud Config提供服务器端和客户端。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。

这个还是静态的,得配合Spring Cloud Bus实现动态的配置更新。

4.ConcurrentHashMap如何实现的线程安全

JDK 1.7 中 ConcurrentHashMap 是如何保证线程安全

ConcurrentHashMap 的线程安全是建立在 Segment 加锁的基础上的,这样就能保证多个线程同时访问 ConcurrentHashMap 时,同一时间只有一个线程能操作相应的节点,这样就保证了 ConcurrentHashMap 的线程安全了。

在 JDK 1.7 中,ConcurrentHashMap 虽然是线程安全的,但因为它的底层实现是数组 + 链表的形式,所以在数据比较多的情况下访问是很慢的,因为要遍历整个链表,而 JDK 1.8 则使用了数组 + 链表/红黑树的方式优化了 ConcurrentHashMap 的实现

链表升级为红黑树的规则:当链表长度大于 8,并且数组的长度大于 64 时,链表就会升级为红黑树的结构。

在 JDK 1.8 中 ConcurrentHashMap 使用的是 CAS + volatile 或 synchronized 的方式来保证线程安全的

sychronized锁机制,当线程进入synchronized代码块前,获得锁,其他线程就会被阻塞挂起。当该线程正常退出或者抛出异常或者调用wait()系列方法释放锁时,其他线程才有机会获得锁,执行该代码块。简而言之,sychronized锁机制通过阻塞来确保java操作的原子性

CAS就是Compare And Swap,是JDK提供给的非阻塞性原子性操作。它通过硬件保证了比较--更新的原子性操作

自旋锁,比较并交换、保证原子操作;通过volatile实现数据的可见性 CAS是非阻塞的、轻量级的乐观锁,利用 CPU 指令保证操作的原子性,以达到锁的效果,并且性能还很高;适合简单对象操作,比如布尔值、整型值

volatile,通过内存屏障保证可见性、不保证原子性、禁止指令重排,当变量被volatile修饰时,它会保证修改的值会立即写回主内存

ConcurrentHashMap 是在头节点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度

你好,这是必应。我从网上搜索了一些关于 ConcurrentHashMap 最新的为提升并发性能的变化的信息,希望对你有帮助。

根据搜索结果¹²³⁴,我了解到:

  • ConcurrentHashMap 是 Java 中支持高并发、高吞吐量的线程安全 HashMap 实现。
  • 在 Java 7 中,ConcurrentHashMap 采用了分段锁的设计,将整个哈希表分为若干个 Segment,每个 Segment 有自己的锁,这样可以减少锁的粒度,提高并发访问的效率。
  • 在 Java 8 中,ConcurrentHashMap 放弃了分段锁的设计,而是采用了 CAS(Compare and Swap)和 synchronized 来实现更好的并发性能。它使用 CAS 操作来保证节点插入和删除的原子性,使用 synchronized 来保证节点更新的互斥性。
  • 在 Java 8 中,ConcurrentHashMap 还引入了红黑树的结构来优化链表过长的情况,当链表长度超过一定阈值时,会将链表转换为红黑树,这样可以降低查找的时间复杂度。

5.Java可达性分析算法

引用计数算法

在对象中添加一个引用计数器,每当新加一个引用时,计数器就加1,当引用失效时,计数器就减1。任何时刻只要计数器为0就代表对象没有引用可以被回收。

这种算法实现简单,判断高效,但是有一些缺点

可达性分析算法

目前主流的商用JVM都是通过可达性分析来判断对象是否可以被回收

通过一系列被称为「GC Roots」的根对象作为起始节点集,从这些节点开始,通过引用关系向下搜寻,搜寻走过的路径称为「引用链」,如果某个对象到GC Roots没有任何引用链相连,就说明该对象不可达,即可以被回收。

对象可达指的就是:双方存在直接或间接的引用关系。根可达或GC Roots可达就是指:对象到GC Roots存在直接或间接的引用关系。

垃圾回收时,JVM首先要找到所有的GC Roots,这个过程称作 「枚举根节点」 ,这个过程是需要暂停用户线程的,即触发STW。 然后再从GC Roots这些根节点向下搜寻,可达的对象就保留,不可达的对象就回收。

Stop-the-World,简称STW,指的是GC事件发生过程中,会产生应用程序的停顿。 停顿产生时整个应用程序线程都会被暂停,没有任何响应。 有点像卡死的感觉

GC Roots就是对象,而且是JVM确定当前绝对不能被回收的对象(如方法区中类静态属性引用的对象 )。只有找到这种对象,后面的搜寻过程才有意义,不能被回收的对象所依赖的其他对象肯定也不能回收嘛。

1、方法区静态属性引用的对象 全局对象的一种,Class对象本身很难被回收,回收的条件非常苛刻,只要Class对象不被回收,静态成员就不能被回收。

2、方法区常量池引用的对象 也属于全局对象,例如字符串常量池,常量本身初始化后不会再改变,因此作为GC Roots也是合理的。

3、方法栈中栈帧本地变量表引用的对象 属于执行上下文中的对象,线程在执行方法时,会将方法打包成一个栈帧入栈执行,方法里用到的局部变量会存放到栈帧的本地变量表中。只要方法还在运行,还没出栈,就意味这本地变量表的对象还会被访问,GC就不应该回收,所以这一类对象也可作为GC Roots。

4、JNI本地方法栈中引用的对象 和上一条本质相同,无非是一个是Java方法栈中的变量引用,一个是native方法(C、C++)方法栈中的变量引用。

5、被同步锁持有的对象 被synchronized锁住的对象也是绝对不能回收的,当前有线程持有对象锁呢,GC如果回收了对象,锁不就失效了嘛。

6.Python怎么解决引用计数循环引用问题

python 采用的是引用计数机制为主,标记 - 清除和分代收集两种机制为辅的策略。

只靠强引用计数方式,会存在循环引用的问题,导致对象永远无法被释放,弱引用就是专门用来解决循环引用问题的:

若 A 强引用了 B,那 B 引用 A 时就需使用弱引用,当判断是否为无用对象时仅考虑强引用计数是否为 0,不关心弱引用计数的数量

这样就解决了循环引用导致对象无法释放的问题

但这会引发野指针问题:当 B 要通过弱指针访问 A 时,A 可能已经被销毁了,那指向 A 的这个弱指针就变成野指针了。在这种情况下,就表示 A 确实已经不存在了,需要进行重新创建等其他操作

7.生产过程中发现问题,内存飙高/OOM,怎么去定位和解决问题?

某Java服务(假设PID=19813)出现了OOM,最常见的原因为:

  1. 有可能是内存分配确实过小,而正常业务使用了大量内存
  2. 某一个对象被频繁申请,却没有释放,内存不断泄漏,导致内存耗尽
  3. 某一个资源被频繁申请,系统资源耗尽,例如:不断创建线程,不断发起网络连接

一、确认是不是内存本身就分配过小 jmap -heap 19813

上图,可以查看新生代,老生代堆内存的分配大小以及使用情况,看是否本身分配过小。

二、找到最耗内存的对象 方法:jmap -histo:live 19813 | more

如上图,输入命令后,会以表格的形式显示存活对象的信息,并按照所占内存大小排序: 实例数 所占内存大小 类名 是不是很直观?对于实例数较多,占用内存大小较多的实例/类,相关的代码就要针对性review了。 上图中占内存最多的对象是char 类型 ,共占用内存11M,属于正常使用范围。

如果发现某类对象占用内存很大(例如几个G),很可能是类对象创建太多,且一直未释放。

三、确认是否是资源耗尽 查看进程创建的线程数,以及网络连接数,如果资源耗尽,也可能出现OOM

8.什么样的写法会导致内存泄漏

Java中内存泄漏主要是因为不能正确释放不需要的资源,长生命周期对象持有短生命周期对象的引用。

静态字段

静态字段引起的内存泄漏比较常见,如果某个不需要的类中含有静态字段,那么就会造成内存泄漏。单例模式中如果持有其他的类引用就会造成内存泄漏,静态集合如HashMap,LinkedList等持有的一些对象没有及时释放等。

可以聊一下HashMap做缓存的泄露问题

9.Spring中的Bean默认是单例还是多例?如何保证并发安全?

Spring的bean默认都是单例的,某些情况下,单例是并发不安全的,以Controller举例,问题根源在于,我们可能会在Controller中定义成员变量,如此一来,多个请求来临,进入的都是同一个单例的Controller对象,并对此成员变量的值进行修改操作,因此会互相影响,无法达到并发安全

1 单例变原型

对web项目,可以Controller类上加注解@Scope("prototype")或@Scope("request"),对非web项目,在Component类上添加注解@Scope("prototype")。

优点:实现简单;

缺点:很大程度上增大了bean创建实例化销毁的服务器资源开销。

2 线程隔离类ThreadLocal

有人想到了线程隔离类ThreadLocal,我们尝试将成员变量包装为ThreadLocal,以试图达到并发安全

ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

3 尽量避免使用成员变量

单例bean的成员变量这么麻烦,能不用成员变量就尽量避免这么用,在业务允许的条件下,将成员变量替换为RequestMapping方法中的局部变量

4 使用并发安全的类

Java作为功能性超强的编程语言,API丰富,如果非要在单例bean中使用成员变量,可以考虑使用并发安全的容器,如ConcurrentHashMap、ConcurrentHashSet等等等等,将我们的成员变量(一般可以是当前运行中的任务列表等这类变量)包装到这些并发安全的容器中进行管理即可。

spring bean作用域有以下5个:

  • singleton:单例模式,当spring创建applicationContext容器的时候,spring会欲初始化所有的该作用域实例,加上lazy-init就可以避免预处理;
  • prototype:原型模式,每次通过getBean获取该bean就会新产生一个实例,创建后spring将不再对其管理;

(下面是在web项目下才用到的)

  • request:搞web的大家都应该明白request的域了吧,就是每次请求都新产生一个实例,和prototype不同就是创建后,接下来的管理,spring依然在监听;
  • session:每次会话,同上;
  • global session:全局的web域,类似于servlet中的application。

10.spring的bean对象是通过什么存储的

bean对象最终存储在spring容器中,我们简单的、狭义上的spring容器,在spring源码底层就是一个map集合,这个map集合存储的key是当前bean的name,如果不指定,默认的是class类型首字母小写作为key,value是bean对象。存储bean的map在DefaultListableBeanFactory类中:

当Spring容器扫描到Bean类时 , 会把这个类的描述信息, 以包名加类名的方式存到beanDefinitionMap 中

spring为什么不在扫描到class文件之后,立即执行生命周期方法进行初始化、实例化?而是要先放到beanDefinitionMap集合中?

因为spring所提供的容器管理功能中,某些class类并不一定是立马需要初始化的,比如:原型bean,就是在使用的时候,再去初始化。

11.BeanFactory和FactoryBean区别

BeanFactory:Bean工厂,是一个工厂(Factory),我们Spring IoC容器的最顶层接口就是这个BeanFactory,它的作用是管理Bean,即实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。

BeanFacotry是spring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory。原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。 ApplicationContext接口,它由BeanFactory接口派生而来,ApplicationContext包含BeanFactory的所有功能,通常建议比BeanFactory优先

FactoryBean,首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean

FactoryBean表现的是一个工厂的职责。 即一个Bean A如果实现了FactoryBean接口,那么A就变成了一个工厂,根据A的名称获取到的实际上是工厂调用getObject()返回的对象

通常情况下,bean 无须自己实现工厂模式,Spring 容器担任了工厂的 角色;但少数情况下,容器中的 bean 本身就是工厂

12.JSON Web Token

Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。

JSON Web Token由三部分组成它们之间用圆点(.)连接。这三部分分别是:

  • Header
  • Payload
  • Signature

因此,一个典型的JWT看起来是这个样子的:

xxxxx.yyyyy.zzzzz

接下来,具体看一下每一部分:

Header header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。

Payload JWT的第二部分是payload,它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明

不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

Signature,为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。

签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。

无论何时用户想要访问受保护的路由或者资源的时候,用户代理(通常是浏览器)都应该带上JWT,典型的,通常放在Authorization header中

服务器上的受保护的路由将会检查Authorization header中的JWT是否有效,如果有效,则用户可以访问受保护的资源。如果JWT包含足够多的必需的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。

如果token是在授权头(Authorization header)中发送的,那么跨源资源共享(CORS)将不会成为问题,因为它不使用cookie。

13.常见的状态码

HTTP 状态代码分为以下五组:

  • 1xx 信息响应。 收到并理解的请求。 请求处理将继续。
  • 2xx 成功。 已成功接收、理解和接受该操作。
  • 3xx 重定向。 客户端必须采取进一步操作才能完成请求。
  • 4xx 客户端错误。 可能是客户端导致的错误。 请求包含错误的语法或无法实现。
  • 5xx 服务器错误。 服务器遇到错误,无法满足请求。

100是一个HTTP状态码,表示服务器已经收到了请求的首部,请求者可以继续发送请求的实体。12

这个状态码主要用于与 Expect: 100-continue 首部一起使用,以允许客户端在发送大量数据之前,让服务器检查请求的首部是否有效。13

  • 状态代码 200 = 这是成功 HTTP 请求的标准”确定”状态代码。 返回的响应取决于请求。 例如,对于 GET 请求,响应将包含在消息正文中。 对于 PUT/POST 请求,响应将包括包含操作结果的资源。
  • 301表示永久性重定向,表示请求的资源已经被永久地移除了,搜索引擎会抓取新的内容并更新旧的网址。12
  • 302表示临时性重定向,表示请求的资源还在,这个重定向只是暂时的,搜索引擎会抓取新的内容但保留旧的网址。12
  • 状态代码 304 = 用于浏览器缓存的状态代码。 如果响应尚未修改,则客户端/用户可以继续使用相同的响应/缓存版本。 例如,如果资源已自特定时间以来被修改,浏览器可以请求。 如果没有,则发送状态代码 304。 如果已修改,则发送状态代码 200 以及资源。
  • 状态代码 404 = 普通用户将看到的最常见状态代码。 当请求有效,但无法在服务器上找到资源时,将发生状态代码 404。 即使这些代码分组在客户端错误”存储桶”中,它们通常是由于不正确的 URL 重定向造成的。
  • 状态代码 500 – 用户通常看到的另一个状态代码,500 系列代码类似于 400 系列代码,因为它们是真正的错误代码。 当服务器由于意外问题无法完成请求时,将发生状态代码 500。 Web 开发人员通常必须对服务器日志进行梳理,以确定问题的确切问题来自何处。

14.sleep()和wait()的区别

在Java中,sleep()和wait()都可以暂停线程的执行。但是,它们有以下不同点:

  1. sleep()是Thread类的方法,而wait()来自Object类。
  2. sleep()方法不需要写在同步方法中,而wait()和notify()方法必须写在同步方法中。
  3. sleep()方法不会释放锁,而wait()方法会释放锁。
  4. sleep()方法必须要指定时间参数;wait()方法可以指定时间参数。

当一个执行中的线程调用了Thread的sleep()方法后,调用线程会暂时让出时间的执行权,这期间不参与cpu的调度,但是该线程持有的锁是不让出的。因此,sleep()方法不会释放锁。

15.Object类中的所有方法

  • protected Object clone():创建并返回一个对象的拷贝。
  • boolean equals(Object obj):比较两个对象是否相等,比较的是值和地址,子类可重写以自定义。
  • protected void finalize():当GC(垃圾回收器)确定不存在对该对象的有更多引用时,由对象的垃圾回收器调用此方法。
  • Class<?> getClass():获取对象的运行时对象的类。
  • int hashCode():获取对象的hash值。
  • void notify():唤醒在该对象上等待的某个线程。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
  • void notifyAll():唤醒在该对象上等待的所有线程。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
  • String toString():返回对象的字符串表示形式。如果没有重写,应用对象将打印的是地址值。
  • void wait():让当前线程进入等待状态。直到其他线程调用此对象的notify()方法或notifyAll()方法。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
  • void wait(long timeout):让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过参数设置的timeout超时时间。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

16.equals()与hashcode(),什么时候重写,为什么重写 为什么重写

  • 成对重写,即重写equals()就应当重写hashcode()。这是为了遵守Object类中对这两个方法的约定123。Object类中的约定是:如果两个对象相同(即equals()返回true),那么它们的hashcode()值必须相同;如果两个对象的hashcode()值相同,它们并不一定相同(即equals()不一定返回true)。
  • 适应性,即:当我们根据业务需求改写了equals()方法的实现,比如比较对象的某些属性而不是地址值,那么我们也应当改写hashcode()方法的实现,使得它能根据对象的属性计算出合理的哈希值23。否则,如果我们还是使用Object类中基于地址值得到的哈希值,那么可能会导致一些问题,比如在HashMap或HashSet等集合中存放数据时,出现哈希冲突或者数据丢失的情况23。

所以,重写equals()方法就必须重写hashcode()方法,这样才能保证对象的比较和存储的正确性和高效性。

17.Java如何保证多线程安全

Java中保证多线程安全的方法有以下几种:

  • 使用volatile关键字,可以保证被修饰的变量在多线程之间的可见性,即当一个线程修改了变量的值,其他线程能够立即看到最新的值。
  • 使用synchronized关键字,可以保证被修饰的方法或代码块在同一时刻只能被一个线程访问,从而实现互斥锁的效果。
  • 使用Lock接口及其实现类,如ReentrantLock,可以提供更灵活的锁机制,支持可中断、可重入、公平和非公平等特性。
  • 使用原子类,如AtomicInteger,AtomicLong等,可以利用CAS(比较并交换)算法实现无锁的原子操作,避免了线程上下文切换和阻塞的开销。(用原子类的话,这里需要把变量声明为原子变量,自增时也要调用原子类的方法。用原子类是自带锁,保证线程安全同步,不用再附加其他锁。)
  • 使用ThreadLocal类,可以为每个线程创建一个独立的变量副本,使得每个线程都可以访问自己的变量,而不会影响其他线程的变量。

18.进程 线程

  • 进程与线程的区别主要有以下几点

  • 进程通信方式有以下几种

  • socket在本机和在网通信区别主要有以下几点 :

19.讲⼀下Mysql聚集索引与⾮聚集索引,hash索引,主键索引使⽤int与string有啥区别

聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致¹²³。聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序¹²³。非聚集索引则是普通索引,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序¹²³。非聚集索引的叶子节点仍然是索引节点,只不过有一个指针指向对应的数据块¹²³。主键索引使用int和string有以下区别:

  • int类型比string类型更适合作为主键,因为int类型占用空间更小,比较效率更高,索引效率更高⁴⁵。
  • string类型作为主键可能会涉及到字符集编码的问题,这会增加CPU资源的消耗⁵。
  • string类型作为主键可能会导致数据类型变更的可能性,因为string类型可能会随着业务需求而发生变化⁵。
  • string类型作为主键可能会导致数据重复的可能性,因为string类型可能不具备唯一性约束⁵。

综上所述,建议使用int类型作为主键索引,除非有特殊需求或场景需要使用string类型。B+树结构是MySQL中常用的索引结构,它可以支持范围查询、排序、分组等操作⁶。使用string类型作为B+树结构的索引键值不会影响到结构本身,但可能会影响到结构的效率和空间占用⁶。

MySQL支持多种存储引擎,如InnoDB、MyISAM、Memory、Merge、Archive、Federated、NDBCluster等

InnoDB和Memory都可以建立hash索引,但是有一些区别和限制。

  • InnoDB的hash索引是自适应的,也就是说,它是根据查询的模式和频率动态生成和调整的¹²。InnoDB的hash索引总是基于表上已有的B-tree索引构建的,可以对B-tree索引的任意长度的前缀建立hash索引¹²。InnoDB的hash索引可以是部分的,只覆盖经常访问的索引页¹²。InnoDB的hash索引可以通过innodb_adaptive_hash_index变量开启或关闭²。
  • Memory的hash索引是显式的,也就是说,它是在创建表或添加索引时指定的³⁴。Memory的hash索引只能基于整个列或整个键构建,不能对列或键的前缀建立hash索引³⁴。Memory的hash索引可以是完整的,覆盖所有的数据行³⁴。Memory的hash索引可以通过engine_condition_pushdown变量开启或关闭⁴。

20.雪花算法和自增id的区别

雪花算法和自增id的区别主要有以下几点:

  • 自增id是数据库生成的,雪花算法是在内存中生成的¹²⁴。
  • 自增id是顺序的,雪花算法是有序递增的¹²⁴。
  • 自增id在分库分表后会造成id冲突,雪花算法可以保证全局唯一¹²⁴。
  • 自增id占用空间小,雪花算法占用空间大¹²⁴。
  • 自增id容易被外界攻破,雪花算法可以保证信息安全¹²。(容易被外界攻破,知道业务实际情况。且例如:显示公告内容index?id=3这样就很容易被人篡改为index?id=2.就可以调到第二条的内容。)
  • 雪花算法依赖于系统时钟,如果时钟回拨,可能会造成id重复²⁴。

snowflake是Twitter开源的分布式ID生成算法,结果是64bit的Long类型的ID,有着全局唯一和有序递增的特点。

  • 最高位是符号位,因为生成的 ID 总是正数,始终为0,不可用。
  • 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
  • 10位的机器标识,10位的长度最多支持部署1024个节点。
  • 12位的计数序列号,序列号即一系列的自增ID,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。

缺点也是有的,就是强依赖机器时钟,如果机器上时钟回拨,有可能会导致主键重复的问题。

写在后面

最近有一些学弟学妹找我帮忙看简历,我也很开心能帮到大家~加上这段时间我比较闲,后面可以更多跟大家交流嘿嘿。我建议还是以后端方向的学弟学妹为主,因为这方面我能给出更多具体建议,希望能为你们提供一些有价值的帮助!

如果需要,欢迎发给我你的学校和名字,我帮你看看

#牛客创作赏金赛#
实习/秋招面经 文章被收录于专栏

实习/秋招面经

全部评论
向前辈致敬,respect
2 回复 分享
发布于 03-18 16:07 江苏
mark一下
1 回复 分享
发布于 03-20 23:18 云南
爱死时错佬了
1 回复 分享
发布于 03-18 20:32 上海
woc,万字好文,爱了
1 回复 分享
发布于 03-18 17:25 江苏
mark一下
点赞 回复 分享
发布于 04-08 19:40 四川
向前辈致敬
点赞 回复 分享
发布于 03-22 09:58 上海
1
点赞 回复 分享
发布于 03-20 21:34 河南
mark一下
点赞 回复 分享
发布于 03-20 20:13 江苏
mark一下
点赞 回复 分享
发布于 03-20 18:42 河北
接好运
点赞 回复 分享
发布于 03-20 18:26 湖南
接好运
点赞 回复 分享
发布于 03-20 15:27 河南
mark一下
点赞 回复 分享
发布于 03-20 14:45 广东
mark一下
点赞 回复 分享
发布于 03-20 12:41 北京
追更追更
点赞 回复 分享
发布于 03-20 10:47 北京
太有帮助了
点赞 回复 分享
发布于 03-19 23:00 辽宁
太牛了
点赞 回复 分享
发布于 03-19 18:52 北京
接好运
点赞 回复 分享
发布于 03-19 18:01 山西
mark一下
点赞 回复 分享
发布于 03-19 15:40 江苏
大佬求带
点赞 回复 分享
发布于 03-19 15:37 北京
mark一下
点赞 回复 分享
发布于 03-19 14:31 江苏

相关推荐

评论
47
167
分享

创作者周榜

更多
牛客网
牛客企业服务