自己总结的面试题

(Java基础)

一.ReenTrantLock和Synchronized区别和原理

相同点:

  1.都是可重入锁

  2.堵塞式同步,当一个线程获取到了对象锁,进入到了同步块,其他访问该同步块的线程都必须进入堵塞状态

  3.加锁方式同步

不同点:

  1.sychronized通过JVM加锁解锁;reentrantLock需要手动加锁解锁

  2.sychronized不可中断,除非抛出异常;reentrantLock可中断,如果线程长期不释放锁,正在等待的线程可以放弃等待

  3.reentrantLock提供更高级的功能,比如isFair(),isLocked()

 

Synchronized原理:

  synchronized进行编译的时候,会在同步块前后形成monitorenter和monitorexit这两个字节码指令,执行monitorenter指令时,会尝试获取对象锁,如果对象没有被锁定,或者当前线程已经拥有了这把对象锁时,就会在锁的计算器上加1,在执行monitorexit指令时,就会将锁的计算器减1,当这个锁的计算器为0时,这个锁就被释放了,如果获取对象锁失败,那当前线程就会进行堵塞,直到对象锁被另一个线程释放

  monitorenter:

    每个对象都有一个监视器锁,当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时会尝试获取monitor的所有权,过程如下:

      1.当monitor的进入数为0时,则该线程就会进入monitor,然后将进入数设置为1,该线程就是monitor的所有者

      2.当线程已经占有这个monitor,只是重新进入,则进入monitor的进入数加1

      3.当其他线程已经占用这个monitor,则该线程进入堵塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权

  monitorexit:

    执行monitorexit的线程必须是objecttref所对应的monitor的所有者

    执行指令时,monitor的进入数减1,如果减1后进入数为0,则线程就退出monitor,不再是这个monitor的所有者,其他被这个monitor堵塞的线程就可以尝试获取这个monitor的所有权

 

ReentrantLock原理:

  比synchronized提供了更高级的功能,主要有以下3项:

    1.等待可中断锁:

      只有锁的线程长期不释放的时候,正在等待的线程可以放弃等待

    2.公平锁:

      多个线程等待同一个锁时,必须按照申请锁的时间获得锁

    3.锁绑定多个条件:

      一个ReentrantLock对象可以绑定多个对象,提供了一个Condition类,用来实现分组唤醒需要唤醒的线程,而不是像synchronized要么随机唤醒一个,要么全部都唤醒

 

二.Mysql的事务

事务的基本要素:ACID

  A(原子性):要么不做,要么全部做完,事务执行过程中出错,会进行回滚

  C(一致性):事务开始前和结束后,数据库的完整性约束没有被破坏

  I(隔离性):同一时间,只允许一个事务请求同一数据,不同事务之间彼此不能有任何干扰

  D(持久性):事务完成后,事务对数据库的所有更新将保存到数据库中,不能回滚

事务的并发问题:

  脏读:事务A读取了事务B更新后的数据,此时事务B进行了回滚,事务A读到的数据是脏数据

  不可重复读:事务A多次读取同一数据,事务B在事务A读取的过程中,对数据进行了修改并提交,导致事务A多次读取数据,结果不一致

  幻读:A将具体成绩改成了等级,此时B插入了一条带有具体成绩的数据,A结束后发现还有一条记录没有改过来

  不可重复读是侧重修改数据,幻读侧重是新增或者删除数据,不可重复读要锁住满足条件的行,幻读需要锁住表

事务隔离级别:

  

 

   Mysql默认的隔离级别是可重复读

  当级别是读未提交时,写数据时锁住对应的行

  当级别是可重复读时,如果有索引,默认加锁方式是next-key,如果没有索引,更新数据会锁住整个表

  当级别是串行化时,读写数据都会锁住整张表

 

三.如何创建线程

1.继承Thread类并重写run()方法

2.实现Runable接口,必须重写run方法,没有返回值

3.实现Callable接口,重写call()方法,有返回值

 

四.线程池

创建和销毁对象很费时间,所以事先创建好多个可执行的线程放到线程池中,需要的时候直接从线程池中获取,使用完后不需要销毁线程而是把它放回线程池中,从而减少创建和销毁线程对象的开销

构造器中各个参数的含义:

  核心线程数:创建了线程池之后,线程池中没有任何线程,只有等待有任务到来的时候才会去创建线程执行任务,除非调用prestartAllCoreThreads()或者prestartCoreThread()方法,当线程池中的线程数达到核心线程数后,会把任务放到缓存队列中

  最大线程数:标识线程池最多能创建多少个线程

  存活时间:默认情况下,只有当线程池中的线程数大于核心线程数,才会起作用

  存活单位:单位

  堵塞队列:存储等待执行的任务,一般使用LinkedBolockingQueue和Synchronous

  线程工厂:用来创建线程

  拒绝策略:不再接受任务下发

 

五.抽象类和接口的区别

抽象类:

  含有abstarct修饰符的class就是抽象类,抽象类不能创建实例对象,必须在子类中实现具体行为,不能有抽象构造函数或抽象静态方法,如果子类没有实现抽象父类的所有抽象方法,那么子类也必须定义chengabstarct类型

接口:

  是抽象类的一种特例,接口中所有方法都是抽象的,默认是public abstarct类型,接口的成员变量默认是public static final

两者语法区别:

  1.抽象类可以有构造方法,接口不能有构造方法

  2.抽象类可以有普通成员变量,接口没有普通成员变量

  3.抽象类可以包含非抽象方法,接口中所有方法必须是抽象的

  4.抽象类的抽象方法的访问类型可以是public或者protected类型,接口中只能是public类型

  5.抽象类可以包含静态方法,接口中不能包含静态方法

  6.抽象类和接口都可以包含静态成员变量,抽象类的静态成员变量的访问类型是任意的,但接口只能是public static final类型

  7.一个类只能继承一个抽象类,但是可以实现多个接口

 

六.抽象类是否可以使用final修饰

不可以使用final修饰,抽象类就是为了让其他类继承,使用了final,则代表不可以被继承

 

七.面向对象的特征

  封装:把一个对象的属性进行私有化,同时提供一些可以被外界访问属性的方法

  继承:在已存在的类的定义作为基础建立新的类,新建立的方法可以增加新的功能

  多态:父类引用指向子类对象(实现多态的三个必要条件:继承,重写,向上转型)

 

八.IO流

按照流的流向划分:输入流,输出流

按照操作单元划分:字节流,字符流

按照流的角色划分:节点流,处理流

InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流

OutPutStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流

 

九.BIO,NIO,AIO的区别

BIO:同步堵塞IO,模式简单,并发处理能力低

NIO:同步非堵塞IO,客户端和服务器通过Cannel(信道)实现通讯,实现了多路复用

AIO:是NIO的升级,实现了一步非堵塞IO,异步IO的操作基于事件和回调机制

 

十.什么是反射

反射机制:对于任意一个类,都可以知道它的方法及属性,对于任意的对象,都可以调用它的方法和属性,这种动态多去信息以及动态调用对象的方法的功能就是反射

反射的优缺点:

  优点:运行期类型的判断,动态加载类,提高代码的灵活度

  缺点:性能瓶颈,需要通知JVM做事,比直接的java代码慢

反射机制的应用场景:

  1.使用JDBC连接数据库时使用Class.forName()

  2.Spring框架的XML配置,通过XML配置状装载Bean,

    具体过程:将XML或者properties文件加载到内存中,Java类解析XML或properties的内容,得到对应的信息,利用反射机                                        制,获取到类的Class实例,并动态的配置实例的属性

Java获取反射的三种方法:

  1.通过路径实现反射    Class clazz = Class.forName("xxxx")

  2.通过new对象实现反射    Student s = new Student()    Class clazz = s.getClass()   clazz.getName()

  3.通过类名实现反射    Class clazz = Student.class()

 

十一.String常用方法

indexOf():返回指定字符串的索引

trim():去除字符串两端的空白

length():返回字符串长度

equals:字符串比较

replace():字符串替换

 

十二.集合

Collection:

  List:有序可重复,可以插入多个null元素,元素都有索引

  Set:无序不重复,只能存放一个null元素

Map:键值对(key-value)集合,key无序且唯一

底层:

  List:

    ArrayList:动态数组

    LinkedList:双向循环链表

    Vector:数组

  Set:

    HashSet:无序且唯一,基于HashMap实现,底层使用HashMap保存元素

    LinkedHashSet:继承HashSet,底层通过LinkedHashMap实现

    TreeSet:有序且唯一,底层是红黑树(自平衡二叉树)

  Map:

    HshMap:在脑子里

    LinkedHashMap:继承HashMap,增加了一条双向链表,可以保持键值对的插入顺序

    HashTable:数组+链表

    TreeMap:红黑树(自平衡的二叉树)

快速失败机制(fail-fast):

  线程A通过Iterator遍历元素,线程B修改元素结构,就会快速抛出异常,产生fail-fast机制

  原因:迭代器遍历元素时,会改变modCount值,每次使用hasNext遍历下一个元素时,都会检测modCount

 

十三.Iterator和ListIterator的区别

1.Iterator可以遍历List和Set,ListIterator只能遍历List

2.Iterator只能单向遍历,ListIterator可以双向遍历

3.ListIterator实现了Iterator接口,添加了新的功能,比如添加元素,替换元素等

 

十四.如何实现数组和List的转换

数组转List:Array.asList(array)

集合转数组:List.toArray()

 

十五.造成死锁的四个条件以及如何避免死锁

1.互斥条件:一个资源只能被一个线程获取到,一直到被这个线程释放(无法破坏)

2.请求与保持条件:一个线程占用资源发生堵塞时,对已获得的资源保持不放(一次性申请所有的资源)

3.不剥夺条件:线程获取到资源后再未使用完前不能被其他线程剥夺(申请不到,主动释放占有的资源)

4.循环等待条件:发生死锁时,会死循环,造成堵塞(按顺序申请资源)

 

十六.Java程序如何保证多线程的安全

1.使用原子类AtomicInteger

2.使用自动锁synchronized

3.使用手动锁Lock

 

十七.Volatile

从可见性而言:保证可见性和禁止指令重排,它确保一个线程的修改能对其他线程是可见的,修改后会立即更新到内存中,其他线程读取时,直接读取内存

从实践角度而言:与CAS结合,保证了原子性,比如AtomicInteger

Volatile是线程同步的轻量级实现,所以性能比synchronized关键字好,但是后来synchronized减少了获取锁和释放锁带来的性能的消耗,实际使用synchronized比较多

 

十八.乐观锁和悲观锁的理解

悲观锁:假设最坏的情况,认为每次拿数据都会被修改,所以就会上锁,synchronized就是悲观锁

 

乐观锁:认为每次拿数据不会被修改,但是更新的时候会判断在此期间别人有没有去更新数据,会使用版本号判断

    实现方式:使用版本号确定读取到的数据和提交时的数据是否一致,提交会后修改版本号,不一致时会才去丢弃或者再次尝                                     试的策略

 

十九.CAS

CAS操作包含三个操作数:内存位置(V),预期原值(A),新值(B)

如果内存地址里面的值和A一样,就将内存里面的值改成B,CAS通过无限循环获取数据,若在第一轮循环中,线程A获取地址里面的值被线程B修改了,线程A就需要自旋

会产生一些问题:

  资源竞争激烈的时候,CAS自旋概率比较大,浪费CPU资源

 

二十.Executors类创建四种常见线程池

1.newSingleThreadExecutor:创建一个单线程的线程池

2.newFixedThreadPool:创建一个固定大小的线程池

3.newScheduledThreadPool:创建一个大小无限的线程池

4.newCachedThreadPool:创建一个可缓存的线程池

 

(Mysql)

二十一.Mysql中Myisam和Innodb的区别

1.Innodb支持事务,Myisam不支持事务

2.Innodb支持行级锁,Myisam支持表级锁

3.Innodb不支持全文检索,Myisam支持全文检索

 

二十二.事务隔离级别

读未提交

读已提交

可重复读(默认)

串行化

 

二十三.为什么Mysql的索引使用的是B+树而不是其他树结构

B-Tree:不管是叶子节点还是非叶子节点都会保存数据,导致在非叶子节点中保存的指针数量变少,增加树的高度,导致IO操作变多,性能变低

Hash:没有顺序,IO复杂度变高

二叉树:高度不均匀,也不能自平衡,查找效率是跟树的高度有关,IO代价高

红黑树:树的高度随着数据的增加而增加,IO代价高

 

二十四.MyISAM和InnoDB实现B+Tree索引方式的区别

MyISAM:叶子节点的data域存放数据记录的地址,索引检索时,按照B+Tree搜索算法搜索索引,如果指定的Key存在的话,就取出对应的data值,再读取相应的数据

InnoDB:一张表的话一定包含一个聚集索引构成的B+Tree和多个非聚集索引的B+Tree

 

二十五.为什么不对每个表都创建索引

1.对表中数据进行增加,删除,修改,索引也需要去进行维护,降低了数据的维护速度

2.索引占用物理空间,如果是聚簇索引,需要的空间更大

3.创建索引和维护索引,需要耗费时间

 

二十六.Mysql执行查询过程

1.客户端通过TCP连接发送连接请求到mysql连接器,连接器会进行权限验证以及资源分配

2.判断缓存是否被命中,Mysql不会去解析SQL语句

3.语法判断,检查数据表和数据列是否存在

4.优化,是否使用索引

5.交给执行器,将数据保存到结果集中,并将数据缓存到查询缓存中,将结果返回给客户端

 

二十七.MVCC

多版本并发控制,通过保存数据的某时间点的快照来实现,根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能不一样

实现原理:InnoDB的每一行数据都有一个隐藏的回滚指针,指向修改前的最后一个历史版本,存在在undo log,如果执行更新操作,将原记录放入undo log,并通过隐藏的回滚指针指向undo log的原记录,MVCC最大的好处就是读不加锁,读写不冲突,增加了Mysql的并发性,保证了ACID的隔离性

 

二十八.数据库锁有哪些

行级锁:针对当前操作的行进行加锁,行级锁大大减少了数据库的冲突,但是开销比较大,加锁慢,会造成死锁,并发程度概率最高,分为共享锁(读锁)和排他锁(写锁)

表级锁:对操作的整张表加锁,实现简单,资源消耗少,开销小,加锁块,不会出现死锁,并发程度概率最低,分为共享锁(读锁)排他锁(写锁)

页级锁:行级锁和表级锁中间的一个锁,并发程度概率一般,也会出现死锁

 

二十九.读写分离,主从复制

主从复制:从一个数据库服务器复制数据到其他服务器,一个服务器当master,其他的当slave

目的:提高数据库的性能,在master进行写入和更新,在slave进行读,并进行备份

如何实现:在master中进行写操作,它会同步到slave

 

三十.索引

1.mysql使用的是B+Tree索引,数据都在叶子节点上,每个叶子节点都指向了相邻的叶子节点上,进行范围查找时,B+Tree主需要查找两个节点,进行遍历,B-Tree需要获取所有节点

2.B+Tree的磁盘读写代价更低

 

(Spring)

三十一.通过三种方式完成依赖注入

1.构造器注入

2.Setter注入

3.接口注入

 

三十二.BeanFactory和ApplicatiinContext区别

BeanFactory:懒加载,运行速度相对比较慢

ApplicationContext:即时加载,启动时都会加载,系统运行速度快

 

三十三.Bean的生命周期

1.对Bean进行实例化,设置Bean的属性

2.声明依赖

3.调用前置初始化方法,添加自定义的处理逻辑

4.做一些属性被自定义后的事

5.调用init()方法,进行初始化

6.调用后置初始化方法

7.调用destory()方法销毁

 

三十四.SpringBoot的核心注解

@SpringBootApplication:

  1.SpringBootConfigyration:结合@Configuration实现配置文件的功能

  2.EnableAutoCOnfiguration:打开自动配置的功能

  3.ComponentScan:Spring组件扫描

 

三十五.Spring Boot Starter工作原理

1.启动时去依赖的starter包寻找文件,根据文件中配置的Jar包扫描项目锁依赖的Jar包

2.根据spring.factories配置加载AutoConfigure类

3.根据Conditional,进行自动装配

    

 

posted @ 2022-02-17 23:55  最强Java代表  阅读(111)  评论(0)    收藏  举报