面试题解析
最主要的还是面试题的解析,但是前提是你有这个资格入得了xxx公司的法眼才行,就是说教育背景啥的。废话不多说,开放面试题……
1、ReentrantLock的可重入是怎么实现的?
答:
2、怎么理解http的无状态?
答:浏览器不会去记录是谁去访问了它,这就是无状态,即使你访问多次浏览器也不记得。Websocekt有状态,也可以使用cookie+session+http实现有状态的http。可以对比http和websocket的优缺点,为啥不适用websocket?
- websocket目的就是解决网络传输中的双向通信的问题,http1.1默认是持久连接
Cookie和session区别:
Cookie(是key和value形式存储)工作原理:
- 浏览器第一次发送请求到服务器端;
- 服务器端创建cookie,该cookie中包含用户的信息,然后将该cookie发送到浏览器端;
- 浏览器端再次访问服务器端时会携带服务器端创建的cookie;
- 服务器端通过cookie中携带的数据区分不同的用户。
- cookie数据保存在客户端,session数据保存在服务器端;
- Cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,如果主要考虑安全应当使用session;
- Session会在一定时间内保存在服务器上。当访问量增多,会比较占用服务器的资源,如果主要考虑减轻服务器性能方面,应当使用cookie;
- 单个cookie在客户端的限制是4k,就是说一个站点在客户端存放的cookie不能4K;
3、多线程是什么?为什么会有多线程问题?
答:单线程程序只能顺序执行,并发性低;多线程就可以解决这个问题。但是多个线程会导致竞争资源、死锁、内存泄露等问题。
4、GC机制
答:GC是垃圾回收的机制。将不在使用的对象从内存中释放出来。哪些对象需要回收?引用计数进行判断、或者可达性分析。流行的都是用可达性分析进行回收对象的。
5、Hashmap原理
答:1.7之前hashmap底层是数组和链表,1.7之后是数组+链表+红黑树。初始化默认数组长度为16,扩容因子=0.75,就是说当数组元素超过数组大小的0.75倍后就会自动扩容,扩容倍数为2。
6、Bean的生命周期
答:
7、IoC和AOP使用场景
答:Ioc是控制反转,是一个创建、管理bean的容器。实现IoC的三种注入方式:构造器、setter、接口。
Spring中有BeanFactory和ApplicationContext两个IOC容器。ApplicationContext接口扩展了BeanFactory接口。
IOC好处:1、最小化应用程序的代码量;2、使得应用程序易于测试;3、松耦合;4、支持即时的实例化和延迟加载服务。
IOC的实现原理就是工厂模式加反射机制。
Spring提供了三种配置方式:xml、注解、java代码。
AOP是将其他公共的功能从业务层剥离出来。比如:日志、事务和异常。AOP是使用动态代理实现的,就是在内存中创建一个临时的AOP代理对象,并且在特定的切点上做了增强。
8、多线程使用场景,线程池、参数、拒绝策略
答:多线程适用于需要并发执行任务的场景,这样可以提高程序的性能和效率。线程之间的频繁切换是会浪费资源的。
程序的运行,本质上就是占用系统资源。那优化资源的技术是啥呢?--->就是线程池。
线程池的好处:降低资源的消耗、复用、方便管理。
Java中不允许使用Executors去创建线程,而是通过ThreadPoolExecutor的方式。目的是明确线程池的运行规则。
Java中有如下线程池创建方法:单一线程池、固定线程池、可伸缩线程池、定时线程池。
线程池中七大参数:核心线程数、最大线程数、拒绝策略、阻塞队列、线程工厂、超时等待时间、超时等待时间单位。
拒绝策略触发时机:当提交的任务数大于阻塞队列+最大线程数。
四种拒绝策略:
- 默认策略,直接抛出异常;
- 不处理任务,直接丢弃;
- 将等待队列的队首任务丢弃,并执行当前任务;
- 由调用线程处理该任务。
超时等待时间是针对最大线程数中的非核心线程的。
9、锁、乐观锁、悲观锁、独占锁(排他锁)、共享锁
答:
乐观锁适合读多写少的场景。乐观锁更新方式认为:在更新数据的时候其他线程争抢这个共享变量的概率很小,所以更新数据的时候不会对共享数据加锁。但是在正式更新数据之前会检查数据是否被其他线程改变过,如果未被其他线程改变过就将共享变量更新成为最新值,如果发现共享变量已经被其他线程更新过了,就重试,直到成功为止。CAS机制就是乐观锁的典型实现。
或:当多个线程尝试使用 CAS 同时更新 同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的 线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
共享锁(S锁):共享锁用于不更改或不更新数据的操作(只读操作),如select语句;如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
独占锁(排他锁)(X锁):用于数据修改操作,例如insert、update或者delete。确保不会同时同一资源进行多重更新。如果事务T对数据A加上排他锁后,则其他事务不能再对A加任何类型的锁。获准排他锁的事务既能读数据,又能修改数据。
10、@Autowired和@Resource区别
答:
- @Autowired是按类型装配的,@Resource是按名称装配的。
- @Autowired注解是spring提供的,@Resource是J2EE提供的。
11、事务,四大特性
答:
12、什么是索引?索引的分类?索引失效场景?
答:
索引就是数据结构,使用数据结构将数据连接起来的。
索引分为:主键、唯一、常规、全文、聚集、非聚集索引
索引失效场景:
- 不满足最左匹配原则(联合索引);
- 使用select *;
- 索引列上有计算;
- 索引列用了函数;
- 字段类型不同;
- Like左边包含了%
13、Redis缓存穿透、缓存雪崩?持久化方式?
答:
14、简单实现一个分布式锁?
答:
15、单例模式写一下?
答:单例模式的编写注意事项:1、构造函数的私有化;2、自行对外提供实例;3、对外提供一个公共的获取该实例的方法。
有懒汉式的单例模式(容易造成多线程的问题、所以需要加锁synchronized)还有饿汉式的单例模式。
懒汉式就是你用的时候我在加载,饿汉式就是类加载的时候我就加载。
使用synchronized的懒汉式单例模式还是会有问题:因为synchronized的范围太大,颗粒度比较大,这个会是成为系统的瓶颈。所以还需要进一步升级----->使用双重校验锁的方式。
但是双重校验锁的方式还是会有问题:指令重排的问题。指令重排就是JVM在优化指令时,不影响单线程执行结果的前提下,提高并行度。
singleton = new Singleton()看似时原子操作,其实可以分为以下三步:
1、分配对象内存空间;
2、初始化对象;
3、设置实例指向刚分配的内存地址;
操作2依赖操作1,但是操作3不依赖上述操作。所以JVM可以对上述指令进行重排的,重排如下:
- 分配对象内存空间;
- 设置实例指向刚分配的内存地址,此时对象还未初始化;
3、初始化对象;
当线程A执行这段指令时,执行到2时,对象还未初始化,就赋值引用了,恰好线程B进行判断对象的引用不为空,然后就返回对象进行使用了,最后程序报错了。
16、为什么innodb选择B+树做索引?
答:回答此问题就和其他几个索引做对比即可。
相较于B树:B树的叶子节点和非叶子节点都存储数据和指针,最后会导致树的高度变高
相较于二叉树:树的高度低,速度快
相较于hash索引:可以范围查询、也会hash冲突
17、索引的分类、聚集索引和非聚集索引(二级索引)
索引分为四类:
主键索引:就是只能有一个;
唯一索引:可以有多个(猛一听以为只能有一个,其实它这个唯一的意思是在表中的某一列数据不能重复而已);
常规索引:可以有多个(快速定位特殊数据,就是平常我们建立的索引);
全文索引:是查找文本中的关键字,而不是比较索引的值,可以有多个。
聚集索引:就是索引和数据在一块---innodb就是,效率高
非聚集索引(二级索引):就是索引和数据单独存储---myisam,效率低
18、Spring事务失效场景?
答:spring事务底层使用了AOP动态代理实现的。同一事务表示指同一数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。
- 访问修饰符错误,只有在public的方法上加事务注解才可以;
- 使用final加在添加注解事务的方法上;
- 在方法内部调用带有事务的方法(原因:方法B调用带有事务的方法A,它们在同一个类中。由于这两个方法都是同一个实例内部调用的,spring的默认代理是CGlib代理。这种情况下,方法A调用是不会经过代理的,因此事务会失效的。解决方法:写成两个类。);
- 未被spring管理;
- 多线程调用(不同数据库,不同事务);
- ……
19、线程池大小怎么设置?
答:线程池设置较小,会导致很多任务没有线程处理,最后JVM就会及逆行垃圾回收;线程池设置较大,会导致多线程竞争CPU资源,使得线程切换频繁,导致性能降低。
如果任务是CPU密集任务:主要消耗CPU资源,那么线程池大小就设置为CPU核心数+1,目的是防止某些原因导致线程阻塞而带来的影响。一旦某个线程被 阻塞,释放了CPU资源,而这种情况多出来的线程就可以利用CPU资源了。
如果任务是IO密集任务:线程池大小设置为2N。因为系统的大部分的时间都在处理IO操作此时线程可能阻塞,释放CPU资源,这时候就可以将CPU资源交给其他线程使用。
20、怎么判断线程池的任务是否执行完毕了?
- 适用于线程池的原生函数isTerminated();
- 使用重入锁,维持一个公共计数;
- 使用CountDownLatch;
- submit'向线程池提交任务,使用Future判断任务执行状态。
以上面试题是我在面试过程中的总结,有些没有答案是因为我不会或者我觉得太简单了。如果你也知道,打在评论区我们讨论一下 ~
后续我会继续分享我的面试题的,今天时间有限,我要下班了,再见!