面试八股文整理版

1. 进程和线程的区别

  • 概念:进程是资源分配的最小单位,线程是cpu调度的最小单/位。
  • 地址空间:进程有独立的地址空间,一个进程可以有多个线程;线程没有自己独立的地址空间,而是共享进程的堆和方法区,但是有自己的程序计数器、虚拟机栈和本地方法栈。
  • 开销:进程和线程切换的都要进行上下文切换,进程切换上下文的开销远大于线程的上下文切换时间,耗费资源也较大,效率差一些。
  • 并发:进程粒度要粗,并发性较低,线程的并发性较高 。
  • 使用:每个进程有一个独立的进程入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在一个进程中,由进程提供多个线程的执行控制。
  • 内存:系统在运行时会为每个进程分配不同的内存空间;对于线程,除了cpu外,系统不会为线程分配内存。 
  • 一个进程崩溃后不会影响其他进程,但是一个线程崩溃后会导致整个进程都死掉,所以多进程比多线程更健壮。

2. 线程和协程的区别

  • 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
  • 线程进程都是同步机制,而协程则是异步。
  • 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
  • 协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程。
  • 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程。

3. 请你说说MySQL索引,以及它们的好处和坏处

        MYSQL索引分为普通索引,唯一索引,复合索引,主键索引,外键索引,全文索引。Mysql的索引是通过不同的数据结构实现,常见有B+tree、B-Tree、Hash等。

  • 好处:1. 能够提高数据的查询性能。 2. 能够提高数据分组、数据排序的性能。
  • 坏处:1.索引需要占用一定的存储空间    2. 对数据表的数据进行增、删、改、查时,MySQL内部需要对索引进行动态维护。

4. 请你说说多线程

  • 进程是操作系统资源调度的基本单位,线程是处理器任务调度和执行的基本单位,一个进程可以创建多个线程,每个线程有自己独立的程序计数器,本地方法栈和虚拟机栈,线程之间共享进程的堆和方法区。线程之间是通过时间片算法来争夺CPU的执行权的。
  • 多线程的好处:当一个线程进入阻塞或者等待状态时,其他的线程可以获取CPU的执行权,提高了CPU的利用率。
  • 多线程的缺点:可能产生死锁;频繁的上下文切换可能会造成资源的浪费;在并发编程中如果因为资源的限制,多线程串行执行,可能速度会比单线程更慢。
  • 业务中使用多线程的原因:1. 更多的CPU核心:程序使用多线程技术,就可以将计算逻辑分配到多个处理器核心上,显著减少程序的处理时间,从而随着更多处理器核心的加入而变得更有效率。2. 更快的响应时间:我们经常要针对复杂的业务编写出复杂的代码,如果使用多线程技术,就可以将数据一致性不强的操作派发给其他线程处理(也可以是消息队列),如上传图片、发送邮件、生成订单等。这样响应用户请求的线程就能够尽快地完成处理,大大地缩短了响应时间,从而提升了用户体验。3. 更好的编程模型:Java为多线程编程提供了良好且一致的编程模型,使开发人员能够更加专注于问题的解决。

5. 怎么保证线程安全

  • 线程安全问题是指在多线程背景下,线程没有按照我们的预期执行,导致操作共享变量出现异常。
  • 在Java中有许多同步方案提供给我们使用,从轻到重有三种方式:原子类、volatile关键字、锁。
  • 原子类是juc atomic包下的一系列类,通过CAS比较与交换的机制实现线程安全的更新共享变量。通过预期值与内存值的比较来判断是否修改。
  • volatile关键字是轻量级的同步机制,它实现了变量的可见性、防止指令重排序。保证了单个变量读写的线程安全。可见性问题是JMM内存模型中定义每个核心存在一个内存副本导致的,核心只操作他们的内存副本,volatile保证了一旦修改变量则立即刷新到共享内存中,且其他核心的内存副本失效,需要重新读取。
  • 原子类和volatile只能保证单个共享变量的线程安全,锁则可以保证临界区内的多个共享变量线程安全。java中常用的锁有两种:synchronized+juc包下的lock锁。synchronized锁是互斥锁,可以作用于实例方法、静态方法、代码块,基于对象头和Monitor对象,在1.6之后引入轻量级锁、偏向锁等优化。lock锁接口可以通过lock、unlock方法锁住一段代码,基于AQS实现,其加锁解锁就是操作AQS的state变量,并且将阻塞队列存在AQS的双向队列中。
  • 除了锁以外,juc包下还提供了一些线程同步工具类,如CountDownLatch、Semaphore等等,我们还可以使用ThreadLocal定义线程局部变量。

6. 请你说说死锁的定义及发生的条件

  • 什么是死锁: 两个或两个以上的线程因为争夺资源造成相互等待的现象,若无外力推动,他们将无法执行下去,这成为死锁
  • 死锁产生的四个条件: 请求与保持条件:当前进程持有至少一个资源,且又去请求其他资源,请求的资源还被占用,进程对于自己占用的资源还不释放。
  • 互斥条件:指进程对于自己占有的资源具有排他性,在一段时间内某资源只能有一个进程占用,其他进程想占用只能等待。
  • 不剥夺条件:对于进程所持有的资源,无论占用时间在长,也不会剥夺他的占用权。
  • 环路等待条件:死锁发生时,必定发生了环路等待,也就是环路集合:{p1、p2、p3...pn}也就是p1等待p2释放资源,p2等待p3释放资源、p3等待pn释放资源以及pn等待p1释放资源。

7. 进程间的通信方式

        进程间的通信方式包括:管道、有名管道、信号、信号量、消息队列、共享内存、内存映射、Socket套接字。

  • 管道:管道实质是内核中的一块内存缓存区。创建缓存后会返回两个文件描述符,分别是读与写。有名管道可以在不存在亲缘关系的进程中通信。管道的缺点:只能FIFO、数据无格式且大小受限、并且通信方式是单向的,若需要双向则需要建立两个通道。
  • 信号:是事件发生对进程的通知机制,它是软件层次上对中断机制的一种模式,是一种异步通信方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
  • 信号量:
  • 消息队列:
  • 共享内存:
  • 内存映射:
  • Socket套接字:

8. 对MVC的理解

 MVC是一种设计模式,在这种模式下软件被分为三层,即Model(模型)、View(视图)、Controller(控制器)。

  • Model:代表数据,指从现实世界中抽象出来的对象模型,是应用逻辑的反应;它封装了数据和对数据的操作,是实际进行数据处理的地方(模型层与数据库才有交互)。
  • View:负责进行模型的展示,一般就是我们见到的用户界面。
  • Controller:代表的是数据的处理逻辑,控制器负责视图和模型之间的交互,控制对用户输入的响应、响应方式和流程;它主要负责两方面的动作,一是把用户的请求分发到相应的模型,二是吧模型的改变及时地反映到视图上。 
  • 最典型的是是jsp+servlet+javabean模式,而现在MVC模式的最主流实现是Spring MVC框架。 Spring MVC框架是基于Java的实现了MVC框架模式的请求驱动类型的轻量级框架。前端控制器是DispatcherServlet接口实现类,映射处理器是HandlerMapping接口实现类,视图解析器是ViewResolver接口实现类,页面控制器是Controller接口实现类。

9. 谈谈Redis的数据类型

        redis中常用的五种数据结构:string、list、set、zset、hash。redis会在性能以及节省内存间考虑,选择最适合当前状态的底层数据结构实现

  • String:结构底层是一个简单动态字符串,支持扩容,存储字符串。
  • list:存储线性有序且可重复的元素,底层数据结构可以是双向链表/压缩列表。
  • set:存储不可重复的元素,一般用于求交集、差集等,底层数据结构可以是hash和整数数组。
  • zset:存储的是有序不可重复的元素,zset为每个元素添加了一个score属性作为排序依据,底层数据结构可以是ziplist和跳表。
  • hash:存储的是键值对,底层数据结构是ziplist和hash。

10. 谈谈乐观锁和悲观锁

  • 乐观锁:乐观锁总是假设最好的情况,每次去拿数据的时候默认别人不会修改,所以不会上锁,只有当更新的时候会判断一下在此期间有没有人更新了这个数据。适用于多读,可以使用版本号机制进行控制
  • 悲观锁:悲观锁总是假设最坏的情况,每次去拿数据是都认为别人会修改,所以每次在拿数据时都会上锁,这样别人想拿这个数据时会阻塞直到拿到锁。
  • mysql数据库的共享锁和排他锁都是悲观锁的实现。

11. 谈谈设计模式

  • 常用的设计模式有单例模式、工厂模式、代理模式、适配器模式、装饰器模式、模板方法模式等等。
  • 像sping中的定义的bean默认为单例模式,spring中的BeanFactory用来创建对象的实例,他是工厂模式的体现。AOP面向切面编程是代理模式的体现,它的底层就是基于动态代理实现的。适配器模式在springMVC中有体现,它的处理器适配器会根据处理器规则适配相应的处理器执行,模板方法模式用来解决代码重复的问题等

12. 谈谈对AOP的理解

  • AOP是面向切面编程。通过预编译方式和运行期间动态代理的方式实现不修改源代码的情况下给程序动态添加统一功能的技术。
  • 将那些影响了多个类的公共行为封装到一个可重用的模块,提高了代码的复用性,降低了代码的耦合度。
  • Spring AOP通过JDK动态代理和CGLib动态代理两种方式实现。常见的应用场景有日志管理和事物以及权限控制等。

13. 谈谈Redis的持久化策略

  • RDB: redis database 在指定的时间间隔内,将内存中的数据集的快照写入磁盘,文件名dump.rdb,适合大规模的数据恢复,对数据库的完整性和一致性要求不是很高,一定时间间隔备份一次,如果数据库意外down掉,就会失去最后一次快照的所有修改。
  • AOF: append only file 以日志的形式记录每个写操作,只允许追加文件,不允许改写文件,redis启动时会读取这个文件,并从头到尾执行一遍,以此来恢复数据,文件名appendonly.aof 在最恶劣的环境下,也丢失不会超过2秒的数据,完整性较高,但是会对磁盘持续的进行IO,代价太大。企业级最少需要5G才能支持 如果.aof文件大小超过原来的一倍,会进行重写压缩,保留最小的指令集合。
  • 优先级: AOF > RDB

14. 讲讲单例模式,手写一下单例模式

  • 单例模式:就是一个类只能创建一个实例对象,提供一个系统的全局访问点。
  • 饿汉式单例模式:类加载时就会创建一个对象。
  • public class Single{
        private static Single instance = new Single();
    }
  • 懒汉式单例模式:只有当类被引用时才会被创建一个实例对象。
  • public class Single{
        private static Single instance = null; 
        synchronized public static Single getInstance() {
            if(instance==null){
                instance = new Single();
                }
            return instance;
        }
    }
  • 单例模式的优点:适用于需要频繁创建和销毁的类,避免消耗更多的资源。缺点:无法创建子类,扩展困难

15. 说说虚拟内存和物理内存的区别

  • 物理内存:最早程序寻址使用的是物理地址,而程序寻址的范围取决于CPU地址线条数(例如32位平台寻址范围位2^32即4g)。即每开启一个程序就需要4g内存,导致内存利用率低下。同时由于程序指令是可以直接访问物理地址的,那么任何进程都可以访问到其他进程的数据并修改也是极度不安全的。
  • 虚拟内存:是计算机内存管理的一种技术,它使得应用程序认为它拥有一块连续完整的内存,而实际上,它通常是被分割开的多个物理内存碎片,并且还有一部分暂时存储在外部磁盘上,在需要时读取,这种方式提高了内存的利用率。同时它通过逻辑地址来对应物理地址,使得指令无法随意操作不同进程的地址空间保证了其安全性。

16. 说说对IOC的理解

  • IoC:控制反转。控制:对象的创建的控制权限;反转:将对象的控制权限交给spring。
  • 之前我们创建对象时用new,现在直接从spring容器中获取,维护对象之间的依赖关系,降低对象之间的耦合度。
  • 实现方式为DI,依赖注入,有三种注入方式:构造器、setter、接口注入

17. 说说内存管理

  • 内存管理主要包括内存的分配(malloc)和释放(free),内存的分配可以分为连续内存分配和非连续内存分配。连续分配:为用户分配一个连续的内存空间,常见的连续分配如块式管理。非连续分配又分为页式存储管理,段式存储管理和段页式存储管理。
  • Linux系统采用段页式存储管理,页式存储管理可以提高内存利用率,分段管理可以反应程序的逻辑结果并且有利于段的共享。两者结合起来就是段页式存储管理。首先将用户程序分成若干个段,然后每个段中分若干个页。为了从逻辑地址映射到物理地址需要设置段表和页表。段表中至少包含段号、页表长度、页表起始地址。页表至少包含页号和块号。每次先从段中找到页表始址,再在页表中找到帧号,进而映射成物理地址。

18. 谈谈IO多路复用

  • IO多路复用指的是单个进程或者线程能同时监听处理多个IO请求,select、poll、epoll是 LinuxAPI提供的复用方式。本质上是由操作系统内核缓存fd文件描述符,使得单个进程线程能监视多个文件描述符。
  • select是将所有文件描述符的集合从用户空间拷贝到内核空间,底层是数组。
  • poll和select相似,主要区别是底层采用链表,从而使得监听文件描述符个数不再受限,但是还是需要多次内核与用户的复制,并且用户空间需要通过轮询O(n)才能确定哪些文件描述符上发生了事件。
  • epoll底层采用红黑树,在内核空间创建需要关注的文件描述符的红黑树,内核监听时会将发生事件的描述符加入队列中,返回到用户空间的时候只需要返回队列中的数据即可。epoll通过这种方式使得减少了每次用户到内核的复制过程,同时用户空间通过O(1)复杂度就可以知道哪些文件描述符发生了事件。

19. 谈谈MySQL的事务隔离级别

  • 事务的隔离级别有:未提交读,已提交读,可重复读,串行化四种隔离级别。
  • 事务隔离级别是为了解决脏读、不可重复读、幻读问题。脏读:一个事务读取了另一个事务未提交的数据。不可重复读:事务A两次读取的数据不一致,读第二次之前可能有其他事务修改了这个数据并提交了。幻读:事务A两次读取数据库,两次查询结果的条数不同,称为幻读。行数变了即为幻读,数据变了即为不可重复度。
  • 未提交读:以上三个问题都解决不了。已提交读:只能解决脏读。可重复读:mysql的默认隔离级别,能解决脏读和不可重复读,包含了间隙锁,可以防止幻读。串行化:都可以解决。(为每个读取操作加一个共享锁)。

20. 如何用Redis实现一个分布式锁

  • 分布式环境下会发生多个server并发修改一条数据的情况,由于多个server是多个不同的JRE环境,而Java自带的锁局限于当前JRE,所以Java自带的锁机制在这个场景下是无效的,那么就需要我们自己来实现一个分布式锁。
  • 我们可以在redis中存放一份代表锁的数据。最简单的方式就是setnx key value,但是如果客户端忘记解锁就会造成死锁。
  • 如果给锁增加过期时间,setnx key value + expire key second会发生另一个问题,因为这两步并非原子性操作,如果第二步失败仍然会出现死锁问题。
  • 因此可以通过set key value nx ex seconds操作将两步封装成原子性的一步,解锁就是将代表锁的数据删除即可。但不能简单地del key因为如果进程A在没有执行完毕时锁到期释放了,但进程A结束后仍然会释放锁,这时候就会释放其他进程的锁。所以要给key赋一个随机值代表进程加个标识,进程A释放锁时进行判断,是自己的再释放锁。另外释放这部分要保证原子性否则会死锁。
  • 可以使用Lua脚本将指令编排到一起,Lua执行是原子性的。可以使用RedLock算法和Redisson框架实现多节点高可用分布式锁。

21. 说说对反射的了解

  • 反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它所有的方法和属性,对于任意一个对象,都能知道他的属性和方法。
  • 获取Class对象的三种方式:getClass(); xx.class; Class.forName("xxx");/
  • 反射的优点:运行期间能够动态的获取类,提高代码的灵活性。
  • 反射的缺点:性能比直接的Java代码要慢很多。
  • 应用场景:spring的xml配置模式,以及动态代理模式都用到了反射;使用JDBC时如果要创建数据库连接要通过反射机制加载数据库驱动程序;AOP实现方案是在程序运行时通过反射机制创建目标对象代理类。

22. 说说ArrayList和LinkedList的区别

  • ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。
  • 对于随机访问ArrayList要优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问,而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,查找某个元素的时间复杂度是O(N)。
  • 对于插入和删除操作,LinkedList要优于ArrayList,因为当元素被添加到LinkedList任意位置的时候,不需要像ArrayList那样重新计算大小或者是更新索引。
  • LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

23. 说说聚簇索引和非聚簇索引

  • 聚簇索引是将数据与索引存储到一起,找到索引也就找到了数据;而非聚簇索引是将数据和索引存储分离开,索引树的叶子节点存储了数据行的地址。
  • 在InnoDB中,一个表有且仅有一个聚簇索引(因为原始数据只留一份,而数据和聚簇索引在一起),并且该索引是建立在主键上的,即使没有指定主键,也会特殊处理生成一个聚簇索引;其他索引都是辅助索引,使用辅助索引访问索引外的其他字段时都需要进行二次查找。 而在MyISAM中,所有索引都是非聚簇索引,叶子节点存储着数据的地址,对于主键索引和普通索引在存储上没有区别。

24. 数据库为什么不用红黑树而用B+树

  • 红黑树是一种近似平衡二叉树(不完全平衡),结点非黑即红的树,它的树高最高不会超过 2*log(n),因此查找的时间复杂度为 O(log(n)),无论是增删改查,它的性能都十分稳定; 但是,红黑树本质还是二叉树,在数据量非常大时,树的高度过高导致查询效率变慢。需要访问+判断的节点数也会比较多,同时数据是存在磁盘上的,访问需要进行磁盘IO,导致效率较低。
  • 而B+树是多叉的,可以有效减少磁盘IO次数;同时B+树增加了叶子结点间的连接,能保证范围查询时找到起点和终点后快速取出需要的数据。

25. 讲讲工厂模式,手写实现工厂模式(暂未完成)

  • 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
  • 工厂模式有 3 种不同的实现方式:简单工厂模式(静态工厂):通过传入相关的类型来返回相应的类,这种方式比较单一,可扩展性相对较差。工厂方法模式:通过实现类实现相应的⽅法来决定相应的返回结果,这种方式的可扩展性比较强。抽象工厂模式:基于上述两种模式的拓展,且⽀持细化产品。

26. 你知道哪些线程安全的集合

  • java.util包下的集合大部分都是非线程安全的,如实现List接口的ArrayList,LinkedList,实现set接口的HashSet类,实现SortSet接口的TreeSet类。
  • 但是也有极少数的线程安全的集合,如Vector,HashTable等,但是它们是基于Synchronized关键字实现的,性能很差,在开发中不常用。
  • 一般可以使用collections工具类中方法将非线程安全的集合类包装成线程安全的类。同时java5之后,concurrent包中提供了大量的支持并发访问的集合类。例如ConcurrentHashMap和CopyOnWriteArrayList等。

27. 请你说说ConcurrentHashMap

  • ConcurrentHashMap的底层数据结构采用的和HashMap一样,即采用“数组+链表+红黑树”的形式。同时,它又采用锁定头结点的方式降低了锁粒度,以较低的性能代价实现了线程安全。
  • 线程安全的实现机制:1. 初始化头结点时,并没有加锁,而是以CAS的方式进行原子替换。  2. 插入数据时会进行加锁处理,但是锁的是头节点,而不是整个数据,降低了锁的范围,并发性能很好,3. 扩容时会进行加锁操作,锁定的仍然是头节点。4. 查询数据时不会进行加锁,所以性能很好。
  • ConcurrentHashMap与Hashtable的区别:HashTable要求key和value必须不能为null。Hashtable中实现线程安全的代价较大,锁的范围更大,将put和get方法的作用操作都加锁,相当于锁住了整个哈希表。

28. 缓存穿透、击穿、雪崩的区别

  • 缓存穿透:客户端访问不存在的数据,使得请求直达存储层,导致负载过大,直至宕机。原因可能是业务层误删了缓存和库中的数据,或是有人恶意访问不存在的数据。解决方式:1.存储层未命中后,返回空值存入缓存层,客户端再次访问时,缓存层直接返回空值。2.将数据存入布隆过滤器,访问缓存之前经过滤器拦截,若请求的数据不存在则直接返回空值。
  • 缓存击穿:一份热点数据,它的访问量非常大,在它缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。解决方案:1.永不过期:对热点数据不设置过期时间。2.加互斥锁,当一个线程访问该数据时,另一个线程只能等待,这个线程访问之后,缓存中的数据将被重建,届时其他线程就可以从缓存中取值。
  • 缓存雪崩:大量数据同时过期、或是redis节点故障导致服务不可用,缓存层无法提供服务,所有的请求直达存储层,造成数据库宕机。解决方案:1.避免数据同时过期,设置随机过期时间。2.启用降级和熔断措施。3.设置热点数据永不过期。4.采用redis集群,一个宕机,另外的还能用

29. Redis如何与数据库保持双写一致性

保证双写一致性共有四种方案:

  • 先更新缓存,再更新数据库。该方式的优点是能够保证数据访问是都会命中redis,降低数据库压力。缺点是每次修改都会操作缓存降低了服务器的性能。
  • 先更新数据库,再更新缓存。缺点是可能导致数据库和redis中的数据不一致(数据库更新成功,redis更新失败)。
  • 先删除redis,再更新数据库。缺点:可能导致数据库和redis中的数据都是旧数据(删除redis后,再更新数据时失败了)。
  • 先更新数据库,再删除redis。缺点:可能导致数据库和redis的数据不一致(redis删除失败)。出错时可以使用重试机制异步重新处理,是效果最好的解决方案。

30. 说说你了解的线程同步的方式

  • Java通过加锁实现线程同步,锁有两类:synchronized和Lock。
  • synchronized加在三个不同的位置,对应三种不同的使用方式,这三种方式的区别是锁对象不同:(1)加在普通方法上,则锁是当前的实例(this)。 (2)加在静态方法上,锁是当前类的Class对象。 (3)加在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。
  • Lock支持的功能包括:支持响应中断、支持超时机制、支持以非阻塞的方式获取锁、支持多个条件变量(阻塞队列)。

31. 说说innodb和myisam的区别

  • innodb支持事务,事务安全;myisam不支持事务,非事务安全。
  • Innodb支持行级锁;myisam支持表级锁。
  • Innodb的增删改性能更优;Myisam的查询性能更优。
  • Innodb不支持全文索引,myisam默认支持。
  • Innodb默认支持外键,而myisam不支持。

32. String、StringBuffer、Stringbuilder有什么区别

  • StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
  • String: 不可变字符序列,效率低,但是复用率高。
  • StringBuffer: 可变字符序列、效率较高(增删)、线程安全

  • StringBuilder: 可变字符序列、效率最高、线程不安全

33. 说说HashMap的底层原理

  • 在1.8之前,HashMap的底层是数组+链表,在1.8之后是数组+链表+红黑树;
  • 它的put流程是:基于哈希算法来确定元素位置,当我们向集合存入数据时,它会计算传入的key的哈希值,并利用哈希值取绝对值再根据集合长度取余来确定元素的位置,如果这个位置已经存在其他元素了,就会发生哈希碰撞,则hashmap就会通过链表将这些元素组织起来,如果链表的长度达到8时,就会转化为红黑树,从而提高查询速度。
  • 扩容机制:HashMap中数组的默认初始容量为16,当达到默认负载因子0.75时,会以2的指数倍进行扩容。 Hashmap是非线程安全的,在多线程环境下会产生循环死链,因此在多线程环境下建议使用ConcurrentHashMap。

34. 说说你了解的JVM内存模型

  • JVM由三部分组成:类加载子系统、执行引擎、运行时数据区。
  • 类加载子系统:可以根据指定的全限定名来载入类或接口。
  • 执行引擎,负责执行那些包含在被载入类的方法中的指令。
  • 当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果,等等,JVM会把这些东西都存储到运行时数据区中,以便于管理。而运行时数据区又可以分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。
  • 运行时数据区是开发者重点要关注的部分,因为程序的运行与它密不可分,很多错误的排查也需要基于对运行时数据区的理解。在运行时数据区所包含的几块内存空间中,方法区和堆是线程之间共享的内存区域,而虚拟机栈、本地方法栈、程序计数器则是线程私有的区域,就是说每个线程都有自己的这个区域。

35. 说说JVM的垃圾回收机制

  • 新生代收集:目标为新生代的垃圾收集。针对新生代的垃圾收集器有Serial、ParNew、Parallel Scavenge。
  • 老年代收集:目标为老年代的垃圾收集,针对老年代的垃圾收集器有CMS、Serial Old、Parallel Old。
  • 混合收集:目标为整个新生代及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。
  • 整堆收集:目标为整个堆和方法区的垃圾收集。
  • 在上述收集器中,常见的组合方式有: 1. Serial + Serial Old,是客户端模式下常用的收集器。 2. ParNew + CMS,是服务端模式下常用的收集器。 3. Parallel Scavenge + Parallel Old,适用于后台运算而不需要太多交互的分析任务。

36. 说说类加载机制

  • java中的类不是一开始就全部加载好的,因为这样对性能和内存的要求很高,而是使用到某一个类才加载某一个类。
  • 一个类对象的生命周期包括:加载,验证,准备,解析,初始化,使用和卸载这7个周期,而类加载这是前5个步骤。
  • 加载过程主要完成:1,将类按照其全限定名获取其二进制字节流文件。2,将获得的二进制流文件中的静态结构转换为方法区的运行时结构。3,在方法区中生成该类的类对象作为数据的入口。
  • 验证过程主要是检查该二进制字节流是否符合当前虚拟机标准避免发生安全问题。
  • 准备阶段主要是将类中定义的类变量按照固定的标准进行分配空间和第一次初始化,这里不会为实例变量分配,而final变量在编译过程中就已经被分配了。解析阶段是将常量池中的符号引用转化为直接引用的过程(例如存入某个字面量进入运行时常量池)。
  • 初始化阶段执行类的构造器的阶段,包括静态代码块的执行,jvm会使用同步机制来保证初始化阶段的类构造器指挥被执行一次从而避免类的重复加载。

37. 讲讲epoll原理

epoll 是一种更加高效的 IO 复用技术,epoll 的使用步骤及原理如下: 1. 调用 epoll_create() 会在内核中创建一个 eventpoll 结构体数据,称之为 epoll 对象,在这个结构体中有 2 个比较重要的数据成员,一个是需要检测的文件描述符的信息 struct_root rbr(红黑树),还有一个是就绪列表struct list_head rdlist,存放检测到数据发送改变的文件描述符信息(双向链表); 2. 调用 epoll_ctrl() 可以向 epoll 对象中添加、删除、修改要监听的文件描述符及事件; 3. 调用 epoll_wt() 可以让内核去检测就绪的事件,并将就绪的事件放到就绪列表中并返回,通过返回的事件数组做进一步的事件处理。 epoll 的两种工作模式: 1. LT 模式(水平触发) LT(Level - Triggered)是缺省的工作方式,并且同时支持 Block 和 Nonblock Socket。在这种做法中,内核检测到一个文件描述符就绪了,然后可以对这个就绪的 fd 进行 IO 操作,如果不作任何操作,内核还是会继续通知。 2. ET 模式(边沿触发) ET(Edge - Triggered)是高速工作方式,只支持 Nonblock socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 检测到。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 进行 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。 ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件描述符的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

38. 说一下接口和抽象类的区别

  • 抽象类多用于在同类事物中有无法具体描述的方法的场景,而接口多用于不同类之间,定义不同类之间的通信规则。
  • 接口只有定义,而抽象类可以有定义和实现。
  • 接口需要实现implement,抽象类只能被继承extends,一个类可以实现多个接口,但一个类只能继承一个抽象类。
  • 抽象类倾向于充当公共类的角色,当功能需要累积时,用抽象类;接口被运用于实现比较常用的功能,功能不需要累积时,用接口。

39. 说说==与equals()的区别

  • 值类型是存储在内存中的堆栈,而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中。
  • ==操作比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。equals操作表示的两个变量是否是对同一个对象的引用,即堆中的内容是否相同。
  • ==比较的是2个对象的地址,而equals比较的是2个对象的内容。
  • 显然,当equals为true时,==不一定为true。

40. 说说synchronized的用法及原理

  • 用法:1. 静态方法上,则锁是当前类的Class对象。 2. 作用在普通方法上,则锁是当前的实例(this)。 3. 作用在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。 能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。
  • 原理:在jvm里锁对象头中有一个markword用来记录锁对象的状态,分别为无锁,偏向锁,轻量级锁,重量级锁四种状态。最开始锁处于无锁状态,当第一个线程请求该锁并获得以后,该锁的状态便变为偏向锁状态,并且线程对象会记录该锁表明当前获取到了该锁,在偏向锁状态中并不会有任何阻塞同步操作,甚至cas操作也不需要。直到有其它线程开始请求锁时,锁就会不再进入偏向锁状态。在后续中当一个线程请求到锁会先使用CAS操作来避免互斥同步带来的性能消耗直到CAS操作失败。这一时段内锁处于轻量级锁状态。失败后严格按照互斥同步处理,此时锁就处于重量级锁状态。

41. 说说你对AQS的理解

  • AQS是一个多线程同步器,是一个用来构建锁的基础框架,JUC包中很多组件都依靠它作为底层实现,例如CountDownLatch,Semaphore等组件。
  • AQS提供了两种锁机制分别是共享锁(读锁)和排他锁(写锁)。共享锁指多个线程共同竞争同一个共享资源时多个线程同时都能够获得,而排他锁在同一时间内只能有一个线程能够获得共享资源。
  • AQS通过设置一个由volatile修饰的Int类型的互斥变量state来实现互斥同步,state=0时表明该锁可以被获取,state>=1时时表明该锁已经被获取。当当前锁为空闲时,多个线程同时使用CAS方式去修改State的值(保证了原子性,可见性),最后只有一个线程修改成功并获得锁。其他线程失败后便会执行unsafe类的park方法进行阻塞。这里的阻塞方式创建一个双向队列顺序存储锁竞争的线程,并先进先出的以公平锁方式去获取锁。非公平锁方式是不管双向链表队列中是否有阻塞的线程它都不会进入队列而是直接去尝试修改互斥变量以获得锁。

42. Java哪些地方使用了CAS

  • 原子类:以AtomicInteger为例,某线程调用该对象的incrementAndGet()方式自增时采用CAS尝试修改它的值,若此时没有其他线程操作该值便修改成功否则反复执行CAS操作直到修改成功。
  • AQS:是一个多线程同步器的基础框架,许多组件都使用到了该框架,通过改变其中state变量的值来进行加锁解锁操作。多个线程竞争该锁时就是采用CAS的方式来修改这个状态码,修改成功则获得锁,失败则进入同步队列等待。
  • 一些具有同步作用的容器,例如ConcurrentHashmap,它采用Synchronize+CAS+Node的方式实现同步。无论是数组的初始化,数组的扩容还是链表节点的操作都是先采用CAS进行操作。

43. 说说JVM的垃圾回收算法

  • jvm中的垃圾回收算法主要包括:标记-清除算法,标记-整理算法和复制算法。
  • 标记清除算法:分为标记和清除两个阶段,在标记阶段中会通过可达性分析算法找到不需要的对象并在它们的对象头上标记。在清除阶段会将为标记的对象清除并返回得到空闲块,同时维护了一种空闲列表存储这些空闲块,若两个空闲块物理地址上没有间隔就会合并成同一个块,在给对象分配内存时会从变量空闲列表找到内存大于等于分配对象的块,若等于则直接返回;若大于则分为两块返回一块保留一块。缺点:标记和清除过程效率都不高还会造成空间碎片化,适用于老年代的垃圾回收。
  • 标记整理算法:它的标记阶段和标记-清除算法相同,标记完后会将存活下来的对象向内存的一侧移动,然后回收边界外的内存。不会造成内存碎片化,但是大量对象的移动效率很低。适用于老年代收集。
  • 复制算法:将内存区域分为大小相等的两块,每次只使用其中一块,垃圾回收时将存活对象移动到另一块,该块内存全部回收。该算法空间利用率很低适合回收频率很高的新生带。

44. 请你说说Redis数据类型中的zset,它和set有什么区别?底层是怎么实现的?

  • set存放的是无序不可重复的数据。zset存放的是有序不可重复的数据,主要通过给每个元素添加分数属性来实现排序。
  • zset底层使用的是压缩列表以及跳跃表,当元素数量小于128个,所有member的长度都小于64字节,是使用压缩列表。不满足这两个条件时使用跳跃表。
  • set的底层是整数集合和哈希表。所以增删改查的复杂度都是1。

45. 说说static修饰符的用法

  • static修饰变量:属于静态变量也叫类变量,直属于类对象而不是实例,可以通过类名访问,它一般会在类加载过程中被初始化。生命周期贯穿整个程序。存储在方法区中。
  • static修饰方法:即静态方法,一个类中的静态方法不能访问该类的实例变量,只能访问静态变量。同时还存在一个静态初始化块,他在类加载过程中被调用用于对该类中的静态变量进行操作。
  • static修饰类:即静态内部类,他只能以内部类的形式存在,可通过外部类的类名调用。它是他也只能访问到外部的的静态成员。

46. 说说线程的状态

  • NEW、RUNNABLE、BLOCKED、WTING、TIMED_WTING、TERMINATED
  • 新建态(NEW):当一个线程被创建成功后,但并没有执行它的start方时处于该状态。
  • 就绪态(RUNNABLE):一个线程执行了start方法进入就绪态开始竞争cpu调度权但还没有竞争到以完成它的任务
  • 运行态(BLOCKED):一个线程对象获取到了cpu的资源调度权,并进入允许态开始完成它的任务。
  • 阻塞态(WTING):若一个运行中的线程存在同步操作,此时锁被其他线程占用,该线程就会进入阻塞态等待获取锁
  • 限期等待(TIMED_WTING):正在运行的线程执行了Thread.spleep()方法或者设置了timeout的wait()方法,join方法等进入一定时间的等待,系统自动唤醒。
  • 不限期等待(TERMINATED):正在运行的线程执行了未设置timeout的wait方法或join方法进入等待,只有通过其他线程使用interrupt()或notify方法对其进行唤醒。
  • 死亡态:线程成功执行完毕或执行中抛出异常中断了线程会进入死亡态。

47. 说说你对ThreadLocal的理解

  • ThreadLocal即线程变量,它用于共享变量在多线程中的隔绝,即每个线程都有一个该变量的副本彼此互不影响也就不需要同步机制了。
  • 实现原理:每个Thread对象中都有一个ThreadLocal类的内部类ThreadLocalMap对象,他是一个键值形式的容器,以ThreadLocal对象的get和set方法来存取共享变量值,原理时:以ThreadLocal对象作为key来存取共享变量值。一个ThreadLocal用完后必须remove,否则会造成内存泄漏。

48. 说说Spring Boot常用的注解

  • @SpringBootApplication:它是SpringBoot的核心注解,用于开启自动配置,准确的说是通过该注解内的@EnablAutoConfiguration注解实现的自动配置。
  • @EnableAutoConfiguration:自动配置注解,在启动Spring应用程序上下文时进行自动配置,自动配置通常是基于项目classpath中引入的类和已定义的bean来实现的。
  • @Import:@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。
  • @Congiguration:配置类注解,根据一些特定条件来控制bean的实例化的行为。
  • @ComponentScan:位置在SpringBoot的启动类上,Spring包扫描。

49. 说说Bean的生命周期

  • 创建,初始化,调用,销毁;
  • bean的创建方式有四种,构造器,静态工厂,实例工厂,setter注入的方式。
  • spring在调用bean的时候因为作用域的不同,不同的bean初始化和创建的时间也不相同。 在作用域为singleton的时候,bean是随着容器一起被创建好并且实例化的, 在作用域为pritotype的时候,bean是随着它被调用的时候才创建和实例化完成。 然后程序就可以使用bean了,当程序完成销毁的时候,bean也被销毁。

50. synchronized和Lock有什么区别

  • Synchronize和Lock都是多线程实现同步的方式。
  • Synchronize根据作用的对象不同其加锁方式也就不同,作用于静态方法,以类对象加锁,作用域实例方法以类对象实例加锁,作用于类也是以类对象加锁,再加锁中使用Objct自带的wait方法或Thread类的静态sleep方法非静态yield等一系列方法来实现线程之间的交互。
  • lock一般在某个方法中显示的运用其lock方法和unlock方法进行加锁解锁,所以synchronize不需要显示的去释放锁,使用condition对象来实现线程之间的交互。
  • Synchronize是基于jvm底层实现的,同时引入锁的四种状态:无锁,偏向锁,轻量级锁,重量级锁。根据不同的状态使得线程采用不同的加锁方式以提高同步效率。
  • Lock是基于AQS多线程同步器实现的,底层维护了一个int类型的变量state用来表示锁的状态,state=0表示锁可以获得,state>0表示锁已经被占用。多个线程以CAS的方式去修改state的值以实现锁的竞争。
  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

f~shuai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值