【Java】留下没有基础眼泪的面试题

7,467 阅读14分钟

前言

只有光头才能变强

本文力求简单讲清每个知识点,希望大家看完能有所收获

一、如何减少线程上下文切换

使用多线程时,不是多线程能提升程序的执行速度,使用多线程是为了更好地利用CPU资源

程序在执行时,多线程是CPU通过给每个线程分配CPU时间片来实现的,时间片是CPU分配给每个线程执行的时间,因时间片非常短,所以CPU通过不停地切换线程执行

线程不是越多就越好的,因为线程上下文切换是有性能损耗的,在使用多线程的同时需要考虑如何减少上下文切换

一般来说有以下几条经验

  • 无锁并发编程。多线程竞争时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据
  • CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁
  • 控制线程数量。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
  • 协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
    • 协程可以看成是用户态自管理的“线程”不会参与CPU时间调度,没有均衡分配到时间。非抢占式

还可以考虑我们的应用是IO密集型的还是CPU密集型的。

  • 如果是IO密集型的话,线程可以多一些。
  • 如果是CPU密集型的话,线程不宜太多。

参考资料:

二、计算机网络

2.1MAC地址已经是唯一了,为什么需要IP地址?

或者可以反过来问:已经有IP地址了,为什么需要MAC地址??在zhihu上还蛮多类似的问题的:

我来简单总结一下为什么有了MAC(IP)还需要IP(MAC):

  • MAC是链路层,IP是网络层,每一层干每一层的事儿,之所以在网络上分链路层、网络层(...,就是将问题简单化。
  • 历史的兼容问题。

已经有IP地址了,为什么需要MAC地址??

  • 现阶段理由:DHCP基于MAC地址分配IP

MAC地址已经是唯一了,为什么需要IP地址?

  • MAC无网段概念,非类聚,不好管理

如果有更好的看法,不妨在评论区下留言哦~

参考资料:

2.2TCP状态

TCP 每个状态说一下,TIME-WAIT状态说一下

TCP总共有11个状态,状态之间的转换是这样的:

流程图:

下面我简单总结一下每个状态:

  • CLOSED:初始状态,表示TCP连接是“关闭着的”或“未打开的”。
  • LISTEN:表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。
  • SYN-SENT:表示客户端已发送SYN报文。当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT状态。
  • SYN_RCVD:表示服务器接收到了来自客户端请求连接的SYN报文。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED状态
  • ESTABLISHED:表示TCP连接已经成功建立
  • FIN-WAIT-1第一次主动请求关闭连接,等待对方的ACK响应。
  • CLOSE_WAIT:对方发了一个FIN报文给自己,回应一个ACK报文给对方。此时进入CLOSE_WAIT状态。
    • 接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接
  • FIN-WAIT-2:主动关闭端接到ACK后,就进入了FIN-WAIT-2。在这个状态下,应用程序还有接受数据的能力,但是已经无法发送数据。
  • LAST_ACK:当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态
  • CLOSED:当收到对方的ACK报文后,也就可以进入到CLOSED状态了。
  • TIME_WAIT:表示收到了对方的FIN报文,并发送出了ACK报文。TIME_WAIT状态下的TCP连接会等待2*MSL
  • CLOSING:罕见的状态。表示双方都正在关闭SOCKET连接

TIME_WAIT状态一般用来处理以下两个问题:

  • 关闭TCP连接时,确保最后一个ACK正常运输(或者可以认为是:等待以便重传ACK)
  • 网络上可能会有残余的数据包,为了能够正常处理这些残余的数据包。使用TIME-WAIT状态可以确保创建新连接时,先前网络中残余的数据都丢失了

TIME_WAIT过多怎么解决?

如果在高并发,多短链接情景下,TIME_WAIT就会过多。

可以通过调整内核参数解决:vi /etc/sysctl.conf 加入以下内容设置:

  • reuse是表示是否允许重新应用处于TIME-WAIT状态的socket用于新的TCP连接;
  • recyse是加速TIME-WAIT sockets回收

我们可以知道TIME_WAIT状态是主动关闭连接的一方出现的,我们不要轻易去使用上边两个参数。先看看是不是可以重用TCP连接来尽量避免这个问题(比如我们HTTP的KeepAlive)~

参考资料:

2.3TCP滑动窗口

TCP是一个可靠的传输协议,它要保证所有的数据包都可以到达,这需要重传机制来支撑。

重传机制有以下几种:

  • 超时重传
  • 快速重传
  • SACK 方法

滑动窗口可以说是TCP非常重要的一个知识点。TCP的滑动窗口主要有两个作用:

  • 提供TCP的可靠性
  • 提供TCP的流控特性

简略滑动窗口示意图:

详细滑动窗口示意图:

  • #1已收到ack确认的数据。
  • #2发还没收到ack的。
  • #3在窗口中还没有发出的(接收方还有空间)。
  • #4窗口以外的数据(接收方没空间)

接受端控制发送端的图示:

2.4拥塞控制

TCP不是一个自私的协议,当拥塞发生的时候,要做自我牺牲。就像交通阻塞一样,每个车都应该把路让出来,而不要再去抢路了

拥塞控制主要是四个算法:

  • 1)慢启动,
  • 2)拥塞避免,
  • 3)拥塞发生,
  • 4)快速恢复

拥塞控制的作用:

拥塞的判断:

  • 重传定时器超时
  • 收到三个相同(重复)的 ACK

强烈建议阅读:

参考资料:

三、操作系统

3.1僵尸进程和孤儿进程是什么(区别)

unix/linux环境下

僵尸进程:

  • 父进程创建出子进程,子进程退出了,父进程没有调用waitwaitId获取子进程的信息(状态),子进程的描述符仍在系统中

孤儿进程:

  • 父进程退出,子进程仍在运行中。这些子进程就叫做孤儿进程,孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作

僵尸进程危害

  • 系统进程表是一项有限资源,如果系统进程表被僵尸进程耗尽的话,系统就可能无法创建新的进程
  • 一个父进程创建了很多子进程,就是不回收,会造成内存资源的浪费

解决僵尸进程的手段:

  • 杀掉父进程,余下的僵尸进程会成为孤儿进程,最后被init进程管理
  • 子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程
  • fork两次:原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程

参考资料:

3.2操作系统进程间通信的方式有哪些?

首先要知道的是:进程和线程的关注点是不一样的:

  • 进程间资源是独立的,关注的是通讯问题。
  • 线程间资源是共享的,关注的是安全问题。

操作系统进程间通信的方式有哪些?

  • 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  • 有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程之间的通信。
  • 消息队列(message queue):消息队列是消息的链表,存放在内核中并由消息队列表示符标示。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限制等缺点。
  • 共享内存(shared memory):共享内存就是映射一段内被其它进程所访问的内存,共享内存由一个进程创建,但是多个进程都可以访问。共享内存是最快的IPC,它是针对其它进程通信方式运行效率低的而专门设计的。它往往与其它通信机制。如信号量,配合使用,来实现进程间的同步和通信。
  • 套接字(socket):套接字也是进程间的通信机制,与其它通信机制不同的是,它可以用于不同机器间的进程通信。
  • 信号(signal):信号是一种比较复杂的通信方式,用于通知接受进程进程某个时间已经发生。
  • 信号量(semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
    • 它常作为一种锁的机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此它主要作为不同进程或者同一进程之间不同线程之间同步的手段

3.3操作系统线程间通信的方式有哪些?

操作系统线程间通信的方式有哪些?(可以直接理解成:线程之间同步的方式有哪些)

  • 锁机制:包括互斥锁、条件变量、读写锁
  • 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
  • 信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步

参考资料:

扩展阅读:

3.4操作系统进程调度算法有哪些?

操作系统进程调度算法有哪些?

  • 先来先服务算法(FCFS)
    • 谁先来,就谁先执行
  • 短进程/作业优先算法(SJF)
    • 谁用的时间少、就先执行谁
  • 最高响应比优先算法(HRN)
    • 对FCFS方式和SJF方式的一种综合平衡
  • 最高优先数算法
    • 系统把处理机分配给就绪队列中优先数最高的进程
  • 基于时间片的轮转调度算法
    • 每个进程所享受的CPU处理时间都是一致的
  • 最短剩余时间优先算法
    • 短作业优先算法的升级版,只不过它是抢占式
  • 多级反馈排队算法
    • 设置多个就绪队列,分别赋予不同的优先级,如逐级降低,队列1的优先级最高

参考笔记:

四、拓展阅读

此部分是看别人的博文已经写得很好了,分享给大家~

4.1ConcurrentHashMap中的扩容是否需要对整个表上锁?

ConcurrentHashMap中的扩容是否需要对整个表上锁?

总结(摘抄)要点:

  • 通过给每个线程分配桶区间(默认一个线程分配的桶是16个),避免线程间的争用。
  • 通过为每个桶节点加锁,避免 putVal 方法导致数据不一致。
  • 同时,在扩容的时候,也会将链表拆成两份,这点和 HashMap 的 resize 方法类似。

参考资料:

4.2什么是一致性Hash算法(原理)?

什么是一致性Hash算法(原理)?

总结(摘抄)要点:

  • 一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,好处就是提高容错性和可扩展性
    • 对于节点的增减都只需重定位环空间中的一小部分数据

参考资料:

4.3MySQL date、datetime和timestamp类型的区别

MySQL date、datetime和timestamp类型的区别

总结(摘抄)要点:

  • date精确到天,datetime和timestamp精确到秒
  • datetime和timestamp的区别:
    • timestamp会跟随设置的时区变化而变化,而datetime保存的是绝对值不会变化
    • timestamp储存占用4个字节,datetime储存占用8个字节
    • 可表示的时间范围不同,timestamp只能到表示到2038年,datetime可到9999年

参考资料:

4.4判断一个链表是否有环/相交

判断一个链表是否有环(实际上就是看看有无遍历到重复的节点),解决方式(3种):

  1. for遍历两次
  2. 使用hashSet做缓存,记录已遍历过的节点
  3. 使用两个指针,一前一后遍历,总会出现前指针==后指针的情况

参考资料:


判断两个无环链表是否相交,解决方式(2种):

  • 将第一个链表尾部的next指针指向第二个链表,两个链表组成一个链表。
    • 判断这一个链表是否有环,有环则相交,无环则不相交
  • 直接判断两个链表的尾节点是否相等,如果相等则相交,否则不相交

判断两个有环链表是否相交(注:当一个链表中有环,一个链表中没有环时,两个链表必不相交):

  • 找到第一个链表的环点,然后将环断开(当然不要忘记了保存它的下一个节点),然后再来遍历第二个链表,如果发现第二个链表从有环变成了无环,那么他们就是相交的嘛,否则就是不相交的了。

参考资料:

4.5keepAlive含义

  • HTTP协议的Keep-Alive意图在于连接复用,同一个连接上串行方式传递请求-响应数据
  • TCP的KeepAlive机制意图在于保活、心跳,检测连接错误

参考资料:

最后

如果大家有更好的理解方式或者文章有错误的地方还请大家不吝在评论区留言,大家互相学习交流~~~

如果想看更多的原创技术文章,欢迎大家关注我的微信公众号:Java3y。公众号还有海量的视频资源哦,关注即可免费领取。

可能感兴趣的链接: