【打卡day01】每天学习10道java面试题

【打卡day01】每天学习10道java面试题

2022/7/22十道面试题:

1.聊聊volatile关键字?
2.java中的互斥锁有哪些?写一个死锁案例。
3.乐观锁和悲观锁区别?
4.消息队列是如何保证有序性和重复消费问题的?
5.Spring MVC的执行过程?
6.CAP理论?BASE理论?
7.数据库的事务的四大特性、并发会出现的问题以及隔离级别?
8.session和cookie?
9.redis持久化方式?优缺点是什么?
10.进程与线程的区别?

1.聊聊volatile关键字?
  • volatile关键字有两大作用:(1)禁止指令重排序 (2)可见性(volatile修饰的变量在修改时对其他线程可见)

  • 具体细节得出JMM说起。

Java内存模型JMM主要定义了各种变量的访问规则,主内存和工作内存。其中主内存是线程共享的,工作内存是线程私有的。所有的变量都存储在主内存中,线程的工作内存中存储了该线程使用变量的主内存副本, 线程对变量的操作(读取、赋值)都必须在工作内存中进行。于是就出现了主内存和工作内存之间变量的一些操作指令:lock和unlock是作用于主内存中的变量,主内存中的变量可以被某个线程反复lock,但不能同时被多个线程lock。read是从主内存读取变量,load是将读取的变量值赋值给工作内存的变量。use是jvm执行引擎读取工作内存中的变量,assign是将执行引擎变量操作完后写回工作内存中。store是主内存读取工作内存中的变量,write是将工作内存中变量写回主内存中。
在这里插入图片描述

  • 再聊聊几个内存屏障:

loadload屏障:A loadload B 表示A的读操作优先于B的读操作

loadstore屏障:A loadstore B 表示A的读操作优先于B的写操作

storeload屏障:A storeload B 表示A的写操作优先于B的读操作

storestore屏障:A storestore B表示A的写操作优先于B的写操作

在volatile修饰的变量读取后面会加上loadstore和loadload屏障,写前加上storestore屏障,写后加上storeload屏障。

因此,禁止指令重排序:通过内存屏障实现前面的指令先执行来实现的。可见性:在发生写操作后会使得其他线程的read操作(已经对同一变量使用过read操作的线程)失效而重新从主内存中读取。

普通变量在有线程修改后,read读到的变量不会失效而继续执行后续的load操作从而产生错误。

  • volatile为什么不能保证原子性?

因为volatil变量的修改虽然对其他线程可见,但也只能对那些还没来得及执行load操作的线程有效(会重新加载),对执行完了load操作后的线程来说是无效的(不会重新加载)。因此可能会出现结果覆盖的问题,比如:

对volatile int a = 1;有2线程对其进行一次自增操作,理想结果应该得到a = 3,结果确是2,线程2的结果覆盖了线程1的结果。

操作线程1操作线程2
read从主内存读到a=1read从主内存读到a=1
load给工作内存a*赋值为1load给工作内存a*赋值为1
use执行引擎读取a*后执行自增操作
assign将自增的结果赋值会a*
store读取工作内存a* = 2
write写回主内存 a = 2
use执行引擎读取a*后执行自增操作
assign将自增的结果赋值会a*
store读取工作内存a* = 2
write写回主内存 a = 2
2.java中的互斥锁有哪些?写一个死锁案例。

互斥锁主要就是synchronized和Lock。

  • synchronized:synchronized是java提供的一个关键字,可以修饰实例方法(锁对象)、静态方法(锁当前类)、代码块(锁对象)。
//修饰实例方法(锁对象)
synchronized void method(){
	//业务代码
}
//修饰静态方法(锁当前类)
synchronized void static method(){
	//业务代码
}
//代码块(锁对象)
synchronized(this){
	//业务代码
}

【如何实现】修饰代码块时JVM通过在代码块开始位置加上monitorenter指令和代码块结束位置加上monitorexit指令。修饰同步方法时JVM采用ACC_SYNCHRONIZED标记符来实现同步(反编译可以看到)。monitorenter、monitorexit和ACC_SYNCHRONIZED都是基于Monitor实现的,HotSpot中通过ObjectMonitor实现的。

【有序性】由于as-if-serial语义的存在,单线程的程序能保证最终结果正确性,而不能保证指令不重排,因此synchronized关键字可以保证执行结果的有序性,而不能防止指令重排。

【锁升级过程(JDK1.6之后)】JDK1.6之后引入了偏向锁,该锁的原理是在锁中存一个线程id,当下次访问时与线程id进行比较,若相同则直接执行代码,该操作即可优化大量单线程没有竞争情况下的资源耗费问题。当存在多线程竞争锁时,锁升级为轻量级锁,当存在大量线程进行竞争时,自旋一定次数(即while(true)循环进行多次后),锁会升级成重量级锁(未抢占到锁的线程会进行阻塞)。这个时候重量级锁耗费的资源可能还会更低,可以理解成如果线程多,就会出现大量cpu空转状态,则不如让其进入阻塞队列。
在这里插入图片描述

//反编译
javap -c -s -v -l Test.class
  • Lock:Lock是JUC包下的一个接口,它的常见实现有可重入锁(ReentrantLock)和读写锁(Read、WriteLock)。Lock需要显式地获取和释放锁,虽然不如隐式获取锁的便捷,但有了锁获取与释放的可操作性、可中断的获取锁及超时获取锁等同步特性。

死锁案例:

public class DeadlockTest {
    static Object resources1 = new Object();
    static Object resources2 = new Object();

    public static void main(String[] args) {
        new Thread(()->{
           synchronized (resources1) {
               try {
                   Thread.sleep(50);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized (resources2) {
                   System.out.println("死锁测试线程1");
               }
           }
        }).start();
        new Thread(()->{
            synchronized (resources2) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resources1) {
                    System.out.println("死锁测试线程2");
                }
            }
        }).start();
    }
}
3.乐观锁和悲观锁区别?

乐观锁:每次读取数据时都认为其他线程不会对其修改。适合于读多写少的场景。

悲观锁:每次读取数据时都认为其他线程会对数据修改。适合于写多读少的场景。

4.消息队列是如何保证有序性和重复消费问题的?

【有序性】出现乱序的原因:一个队列被多个consumer消费,就会出现MQ乱序问题

  • 解决方法一:将消息分散至多个队列,让一个consumer对应一个队列。
  • 解决方法二:如果是要求结果的有序性,多consumer消费后将结果汇总重新进行排序。 (一般说消息队列的有序性是指消费的有序性,而不是指结果的有序性)

【重复消费】重复消费的原因最可能的原因是来自生产者端:(1)手动ack时由于网络原因导致的重试 (2)多线程入消息队列对数据操作不当,比如用了JU下并发不安全的工具包且没有使用锁机制。

  • 解决方法一:消费者端保证数据的幂等性,比如给该消息做一个唯一主键,出现重复消费时就会导致主键冲突而避免数据库出现重复数据。
  • 解决方法二:消息先提取出唯一区别字段或者使用序列化后的时间戳,然后可以通过map集合进行过滤,或者可以借用redis,先将数据放到redis,然后再将其取出入库。
5.Spring MVC的执行过程?
  1. 客户端向服务器发送一次请求,这个请求会先到前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求后会调用HandlerMapping处理器映射器,由此得知该请求由哪个Controller来进行处理。
  3. DispatcherServlet调用HandlerAdapter处理器适配器,告知其要去执行哪个Controller。
  4. HandlerAdapter执行Controller并得到数据和视图ModelAndView(前后端分离的Restful风格的接口只有数据无视图)并返回给DispatcherServlet。
  5. DispatcherServlet将ModelAndView交给视图解析器ViewResolver解析,然后返回真正的视图。
  6. DispatcherServlet将模型数据填充到视图中并将结果响应给客户端。
    原图来自于三分恶

原图来自于三分恶:https://blog.csdn.net/sinat_40770656

6.CAP理论?BASE理论?

【CAP理论】在一个分布式系统中,最多只能满足C、A、P中的两个需求。其中C代表一致性(Consistency),表示同一数据的多个副本是否实时相同;A代表可用性Availability,系统收到请求后能在一定时间内返回一个明确的结果;P代表分区容错性(Partition tolerance),表示将一服务分布在多个系统中,当某一个系统宕机时,仍然有其他系统提供相同的服务。

【BASE理论】三个只能选择两个如何选择?选用分布式的主要原因是提升整体性能和实现分区容错性(单点故障时依旧可以正常提供服务),由此得知可用性和分区容错性对系统是尤为重要的,只能牺牲一致性来换取系统的可用性和分区容错性。而所谓牺牲一致性并不是完全放弃数据一致性,而是牺牲强一致性换取弱一致性

BA:Basic Available 基本可用,系统在一些不可抗力的情况下(比如大促)时,响应时间可以适当延长或者是给用户返回一个降级页面。S:Soft State:柔性状态 同一数据的不同副本的状态,可以不需要实时一致。E:Eventual Consisstency:最终一致性 同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的。

7.数据库的事务的四大特性、并发会出现的问题以及隔离级别?

【四大特性】

  • 原子性:要求所有操作要么全部执行,要么全部不执行。
  • 一致性:要求事务开始前和结束后,数据库的完整性约束没有被破坏。
  • 隔离性:要求事务的执行时相互独立的,不会相互干扰。
  • 持久性:要求事务完成后是持久化保存的,即使数据库发生崩溃,在数据库恢复后事务提交的结果不会丢失。

【并发出现的问题】

  1. 更新丢失:当有两个并发执行的事务,更新同一行数据,那么有可能一个事务会把另一个事务的更新覆盖掉。 当数据库没有加任何锁操作的情况下会发生。
  2. 脏读:一个事务读到另一个尚未提交的事务中的数据。 该数据可能会被回滚从而失效。 如果第一个事务拿着失效的数据去处理那就发生错误了。
  3. 不可重复读:一个事务对同一行数据读了两次,却得到了不同的数据(强调修改)。
  4. 幻读:一个事务对同一行数据读了两次,却得到了数据条数不一样(强调有数据插入和删除)。

【隔离级别】

  1. 读未提交(Read uncommitted):一个事务对一行数据修改的过程中,不允许另一个事务对该行数据进行修改,但允许另一个事务对该行数据读。可以解决更新丢失问题,但还是会出现脏读、不可重复度、幻读等问题。

  2. 读已提交(Read committed):未提交的写事务不允许其他事务访问。可以解决更新丢失、脏读问题,但还是会出现不可重复度、幻读等问题

  3. 可重复读(Repeatable read):在有读事务时,禁止写事务但允许读事务。可以解决更新丢失、脏读、不可重复度问题,但还是会出现幻读等问题

  4. 序列化(Serializable):串行化执行,可以解决以上所有问题。

    隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

8.session和cookie?

【cookie】cookie的出现是因为HTTP是无状态的一种协议(用户每次刷新网页就要重新登录,这显然是无法接受的),cookie的作用就好比服务器给用户贴了个标签,然后用户每次再发起请求时,服务器可以辨认出该用户。抽象概括:cookie可以认为是一个变量(也可以是键值对),形如name=xxx,存在浏览器;

【session】session多数情况下是【映射】(键值对),存储在服务器上。但问题是:很多网站功能很复杂,涉及很多数据交互,而cookie是存在HTTP header中的,就算能够承载这些信息,也会消耗很多带宽,比较耗费网络资源。session就可以配合cookie解决上述问题,比如cookie存储一个变量:sessionID=xxxx,传送给服务器,服务器就可以根据该ID找到对应的session,这个session的key是sessionID,value是该用户的用户信息,(如登录信息等),服务器就可以根据这些信息返回该用户的定制化网页,有效解决了追踪用户的问题。当然,session一直存储在服务器中,也会耗费资源,因此session一般都有一个过期时间,服务器一般会定期检查并删除过期的session,如果用户再次访问服务器,可能面临重新登录等措施,然后服务器再新建一个session,将sessionID以cookie的形式传送给客户端。

【session的实现】Manager + provider + Session

为什么session不直接通过键值对存储而需要分成Manager、provider、Session?

第一,因为Session结构可能不止存储了一个哈希表,还存储了一些辅助数据,比如sid,访问次数,过期时间或最后一次访问时间,这样便于实现LRU(Least Recently Used)、LFU(Least Frequently Used,最近最不常用)这样的算法。

第二,方便Session拥有不同的存储方式,如果用编程语言的哈希表,则session数据存在内存中,如果数据量大,容易造成程序崩溃,并且程序结束,所有session数据都会丢失。而该结构可以让session存在Redis或者Mysql中。
在这里插入图片描述

9.redis持久化方式?优缺点是什么?

【持久化方式】RDB持久化、AOF持久化和混合持久化方式(Redis 4.0)

  • RDB持久化:将当前数据生成快照保存到硬盘的方式,触发方式分为手动触发和自动触发。RDB⽂件是⼀个压缩的⼆进制⽂件,通过它可以还原某个时刻数据库的状态。由于RDB⽂件是保存在硬盘上的,所以即使Redis崩溃或者退出,只要RDB⽂件存在,就可以⽤它来恢复还原数据库的状态。

手动触发:save和bgsave命令。

save命令:执行时会阻塞当前Redis服务直到命令执行完成,对于内存比较大的实例会造成长时间阻塞,线上不推荐使用。
bgsave命令:Redis进程执⾏fork操作创建⼦进程,RDB持久化过程由⼦进程负责,完成后 ⾃动结束。阻塞只发⽣在fork阶段,⼀般时间很短。

自动触发:

* ​	使⽤save相关配置,如“save m n”。表⽰m秒内数据集存在n次修改时,⾃动触发bgsave。
* ​	如果从节点执⾏全量复制操作,主节点⾃动执⾏bgsave⽣成RDB⽂件并发送给从节点
* ​	执⾏debug reload命令重新加载Redis时,也会⾃动触发save操作
* ​	默认情况下执⾏shutdown命令时,如果没有开启AOF持久化功能则⾃动执⾏bgsave。
  • AOF持久化:以独⽴⽇志的⽅式记录每次写命令, 重启时再重新执⾏ AOF⽂件中的命令达到恢复数据的⽬的。AOF的主要作⽤是解决了数据持久化的实时性,⽬ 前已经是Redis持久化的主流⽅式。

    AOF的⼯作流程操作:命令写⼊ (append)、⽂件同步(sync)、⽂件重写(rewrite)、重启 加载 (load)

在这里插入图片描述

原图来自三分恶:https://blog.csdn.net/sinat_40770656

流程如下:

1)所有的写⼊命令会追加到aof_buf(缓冲区)中。

2)AOF缓冲区根据对应的策略向硬盘做同步操作。

3)随着AOF⽂件越来越⼤,需要定期对AOF⽂件进⾏重写,达到压缩的⽬的。

4)当Redis服务器重启时,可以加载AOF⽂件进⾏数据恢复。

【RDB和AOF两种方式对比】

1)RDB方式恢复速度快,启动效率高,适合备份、全量复制的场景;

2)AOF实时性好,数据安全性高。

  • 混合持久化(Redis 4.0):将 rdb ⽂件的内容和增量的 AOF ⽇志⽂件存在⼀起。这⾥的 AOF ⽇志不再是全量的⽇志,⽽是RDB⾃持久化开始到持久化结束 的这段时间发⽣的增量 AOF ⽇志,通常这部分 AOF ⽇志很⼩。

    在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF ⽇志就可以完全 替代之前的 AOF 全量⽂件重放,重启效率因此⼤幅得到提升。

10.进程与线程的区别?

【进程】进程是代码在数据集合上的一个执行过程,是系统资源分配的基本单位。

【线程】线程是进程的一个执行路径,是CPU资源分配的基本单位。一个进程中至少有一个线程,进程的多个线程共享进程中的堆和方法区资源,各线程都有自己的程序计数器和栈。


【参考链接】

synchronized:https://juejin.cn/post/6844903600334831629
Lock锁:https://juejin.cn/post/7085252554665230350
Cookie和Session:https://blog.csdn.net/weixin_45393094/article/details/104747360
消息队列重复消费问题:https://blog.csdn.net/lisheng19870305/article/details/112849591
事务:https://juejin.cn/post/6844903573667446797
三分恶大佬主页:https://blog.csdn.net/sinat_40770656

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值