2021春招Java面试题大全(精华六)

返回首页

81.Redis是单线程的,为什么这么快

  1. redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文切换是一个耗时操作),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,所以说,在内存情况下,这个就是最佳的方案!

  2. 使用多路 I/O 复用模型,非阻塞 IO;

    多路复用就是一个监视的效果,当有多个请求访问redis时,Redis会用多路复用器来监视这个请求是否准备完毕,如果准备完毕(准备完毕意思就是建立链接以后,需要进行读操作或者写操作),直接让redis给这个请求进行处理(不需要等待的过程),这样Redis就不会空闲会一直处理准备完毕的请求。可以让单线程+多路复用效率很高。(原来Redis要处理请求,就要看这个请求有没有准备完毕,如果准备完毕了就处理,如果没有准备完毕就不处理等待,阻塞的)

拓展

IO复用又包含select、poll、epoll三种模式

  • select模式:监视请求的方式是一个一个的跟请求通讯,看各个请求有没有准备就绪,准备完成的交给redis处理,没有准备完成的,select就继续问下一个请求。但是他最多能监视1024个请求。
  • poll模式:跟select的区别是这个模式没有数量上的限制。
  • epoll模式:他在监视请求时会为这些请求设置一个标识符。标识符写的就是这个线程是否准备完毕,如果准备完毕就不需要一个一个去问了,看看标识符就行了。

82.Redis有哪些数据类型

String,List,Set,Zset,Hash,BitMap,geo


String使用场景:计数器,统计粉丝数量等

List:类似粉丝列表、文章的评论列表之类的数据

Set:可以做交集并集差集,计算共同好友

Hash:存储用户信息,对象。 一般key为ID或者唯一标示,value对应的就是详情了。如商品详情

ZSet:获取排名前几名的用户

geo:可以进行朋友定位,附近的人功能

BitMap:疫情期间统计打卡,我们没个人只需要40多个字节就能记录一年的打卡记录

83.什么是Redis的持久化

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制:

RDB

在指定的时间间隔内对内存的数据进行快照存储,他恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写到一个临时文件中,等持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完成性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB。

什么时候触发RDB机制

  • save规则满足的情况下
  • 执行flushall命令
  • 退出redis

如何恢复RDB文件

只需要将文件放在redis启动目录下,redis启动的时候就会自动检查dump.rdb文件恢复其中的数据

优缺点:

性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。

节省磁盘空间

恢复速度快

  • 需要一定的时间间隔进行操作!如果redis意外宕机了,这个最后一次修改数据就没有的了(所以这种方式更适合数据要求不严谨的时候)
  • fork进程的时候,会占用一定的内存空间

AOF

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以进行改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

AoF默认是每秒执行一次,如果最后一秒宕机了,那那一秒的数据就会丢失

Aof同步频率:始终同步,每次redis的写入都会立刻记入日志。每秒同步。不主动同步,把同步时机交给操作系统

AOF备份机制是默认不开启,需要去配置文件中将appendonly改为yes就开启了

如果aof文件有问题了,就不能启动Redis了。需要修复这个文件,redis为我们提供了一个工具redis-check-aof --fix XXX.aof。如果文件正常,重启就可以直接恢复了。

Rewrite

AOF采用文件追加的方式,文件会越来越大,为了避免这种情况新增了重写机制。

重写机制如何实现?AOF文件持续增长而过大时,就会fork出一条新进程来将文件重写,遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件。

优缺点

  • 每次修改都同步,文件的完整性会更加好
  • 每秒同步一次,如果服务器宕机,只会丢失1秒的数据
  • aof的文件体积远远大于rdb,恢复备份的速度也比rdb慢
  • aof运行效率也要比rdb慢

当我们同时开启两种持久化方式,redis启动后会优先加载AOF文件来恢复原始数据,因为通常情况下AOF文件保存的数据集要比RDB保存的数据集要完整。

84.主从复制

读写分离是来减少主服务器和从服务器的读写压力并不是减少内存压力

不管什么时候,只要给主服务器配了一台从服务器,从服务器永远都要和主服务器的数据保持一致(就算从服务器宕机了,主服务器写了数据,从服务器连接上以后数据还是一致的)

Redis主从复制

主从复制就是把一个redis服务器上的数据,复制到其他的redis服务器。数据的复制是单向的,只能由主节点到从节点。主机以写为主,从机以读为主。

主从复制的原理

slave启动成功连接到master后会发送一sync同步命令

master接收到命令后,立刻进行存盘操作,master将传送整个数据文件(rdb文件)到slave,从机收到RDB文件后,进行全盘加载,之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令

主从复制的作用

主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式

故障恢复,主节点出问题了,可以由从节点提供服务

负载均衡,在主从复制的基础上,配合读写分离,可以有主节点提供写服务,由从节点提供读服务,分担服务器负载。在读多写少的情况下可以通过从节点分担读负载,

主从复制不同情况

  • 主机断开链接,从机依旧链接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息!
  • 如果是使用命令行来配置的主从,这时候如果从机重启了,这时又会变为主机(需要手动配置才能变为从机)。只要变为从机,立马就会从主机中获取值(只要重新连接master,我们的数据一定可以在从机中看到!)

85.哨兵模式

主从切换技术: 当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力。

哨兵模式: 能后后台监控主机是否故障,如果故障了根据投票数自动从库转换为主库。

哨兵模式的原理就是哨兵同步发送命令,等待redis服务器响应,从而监控运行的多个redis实例。

哨兵的作用就是:通过发送命令,让redis服务器返回其运行状态,包括主、从服务器

当哨兵检测到主服务器宕机了,就会自动slave切换成master,然后通过发布订阅模式通知其他的服务器,修改配置文件,让他们切换主机。

一般情况下会使用多哨兵进行监控,各个哨兵之间还会进行监控。

哨兵模式的一个流程:

假设主服务器宕机,哨兵1先检测到这个结构,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值的时候,那么哨兵之间会进行一次投票,最后把票数高的作为主服务器,然后通过发布订阅模式让各个哨兵把自己监控的从服务器实现切换主机,这个过程就是客观下线

如果Master节点断开了,这个时候就会从从机中随机选取一个服务器(投票算法)

如果主机此时回来了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则。

投票选举策略:

  • 选择优先级靠前的(优先级可以在redis.conf文件中设置)
  • 选择偏移量最大的(就是获得原主服务器数据最多的)
  • 选择runid最小的(没个redis启动后会生成一个40位的runid)

86.分布式集群架构中的 session 分离

因为Session是服务端技术,一台服务上记录了用户登录状态,但是其他服务器上并不知道。所以我们需要考虑将Session存在哪里?

缓存数据库,完全存储在内存中,数据读写速度快,数据结构简单

  • 客户端先发送请求到服务器,服务器先去缓存中找看有没有该用户的缓存信息,如果有就不用再去数据库中查找,如果缓存服务器中没有,那就去数据库中查找,查完数据库后现将数据放回到缓存中,然后再返回给客户端。

87.Redis事务

Redis事务是一组命令的集合,在事务执行过程中,会按照顺序串行的被执行,不会被打断。

  • Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

  • Redis单命令的原子性主要得益于Redis的单线程

  • Redis事务没有隔离级别的概念

    • 所有的命令在事务中,并没有被直接执行,只有发起执行命令的时候才会执行

编译型异常(代码有问题,命令有错),事务中所有的命令都不会被执行

运行时异常,如果事务队列中存在语法性错误,那么执行命令的时候,其他命令可以正常执行,错误命令会抛出异常(不保证原子性

88.Ngnix

配置的过程就是配置文件中的server的server-name来指定域名,然后在配置localtion中的proxy_pass来决定把我们这个请求转发到哪里去。

负载均衡就是指: 如果在同一个域名下有多台服务器提供服务,此时需要nginx来进行负载均衡。

  • 对用户的访问请求进行调度管理
  • 对用户的访问请求进行压力分担

反向代理:我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。

负载均衡:如果请求数量太多,并发量太大,单个服务器解决不了,我们增加服务器的数量,然后将请求分发到各个服务器上,将原先请求集中到单个服务器上的情况改为了将请求分发到多个服务器上,将负载分发到不同的服务器,也就是我们说的负载均衡。即:把多个请求通过Nginx平均分担到集群中的不同服务器上

动静分离:为了加快网站的解析速度,可以把动态页面和静态页面有不同的服务器来解析,加快解析速度。降低原来单个服务器的压力。

Nginx的启动,配置

启动路径:/usr/local/nginx/sbin目录下 ./nginx

配置路径:/usr/local/nginx/conf

第一部分:全局块

主要设置一些影响nginx服务器整体运行的配置指令。比如:worker_processes 1; 表示nginx处理并发的数量。

第二部分:events块

worker_connections 1024; 表示nginx支持的最大连接数

第三部分:Http块

反向代理,负载均衡在这里配置:

反向代理配置:

在location中配置proxy_pass,表示当访问 server_name配置的IP地址时,Nginx就会给我们转到Proxy配置的ip地址去。

负载均衡配置:

upstream,添加负载均衡的服务列表

然后在location配置Proxy_pass

Nginx分配服务器策略

  • 轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
  • weight:weight代表权重,默认是1,权重越高分配的客户端越多
  • ip_hash:每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session问题。

89.HTTP和HTTPS

  • https协议需要到CA申请证书
  • http是超文本传输协议,信息是明文传输,https则是具有安全性的SSL加密传输协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。所以他比HTTP协议安全。

客户端在使用HTTPS方式与Web服务器通信时的步骤

(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。

(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。

(3)客户端的浏览器与Web服务器开始协商SSL/TLS连接的安全等级,也就是信息加密的等级。

(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给服务器。

(5)Web服务器利用自己的私钥解密出会话密钥。

(6)Web服务器利用会话密钥加密与客户端之间的通信。

HTTP

传输协议:定义了客户端和服务器端通信时,发送数据的格式

特点:

  • 基于TCP/IP的高级协议

  • 默认端口号:80

  • 基于请求/响应模型的:一次请求对应一次响应

  • 无状态的:每次请求之间相互独立,不能交互数据

90.ArrayList底层源码

  • ArrayList底层是什么?

    • 数组
  • 数组默认是什么类型的

    • Object
  • 初始数组多大

    • 10
  • 怎么扩容

    • ArrayList扩容采用的是Arrays的Copyof方法
  • 一次扩容多少

    • 原来的一半
  • ArrayList线程安全吗

    • 不安全
  • 写一个不安全的例子

    • 故障出现:ArrayList线程不安全,在高并发多线程下访问常见的异常:ConcurrentModificationException。

    • 导致原因:一个正在写入,另一个线程过来抢夺,导致数据不一致(并发修改异常)

    • public class ArrayListErrorTest {
               
               
          public static void main(String[] args) {
               
               
              List list = new ArrayList();
              for (int i = 0; i < 30; i++) {
               
               
                  new Thread(new Runnable() {
               
               
                      @Override
                      public void run() {
               
               
                              list.add(UUID.randomUUID().toString().substring(0,7));//写
                              System.out.println(list);//读
                      }
                  }).start();
              }
          }
      }
      
  • 怎么解决呢?

    • 使用Vector,虽然保证了数据一致性,但是效率低。

    • Collections.synchronizedList(new ArrayList());

    • new CopyOnWriteArrayList()(JUC包下的)写时复制技术

      • 底层的容器是 volatile的Object类型的数组

        • 因为我们修改数据的线程是在拷贝的数组容器上修改的,而读取数据的线程是在原数组容器上进行的。为了让写线程把副本数组的数据修改完让读线程感知到,就需要数组容器是volatile的。
    • 这个类的add方法是用ReentrantLock锁的

      • 因为是通过副本来进行更新的,万一要是多个线程都要同时更新呢?那搞出来多个副本会不会有问题?

        当然不能多个线程同时更新了,这个时候就是看上面源码里,加入了lock锁的机制,也就是同一时间只有一个线程可以更新。

      • 然后在老版本的基础上扩容:1

      • 源码:

        // 这个数组是核心的,因为用volatile修饰了
        // 只要把最新的数组对他赋值,其他线程立马可以看到最新的数组
        private transient volatile Object[] array;
        
        public boolean add(E e) {
                   
                   
                final ReentrantLock lock = this.lock;
                lock.lock();
                try {
                   
                   
                  Object[] elements = getArray();//得到老版本的名单
                    int len = elements.length;//现在的长度
                    Object[] newElements = Arrays.copyOf(elements, len + 1);//扩容+1
                    newElements[len] = e;//写自己的名字
                    setArray(newElements);//把新的花名册放回去
                    return true;//好,我写好了
                } finally {
                   
                   
                    lock.unlock();
                }
            }
        
      • 原理:CopyOnWrite容器即写时复制的容器,往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后往新的容器Object[] new Elements里添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements)。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。(如果用读写锁互斥的话,会导致写锁阻塞大量读操作,影响并发性能。)

  • 同样的有CopyOnWriteArraySet解决ArraySet的不安全问题

  • ConcurrentHashMap解决HashMap的不安全问题

91.Java8锁

class MyPhone {
     
     
 public static synchronized void sendMessage() {
     
     
     System.out.println("sendMessage...");
 }
 public synchronized void sendEmail() throws InterruptedException {
     
     
     Thread.sleep(4000);
     System.out.println("sendEmail...");
 }
 public void sayHello() {
     
     
     System.out.println("sayHello...");
 }
}

/*
* 8锁
* 1.标准访问(先启动调用发邮件的线程,中间再暂停10毫秒,保证他先打印),先打印的sendEmail...
* 2.暂停4秒在邮件方法,还是先打印sendEmail...
* 3.新增普通sayHello方法,先打印sayHello,因为他没加锁
* 4.两部手机,先打印sendMessage...,因为锁的不是同一个对象,没锁住
* 5.两个静态同步方法,同一部手机,先打印sendEmail...,因为静态同步方法默认锁类型
* 6.两个静态同步方法,两部手机,先打印sendEmail...,因为静态同步方法默认锁类型
* 7.一个静态同步方法,一个普通同步方法,同一部手机,先打印sendMessage...
* 7.一个静态同步方法,一个普通同步方法,两部手机,先打印sendMessage...
*
* */

public class Lock8Demo {
     
     
    public static void main(String[] args) throws InterruptedException {
     
     

        MyPhone phone = new MyPhone();

        //锁4
        MyPhone phone2 = new MyPhone();

        new Thread(() -> {
     
     
            try {
     
     
                phone.sendEmail();
            } catch (InterruptedException e) {
     
     
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

/*
        //锁1,2
        new Thread(() -> {
            phone.sendMessage();
        },"B").start();
*/

/*
        //锁3
        new Thread(() -> {
            phone.sayHello();
        },"B").start();
*/

        //锁4
        new Thread(() -> {
     
     
            phone2.sendMessage();
        },"B").start();


    }
}

上述代码省略了不同情况,直接看可能看不懂,可以直接看结论

结论:

锁1、锁2:

  • 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法。
  • 锁的是当前对象this,被锁定后,其他的线程都不能进入到当前对象的其他的synchronized方法
  • 举例子:班长(A线程)在用手机发短息,我(B线程)要想用手机发邮件,那就得等待,等班长发完了。

锁3:

  • 加普通方法和同步锁无关

  • 锁三可以这么理解:发邮件发短信(synchronized的)相当于是手机内部的功能,逻辑上充电线属于手机,但是没有加锁。那么在班长用手机发邮件的时候,我们可以问他借一个充电器,他可以给我们,这不影响他发邮件,这不冲突。

锁4:

  • 不是同一把锁了

  • 上面是多对一,狼多肉少,会争抢;但是锁4是一对一的,各回各家各找各妈

锁5、锁6:

区分对象锁和全局锁

  • 我去上厕所,只锁那个小门,就是对象锁。我要是锁了整个男厕所的门,这就是全局锁。
  • synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
  • 具体表现为以下的三种形式
    • 对于普通方法,锁是当前实例对象
    • 对于同步方法块,锁是synchronized括号里配置的对象
    • 对于静态同步方法,锁是当前类的Class对象

锁7、锁8:

  • 锁的对象不一样,他们不会冲突

92.进程和线程?并发和并行?

进程就是后台运行的程序,线程的就是轻量级的进程。

举个例子:我登录一个QQ后台就会有一个QQ进程,我可以通过QQ聊天,打游戏,发语音,打视频,这些就是QQ的线程。

再比如:我们打开word文档写论文,突然断电了,等重新打开电脑进入word后左上角会有一个恢复文件,这就是一个后台容灾线程,我们单词打错了,下面会有一个红色的波浪号,这也是一个后台的线程(语法检查线程),来检查我们的拼写

并发:指多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。

并行:指多个事件在同一时刻发生,即同时做不同事的能力。

高并发:多个线程同一时间点蜂拥而入,去抢同一份资源。秒杀

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。

93.什么是同步,什么是异步

同步:发送一个请求,等待返回,再发送下一个请求,同步可以避免出现死锁,脏读的发生

异步:发送一个请求,不等待返回,随时可以再发送下一个请求,可以提高效率,保证并发

94.MVCC叫做多版本并发控制,实际上就是保存了数据在某个时间节点的快照。

mysql InnoDB引擎会默认的给要操作的数据行加上锁,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,这是隐式加锁,也可以手动加锁,但是一般不建议。

基于性能考虑,实际MySQL大多的事务型存储引擎的实现都不是简单的行级锁,而是大量的使用了MCVV。

InnoDB的MVCC,其实就是在每一行记录的后面增加两个隐藏列,行创建版本号和删除版本号,而每一个事务在启动的时候,都有一个唯一的递增的版本号。

  • 在插入操作时 : 行的创建版本号就是事务版本号。
  • 在更新操作的时候,采用的是先标记旧的那行记录为已删除,并且删除版本号是事务版本号,然后插入一行新的记录的方式。
  • 删除操作的时候,就把事务版本号作为删除版本号。
  • 查询操作:要符合两个条件:
    • 删除版本号 大于 当前事务版本号,就是说删除操作是在当前事务启动之后做的。
    • 创建版本号 小于或者等于 当前事务版本号。

总结:MCVV提高系统性能的思路就是:通过版本号来减少锁的争用。另外,只有read-committed和 repeatable-read 两种事务隔离级别才能使用MCVV。read-uncommited由于是读到未提交的,所以不存在版本的问题。而serializable 则会对所有读取的行加锁。


MVCCMySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

95.读写分离

读写分离也是一种提高数据库性能的方法。

读写分离的概念就是:主数据库复制写数据,从数据库复制读数据。在主数据库中加入了数据后,这个数据会同步到从数据库

原理:

数据库有一个日志功能,binlog,他记录了我们对数据库的增删改操作。master会将改变操作记录到这个binlog中,然后slave会实时监控主服务器的binlog日志,当日志有了变化就会把主服务器的同步信息同步到自己的服务器上执行。

猜你喜欢

转载自blog.csdn.net/ysf15609260848/article/details/114447589