面试篇【二】

  Java   21分钟   372浏览   0评论

TCP 和 UDP 区别?

  • TCP 基于连接,UDP 基于无连接。

  • TCP 要求系统资源较多,UDP 较少。

  • UDP 程序结构较简单。

  • TCP 保证数据正确性,UDP 可能丢包。

  • TCP 保证数据顺序,UDP 不保证。

TCP/IP 协议涉及哪几层架构?

应用层 传输层 互连网络层 网络接口层。

描述下 TCP 连接 4 次挥手的过程?为什么要 4 次挥手?

因为 TCP 是全双工,每个方向都必须进行单独关闭。关闭连接时,当 Server 端收到 FIN报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉 Client端,”你发的 FIN 报文我收到了”。只有等到 Server 端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四步握手。

计算机插上电源操作系统做了什么?

  • 加电––––打开电源开关,给主板和内部风扇供电。

  • 启动引导程序––––CPU 开始执行存储在 ROM BIOS 中的指令。

  • 开机自检––––计算机对系统的主要部件进行诊断测试。

  • 加载操作系统––––计算机将操作系统文件从磁盘读到内存中。

  • 检查配置文件,定制操作系统的运行环境––––读取配置文件,根据用户的设置对操作系统进行定制。

  • 准备读取命令和数据––––计算机等待用户输入命令和数据。

Linux 操作系统设备文件有哪些?

字符设备、块设备。

多线程同步有哪些方法?

  • 使用 synchronized 关键字

  • wait 和 notify

  • 使用特殊域变量 volatile 实现线程同步

  • 使用重入锁实现线程同步

  • 使用局部变量来实现线程同步

  • 使用阻塞队列实现线程同步

  • 使用原子变量实现线程同步

一个对象的两个方法加 synchronized,一个线程进去 sleep,另一个线程可以进入到另一个方法吗?

不能。

什么是可重入锁(ReentrantLock)?

举例来说明锁的可重入性

public class UnReentrant {
    Lock lock = new Lock();

    public void outer() {
        lock.lock();
        inner();
        lock.unlock();
    }

    public void inner() {
        //do something lock.unlock();
        lock.lock();
    }
}

outer 中调用了 inner,outer 先锁住了 lock,这样 inner 就不能再获取 lock。其实调用outer 的线程已经获取了 lock 锁,但是不能在 inner 中重复利用已经获取的锁资源,这种锁即称之为不可重入可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码 块。

synchronized、ReentrantLock 都是可重入的锁,可重入锁相对来说简化了并发编程的开发。

创建线程的三个方法是什么?

  • 通过继承 Thread 类创建线程类。

  • 实现 Runnable 接口创建线程类。

  • 通过 Callable 和 Future 接口创建线程。

Java 怎么获取多线程的返回值?

  • 主线程等待。

  • 使用 Thread 的 join 阻塞当前线程等待。

  • 实现 Callable 接口(通过 FutureTask 或线程池的 Future)。

线程池有哪几种创建方式?

Java 通过 Executors(jdk1.5 并发包)提供四种线程池,分别为:

  • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

线程池参数有哪些?

  • corePoolSize 核心线程大小。

  • maximumPoolSize 线程池最大线程数量。

  • keepAliveTime 空闲线程存活时间。

  • unit 空间线程存活时间单位。

  • workQueue 工作队列。

  • threadFactory 线程工厂。

  • handler 拒绝策略。

线程池拒绝策略有哪些?

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常(默认拒绝策略)。

  • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。

你认为对线程池的核心参数实现自定义可配置,三个核心参数是?

  • corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。

  • maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。

  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。

ThreadPoolExecutor

线程池corePoolSize=5,maximumPoolSize=10,queueCapacity=10,有 20 个耗时任务交给这个线程池执行,线程池会如何执行这 20 个任务?

  • 如果当前线程数<corePoolSize,如果是则创建新的线程执行该任务 。

  • 如果当前线程数>=corePoolSize,则将任务存入 BlockingQueue 。

  • 如果阻塞队列已满,且当前线程数<maximumPoolSize,则新建线程执行该任务。

  • 如果阻塞队列已满,且当前线程数>=maximumPoolSize,则抛出异常 。

  • RejectedExecutionException,告诉调用者无法再接受任务了。

给用户发消息任务超出队列,你用哪个拒绝策略?有其他方法吗 ?

ThreadPoolExecutor.CallerRunsPolicy

  • 无界队列(LinkedBlockingQuene),继续添加任务到阻塞队列中等待执行。

  • 用消息队列存任务数据,线程池慢慢处理。

Java8 新特性有哪些了解?

  • 接口的默认方法

  • Lambda 表达式

  • 函数式接口

  • 方法和构造函数引用

  • Lamda 表达式作用域

  • 内置函数式接口

  • Optional

  • Streams(流)

  • Parallel Streams(并行流)

  • Maps

  • Date API(日期相关 API)

  • Annotations(注解)

什么时候用多线程、为什么要设计多线程?

高并发

系统接受实现多用户多请求的高并发时,通过多线程来实现。

线程后台处理大任务

一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的。那用户就不得不等待它执行完。这时候可以开线程把花大量时间处理的任务放在线程处理,这样线程在后台处理时,主程序也可以继续执行下去,用户就不需要等待。线程执行完后执行回调函数。

大任务

大任务处理起来比较耗时,这时候可以起到多个线程并行加快处理(例如:分片上传)。

好处:可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其他的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

多线程越多效率越高吗?

不是

当线程总数较少时,线程越多,效率越高。

当线程总数较多时,由于线程本身调用耗时,线程越多,效率越低。

线程数越多会造成:

  • 线程的生命周期开销非常高

  • 消耗过多的 CPU 资源。

多线程会产生哪些并发问题 ?

安全性问题:在单线程系统上正常运行的代码,在多线程环境中可能会出现意料之外的结果。

活跃性问题:不正确的加锁、解锁方式可能会导致死锁 or 活锁问题。

性能问题:多线程并发即多个线程切换运行,线程切换会有一定的消耗并且不正确的加锁。

Mybatis 如何将对象转换成 SQL?

SQL 绑定是在加载 Mybatis 配置文件,然后扫描到哪个 mapper 子节点,再加载mapper 映射文件,扫描里面的 SQL 节点,然后封装成对象(MappedStatement,在这个对象的 SqlSource 封装着 sql 语句)。所有的配置信息保存在 Configuration 类,最后动态代理执行的时候,取出来封装 sql 的对象,执行 sql。

虚拟内存是什么,虚拟内存的原理是什么?

虚拟内存是计算机系统内存管理的一种技术。

虚拟内存有以下两个优点:

  • 虚拟内存地址空间是连续的,没有碎片。

  • 虚拟内存的最大空间就是 cup 的最大寻址空间,不受内存大小的限制,能提供比内存更大的地址空间。

当每个进程创建的时候,内核会为每个进程分配虚拟内存,这个时候数据和代码还在磁盘上,当运行到对应的程序时,进程去寻找页表,如果发现页表中地址没有存放在物理内存上,而是在磁盘上,于是发生缺页异常,于是将磁盘上的数据拷贝到物理内存中并更新页表,下次再访问该虚拟地址时就能命中了。

栈会溢出吗?什么时候溢出?方法区会溢出吗?

栈是线程私有的,它的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型。如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError 异常,方法递归调用产生这种结果。如果 Java 虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么 Java 虚拟机将抛出一个 OutOfMemory 异常。(线程启动过多)。

方法区会发生溢出。

HotSpot jdk1.7 之前字符串常量池是方法区的一部分,方法区叫做“永久代”,在 1.7 之前无限的创建对象就会造成内存溢出,提示信息:PermGen space 而是用 jdk1.7 之后,开始逐步去永久代,就不会产生内存溢出。方法区用于存放 Class 的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等,如果动态生成大量的 Class 文件,也会产生内存溢出。常见的场景还有:大量 JSP 或动态产生 JSP 文件的应用(JSP 第一次运行时需要编译为 java 类)、基于 OSGi 的应用(即使是同一个类文件,被不同的类加载器加载也会视为不同的类)。

JVM 如何加载类的?

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。

加载

加载是类加载过程中的一个阶段, 这个阶段会在内存中生成一个代表这个类 java.lang.Class对象, 作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。

验证

这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static指令是程序被编译后, 存放于类构造器方法之中。

但是注意如果声明为:

public static final int v = 8080;

在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v 赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的public static int v = 8080;

实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static指令是程序被编译后, 存放于类构造器方法之中。但是注意如果声明为:

在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v 赋值为 8080。

初始化

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。

自己写过 String 类能加载吗,之前的 String 是什么时候加载进去的?

不能加载,因为双亲委派机制,JVM 出于安全性的考虑,全限定类名相同的 String 是不能被加载的。

java.lang.String 会被顶级类加载器 Bootstrap Classloader 加载。当 class 文件被加载到内存中时,类文件常量池中的其他常量会加载到运行时常量池,但是字符串常量不会。它会首先在堆区中创建一个字符串对象,然后再把这个对象的引用保存到全局字符串常量池中。

描述 ThreadLocal(线程本地变量)的底层实现原理及常用场景?

实现原理:

  • 每个 Thread 线程内部都有一个 ThreadLocalMap;以线程作为 key,泛型作为 value,可以理解为线程级别的缓存。每一个线程都会获得一个单独的 map。

  • 提供了 set 和 get 等访问方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此 get 方法总是返回由当前执行线程在调用 set 时设置的最新值。

应用场景:

  • JDBC 连接

  • Session 管理

  • Spring 事务管理

  • 调用链,参数传递

  • AOP

ThreadLocal 是一个解决线程并发问题的一个类,用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择 ThreadLocal 变量。例如,由于 JDBC 的连接对象不是线程安全的,因此,当多线程应用程序在没有协同的情况下,使用全局变量时,就不是线程安全的。通过将 JDBC 的连接对象保存到 ThreadLocal 中,每个线程都会拥有属于自己的连接对象副本。

什么是微服务架构?

微服务架构就是将单体的应用程序分成多个应用程序,这多个应用程序就成为微服务,每个微服务运行在自己的进程中,并使用轻量级的机制通信。这些服务围绕业务能力来划分,并通过自动化部署机制来独立部署。这些服务可以使用不同的编程语言,不同数据库,以保证最低限度的集中式管理。

微服务有哪些特点?

  • 解耦 – 系统内的服务很大程度上是分离的。因此,整个应用程序可以轻松构建,更改和扩展

  • 组件化 – 微服务被视为可以轻松更换和升级的独立组件

  • 业务能力 – 微服务非常简单,专注于单一功能

  • 自治 – 开发人员和团队可以彼此独立工作,从而提高速度

  • 持续交付 – 通过软件创建,测试和批准的系统自动化,允许频繁发布软件

  • 责任 – 微服务不关注应用程序作为项目。相反,他们将应用程序视为他们负责的产品

  • 分散治理 – 重点是使用正确的工具来做正确的工作。这意味着没有标准化模式或任何技术模式。开发人员可以自由选择最有用的工具来解决他们的问题

  • 敏捷 – 微服务支持敏捷开发。任何新功能都可以快速开发并再次丢弃

Lambda 表达式是啥?优缺点?

lambda 表达式,也被称为闭包,它是推动 Java 8 发布的最重要新特性。lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),使用 Lambda 表达式可以使代码变的更加简洁紧凑。

优点:

  • 代码更加简洁

  • 减少匿名内部类的创建,节省资源

  • 使用时不用去记忆所使用的接口和抽象函数

缺点:

  • 不易于后期维护,必须熟悉 lambda 表达式和抽象函数中参数的类型

  • 可读性差

  • 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)

  • 不容易调试。

  • 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。

讲一下 Lambda 的表达式作用域(Lambda Scopes)。

访问局部变量

  • 我们可以直接在 lambda 表达式中访问外部的局部变量:但是和匿名对象不同的是,这里的变量可以不用声明为 final,该代码同样正确,不过这里的变量必须不可被后面的代码修改(即隐性的具有 final 的语义)

访问字段和静态变量

  • 与局部变量相比,我们对 lambda 表达式中的实例字段和静态变量都有读写访问权限。该行为和匿名对象是一致的。

访问默认接口方法

  • 无法从 lambda 表达式中访问默认方法。

MySQL 事务的特性有什么,说一下分别是什么意思?

  • 原子性:即不可分割性,事务要么全部被执行,要么就全部不被执行。

  • 一致性/可串性:事务的执行使得数据库从一种正确状态转换成另一种正确状态。

  • 隔离性:在事务正确提交之前,不允许把该事务对数据的任何改变提供给任何其他事务。

  • 持久性:事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。

如果你觉得文章对你有帮助,那就请作者喝杯咖啡吧☕
微信
支付宝
  0 条评论