腾讯实习基地后端面经解析:死背八股不可取

嗨~我是可拟雀,一个后端开发工程师,毕业于某985大学,目前供职于bat某大厂核心部门后端。每天分享最新面经答案,希望在大环境不好的当下能帮到你,让你多积累面试经验。需要内推或者面经合集请评论哦。

1. Java中保证线程安全有哪些数据结构。

答:

java.util.concurrent 包中的数据结构:

ConcurrentHashMap:线程安全的哈希表实现,适用于高并发场景。

CopyOnWriteArrayList:线程安全的列表实现,通过复制底层数组来实现线程安全,适用于读多写少的场景。

BlockingQueue接口的实现类,如ArrayBlockingQueue、LinkedBlockingQueue等:线程安全的队列实现,常用于生产者消费者模型。

ConcurrentLinkedQueue:一个基于链接节点的无界线程安全队列,此队列按照 FIFO(先进先出)原则对元素进行排序。

ConcurrentSkipListMap 和 ConcurrentSkipListSet:线程安全的排序映射和排序集合,基于跳表实现。

java.util 包中的同步包装器:

Collections.synchronizedList(), Collections.synchronizedSet(), Collections.synchronizedMap()等:这些方法可以对现有的集合进行包装,使其变为线程安全的。但需要注意的是,迭代时必须手动同步,以防止在迭代过程中集合被其他线程修改。

使用synchronized关键字:可以通过在访问共享资源的方法或代码块上添加synchronized关键字来保证线程安全。但这种方法需要开发者自己处理同步的细节,容易出错。

使用Atomic类:Java的java.util.concurrent.atomic包提供了一些原子变量类,如AtomicInteger、AtomicLong、AtomicReference等,这些类提供了原子操作,可以在多线程环境下安全地更新变量的值。

使用锁:Java的java.util.concurrent.locks包提供了更灵活的锁机制,如ReentrantLock、ReadWriteLock等,可以更精细地控制资源的访问。

2. Java中内存泄露的原因,什么场景下会存在内存泄露。

答:

没有正确释放资源:当程序中没有正确关闭数据库连接、文件流等资源时,这些资源就会一直占用内存,导致内存泄漏。

静态引用过多:程序中过多地使用静态变量或静态集合时,这些变量会在整个程序运行期间一直占用内存,从而导致内存泄漏。例如,像HashMap、Vector、List等静态集合类的使用,如果管理不当,容易出现内存泄漏。

匿名内部类导致的内存泄漏:如果程序中使用匿名内部类,而匿名内部类中包含外部类的引用,且匿名内部类没有完成其生命周期,那么外部类的引用也不能被垃圾回收,导致内存泄漏。

内存对象被意外地保持引用:有时程序中会意外地保持某些内存对象的引用,虽然这些对象不再使用,但由于存在强引用,导致这些对象无法被垃圾回收机制回收,从而造成内存泄漏。

线程池使用不当:如果程序中使用了线程池,而线程池没有正确地关闭或者任务队列中的任务阻塞等待超时,会导致线程不能正常退出,造成内存泄漏。

常见的场景:

创建大量无用对象:在需要大量拼接字符串时,如果使用String而不是StringBuilder,会频繁创建新的字符串对象,从而可能导致内存泄漏。

各种连接对象未关闭:IO流对象、数据库连接对象、网络连接对象等都属于物理连接,与硬盘或网络相连。当这些连接不再需要时,如果没有正确关闭它们,会导致内存泄漏。

监听器的使用:在释放对象时,如果没有删除对应的监听器,也可能导致内存泄漏。

3. 面向对象中继承和组合的理解,它们的不同和优缺点。

答:在面向对象编程(OOP)中,继承和组合是两种用于实现代码重用和创建更复杂对象的主要技术。它们各自具有独特的特点和适用场景。

继承

定义:继承是一种允许一个类(派生类或子类)继承另一个类(基类或父类)的属性和方法的机制。子类可以继承父类的所有属性和方法,并可以添加或覆盖自己的属性和方法。

优点

  1. 代码重用:通过继承,子类可以自动获得父类的所有属性和方法,无需重新编写。
  2. 扩展性:子类可以在父类的基础上添加新的属性和方法,从而扩展功能。
  3. 多态性:子类可以覆盖父类的方法,实现多态性,使得不同类的对象可以使用相同的方法名但执行不同的操作。

缺点

  1. 紧耦合:继承会导致类之间的紧耦合,一旦父类发生变化,子类可能也会受到影响。
  2. 继承层次过深:过多的继承层次可能导致代码难以理解和维护,也称为“继承爆炸”。
  3. 不符合“组合优于继承”的原则:有时使用继承可能会引入不必要的复杂性,而使用组合可能更为简单和灵活。

组合

定义:组合是一种将一个类的对象作为另一个类的属性的机制。通过这种方式,一个类可以包含其他类的对象,从而利用这些对象的属性和方法。

优点

  1. 低耦合:组合通常导致更低的耦合度,因为类之间的关系更加松散。
  2. 灵活性:组合允许更灵活地组合不同的对象来创建新的对象,而无需定义新的类。
  3. 易于理解和维护:组合通常使代码更加直观和易于理解,因为每个类只关注其自己的职责。

缺点

  1. 代码冗余:如果多个类需要使用相同的属性和方法,那么每个类都需要单独实现这些属性和方法,可能导致代码冗余。
  2. 可能增加复杂性:在某些情况下,使用组合可能会导致代码结构变得更加复杂,特别是当需要管理的对象数量较多时。

4. 进程间通信的方式,如果要传输大量的数据应该用哪些方法。

答:进程间通信(IPC)的方式主要有以下几种:

  1. 管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动,常用于具有亲缘关系的进程间通信。
  2. FIFO(命名管道):FIFO也是一种文件类型,允许无亲缘关系的进程间通信。
  3. 消息队列(Message Queue):消息队列是在两个不相关的进程之间传递数据的一种简单高效的方式,它克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 信号量(Semaphore):信号量是一个计数器,用于控制多个进程对共享资源的访问,常作为锁机制防止进程访问共享资源时发生冲突。
  5. 共享内存(Shared Memory):多个处理器中的中央处理器可以访问的大容量内存,是最快的IPC方式,常用于实现进程间的同步通信。

如果要传输大量的数据,推荐使用以下方式:

  1. 消息队列通信:消息队列是在两个不相关的进程之间传递数据的一种简单高效的方式,它可以存放大量的数据在内核中,并通过队列的形式进行管理和传输。
  2. 共享内存通信:对于大量数据的传输,共享内存可能是最快的方式。因为多个进程可以直接访问同一块内存区域,无需进行数据的复制或转换。但是,使用共享内存时需要特别注意同步和互斥的问题,以防止数据冲突。
  3. 利用缓存:使用缓存可以加快数据传输速度,对于大量数据的传输尤其有效。缓存可以存储已经读取或写入的数据,当需要再次访问这些数据时,可以直接从缓存中获取,而无需重新从数据源读取。

此外,传统的文件传输协议如FTP、HTTP等可能不适合大数据传输,而采用P2P传输技术的高速数据传输工具,如镭速传输,可以实现高速、稳定的数据传输。同时,增加带宽、数据压缩、分段传输等方法也可以有效提高大数据的传输效率。

5. 什么是死锁,怎么避免死锁。

答:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

为了避免死锁,可以采取以下策略:

  1. 避免嵌套锁:当多个对象需要加锁时,需要将它们的锁顺序统一,尽量避免嵌套锁。
  2. 使用tryLock()方法:利用ReentrantLock类的tryLock()方法,在获取锁时设置超时时间,避免一直等待而产生死锁。
  3. 避免无限期等待:在获取锁时,应该设置一个等待的超时时间,即一段时间之后如果还没有获取到锁,就放弃任务执行。
  4. 使用不同的锁:如果可以使用不同的锁来代替原有的锁,那么可以尝试使用不同的锁来避免死锁。
  5. 尽量减少锁的持有时间:如果持有锁的时间过长,那么就会增加死锁的可能性,因此需要尽量减少锁的持有时间。
  6. 避免使用多个锁:在程序中使用了多个锁,会增加死锁的可能性。因此,应尽量避免使用多个锁,或者采用一些技巧来合并多个锁。
  7. 按照规定的顺序申请锁:为了避免死锁,可以规定一个申请锁的顺序,在申请锁的时候按照规定的顺序进行申请。
  8. 使用死锁检测工具:可以使用一些工具(如Java探针、Eclipse自带的死锁检测等)来检测和解决死锁问题。

6. 进程和线程的区别。

答:进程和线程是操作系统中用于实现并发执行的两种基本单位,它们之间存在以下主要区别:

  1. 资源占用:进程是系统分配资源的基本单位,它拥有一个完整的地址空间和系统资源,包括代码、数据和系统资源(如打开的文件、内存分配等)。而线程是CPU调度的基本单位,它只拥有少量的资源,如程序计数器、一组寄存器和栈,线程之间共享进程的资源,包括内存和打开的文件等。
  2. 执行独立性:进程在执行过程中拥有独立的内存单元和系统资源,因此一个进程出现问题不会影响其他进程的执行。而多个线程共享进程的内存和资源,因此一个线程崩溃可能会导致整个进程的崩溃。
  3. 系统开销:由于进程拥有独立的内存和资源,因此创建和销毁进程的系统开销较大。相比之下,线程之间的切换和调度的开销较小,因为线程之间共享进程的资源,无需进行大量的数据复制和初始化操作。
  4. 通信与同步:进程间的通信需要借助IPC(进程间通信)机制,如管道、消息队列、共享内存等。而线程间可以直接读写共享变量进行通信,但需要注意同步和互斥的问题,以避免数据竞争和不一致。
  5. 并发性:多线程程序在多个线程之间共享进程资源,使得并发执行更加高效。而多进程程序中的每个进程拥有独立的资源,因此并发执行可能需要更多的系统资源和开销。

7. 一个进程行能创建的线程有数量限制吗?

答:一个进程能创建的线程数量是有限制的。这个限制主要受到操作系统、硬件以及进程本身可用内存的限制。

在操作系统层面,每个进程都有一个虚拟地址空间,这个空间的大小限制了进程可以使用的内存总量。在默认情况下,线程的栈会预留一定的内存空间,例如1MB。因此,理论上,进程能创建的线程数量就受限于其虚拟地址空间大小和每个线程栈的大小。例如,如果进程的虚拟地址空间是2GB,每个线程的栈是1MB,那么理论上最多只能创建2048个线程。

然而,这只是一个理论上的限制。实际上,由于线程本身还需要其他资源(如内核数据结构、线程ID等),并且进程的其他部分也需要内存,因此实际能创建的线程数量通常会少于这个理论值。

此外,即使物理内存非常大,同一个进程能创建的线程数量也会受到限制。因为每个线程对象都需要占用非页面内存,而非页面内存是有限的。当非页面内存被耗尽时,就无法再创建新的线程了。

在某些情况下,可以通过调整线程栈的大小来突破这个限制。例如,将线程栈的大小从1MB减小到512KB,理论上就可以创建更多的线程。但是,这并不意味着应该无限制地创建线程,因为过多的线程可能会导致上下文切换的开销增大,降低程序的性能。

8. Java线程池的理解,线程的数量是怎么考虑的,线程回收是怎么做的。

答:Java线程池是一种用于优化线程管理的机制,它能够在后台维护多个线程,并将任务分配给这些线程来并发执行。使用线程池可以减少线程的创建和销毁次数,从而提高系统的性能和响应速度。

关于线程池中的线程数量,其确定通常需要考虑以下几个因素:

  1. 系统资源:线程数量受到系统可用处理器核心数、内存大小等资源的限制。过多的线程会导致系统资源耗尽,反而降低性能。
  2. 任务性质:任务的性质也会影响线程数量的选择。对于CPU密集型任务,线程数一般设置为处理器核心数加1(现代CPU支持超线程);而对于IO密集型任务,线程数则需要根据线程等待时间与CPU时间的比率以及处理器核心数来计算。
  3. 任务队列长度:当任务队列长度足够大时,核心线程数和最大线程数可能会相等。否则,不能触发到创建非核心线程。

在Java中,线程池通过线程池的维护者(ThreadPoolExecutor)来进行线程的回收。线程回收主要发生在两个方面:

  1. 空闲线程的回收:如果线程池中的线程处于空闲状态(即没有执行任务且在空闲时间超过一定阈值),则这些空闲线程可能会被回收。线程池中的核心线程数在空闲时不会被回收,但超过核心线程数的线程如果在一定时间内没有执行任务,可能会被回收。
  2. 超时线程的回收:当线程池中的线程数超过核心线程数,并且工作队列已满,新的任务无法立即执行,那么超过核心线程数的线程可能会被保留一段时间以等待新任务。但如果在一定时间内没有新任务提交,这些超过核心线程数的线程也可能被回收。线程空闲时间(keepAliveTime)表示非核心线程在空闲状态下的最长等待时间,超过这个时间,线程可能会被回收。

9. TCP粘包问题,解决方法,UDP有这个问题吗。

答:TCP粘包问题是指发送方发送的多个数据包在接收方被错误地合并成一个数据包,或者一个数据包被错误地拆分成多个数据包的现象。这主要是由于TCP协议的无边界特性导致的。

对于TCP粘包问题,有几种常见的解决方法:

  1. 发送固定长度的消息:如果消息的长度固定,那么接收方每次接收固定长度的数据即可。
  2. 把消息的尺寸与消息一块发送:在发送数据前,先发送数据的长度,接收方根据这个长度来接收数据。
  3. 双方约定每次传送的大小:发送方和接收方在通信前约定好每次传输的数据大小,确保数据按照约定的大小进行传输。
  4. 使用特殊标记来区分消息间隔:在数据包的末尾添加特定的分隔符,接收方根据这个分隔符来区分不同的数据包。

而UDP协议则不存在粘包问题。这是因为UDP协议是面向消息传输的,它会对每一个数据报进行封装,并添加UDP头和IP头等信息。在传输过程中,UDP会保持数据报的边界,接收方每次只接受一条独立的信息。因此,即使发送方连续发送多个数据报,接收方也会将它们作为独立的消息进行接收,不会出现粘包现象。

10. HTTP和HTTPS的区别,SSL的加密过程,如何知道服务端的数据是没问题的(简单)。

11. 如何判断链表是否有环,怎么实现(简单)。

12. 数据库的索引用什么数据结构,为什么用B+树而不是B树,为什么B+树更快(简单)。

13. 什么是SQL注入。

答:SQL注入是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,从而欺骗数据库服务器执行非授权的任意查询,进一步得到相应的数据信息。简而言之,SQL注入是攻击者在用户输入字段中插入恶意代码,欺骗数据库执行SQL命令,从而窃取、篡改或破坏各类敏感数据的行为。

SQL注入漏洞的形成原因主要在于数据交互过程中,前端数据传入后端进行处理时,没有做严格的判断,导致传入的“数据”在拼接到SQL语句后,由于其特殊性,被当作SQL语句的一部分被执行,从而导致数据库受损。

为了防止SQL注入攻击,可以采取多种防御措施,包括参数化查询、输入验证和过滤、最小权限原则、静态SQL替代动态SQL以及使用Web应用防火墙等。这些措施能够提高应用程序的安全性,减少SQL注入攻击的风险。

14. 稳定的排序算法有哪几种(简单)。

算法: 合并两个有序链表。

大厂校招实习最新面经解析 文章被收录于专栏

专注于最新各大厂最新面筋解析

全部评论
学长好!太厉害了
1
送花
回复
分享
发布于 03-23 21:33 吉林

相关推荐

7 44 评论
分享
牛客网
牛客企业服务