一、java基础
1. 解释下什么是面向对象?面向对象和面向过程的区别?
面向对象: 面向对象的方法主要是把事物给对象化,包括其属性和行为。
面向对象编程更贴近实际生活的思想。总体来说面向对象的底层还是面向过程,面向过程抽象成类,然后封装,方便使用就是面向对象,(万物皆对象)。
区别:
面向对象的编程方式使得每一个类都只做一件事。 面向过程会让一个类越来越全能,就像一个管家一样做了所有的事。而面向对象像是雇佣了一群职员,每个人做一件小事,各司其职,最终合作共赢!
2.面向对象的三大特性?分别解释下?
面向对象的三大特性:封装、继承、多态。
封装: 就是把对象的属性和行为(数据)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节,就是把不想告诉或者不该告诉别人的东西隐藏起来,把可以告诉别人的公开,别人只能用我提供的功能实现需求,而不知道是如何实现的。增加安全性。
继承: 子类继承父类的数据属性和行为,并能根据自己的需求扩展出新的行为,提高了代码的复用性。
多态: 指允许不同的对象对同一消息做出相应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。封装和继承几乎都是为多态而准备的,在执行期间判断引用对象的实际类型,根据其实际的类型调用其相应的方法。
3. JDK、JRE、JVM 三者之间的关系?

JDK(Java Development Kit)是整个 Java 的核心,是 java 开发工具包,包括了 Java 运行环境 JRE、Java 工具和 Java 基础类库。
JRE(Java Runtime Environment)是运行 JAVA 程序所必须的环境的集合,包含 java 虚拟机和 java 程序的一些核心类库。
JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,是整个 java 实现跨平台的最核心的部分,能够运行以 Java 语言写作的软件程序。
4. 重载和重写的区别?
重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
5. Java 中是否可以重写一个 private 或者 static 方法?
Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法类的任何实例都不相关,所以概念上不适用。
Java中也不可以覆盖private的方法,因为private修饰的变量和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问到private变量或方法的,当然也不能覆盖。
6. 构造方法有哪些特性?
- 名字与类名相同;
- 没有返回值,但不能用void声明构造函数;
- 生成类的对象时自动执行,无需调用。
7. 在 Java 中定义一个不做事且没有参数的构造方法有什么作用?
Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定 义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类 中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没 有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数 的构造方法。
8. Java 中创建对象的几种方式?

扩展:
通过Class.newInstance()和Constructor.newInstance()两种反射方法创建对象的异同?
首先两种方式在源码里所在的位置:
Class.newInstance() → Inside java.lang 包
Constructor.newInstance() → Inside java.lang.reflect 包
使用方法:
(1)Class.newInstance():
Class.forName("HelloWorld").newInstance();
或者
HelloWorld.class.newInstance();
(2)Constructor.newInstance()
HelloWorld.class.getConstructor().newInstance();
二者区别:
Class.newInstance()只能反射无参的构造器; Constructor.newInstance()可以反任何构造器;
Class.newInstance()需要构造器可见(visible); Constructor.newInstance()可以反私有构造器;
Class.newInstance()对于捕获或者未捕获的异常均由构造器抛出; Constructor.newInstance()通常会把抛出的异常封装成InvocationTargetException抛出;
因为以上差异,所以很多框架使用的都是构造器反射的方式获取对象,像Spring, Guava, Zookeeper, Jackson, Servlet 等。
9. 抽象类和接口有什么区别?
区别:
抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
抽象类可以有构造函数;接口不能有。
抽象类可以实现很多个接口;但是只能继承一个抽象类。
抽象类中的成员变量,可以是变量,也可以是常量;接口中的成员变量只能是公共的静态的常量。
设计层面看,抽象类是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为规范。
抽象类与接口的设计层次是不同的:抽象类是一种自下而上的设计,先有子类才能提取公同的属性与行为,抽象出父类; 接口是一种自上而下的设计,先规定行为方法,只要可以实现这些行为,就可以成为接口的实现类。
扩展:
抽象类的成员特点
成员变量
- 可以是变量
- 也可以是常量
构造方法
- 有构造方法,但是不能实例化
- 构造方法的作用:用于子类访问父类数据的初始化
成员方法
- 可以有抽象方法:限定子类必须要完成某些动作
- 也可以是非抽象方法:提高代码的复用性
接口 接口的成员说明
接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。
在JDK8之前,接口中只允许出现:
(1)公共的静态的常量:其中public static final可以省略
(2)公共的抽象的方法:其中public abstract可以省略
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
在JDK1.8时,接口中允许声明默认方法和静态方法:
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
在JDK1.9时,接口又增加了:
(5)私有方法
除此之外,接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要初始化。
接口面试题:
1、为什么接口中只能声明公共的静态的常量?
因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。
例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA
例如:尚硅谷学生行为规范中规定学员,早上8:25之前进班,晚上21:30之后离开等等。
2、为什么JDK1.8之后要允许接口定义静态方法和默认方法呢?
因为它违反了接口作为一个抽象标准定义的概念。
静态方法:
因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。
默认方法:
(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。
(2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。
3、为什么JDK1.9要允许接口定义私有方法呢?
因为我们说接口是规范,规范时需要公开让大家遵守的
私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。
4、一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法怎么处理?
第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:
第二种解决方案可以使用 super 来调用指定接口的默认方法:
10. 静态变量和实例变量的区别?
1.首先在语法定义上区别:静态变量前面要加static,实例变量不用
2.在程序运行时:
实例变量输入对象的属性,必须创建了实例对象(如 new)才会被分配空间,才可以使用实例变量,静态变量不属于某个实例对象,而是属于类,也叫类变量,只要程序加载了类的字节码,不用创建任何实例对象就会被分配空间,就可以被使用
3.总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
11.short s1 = 1;s1 = s1 + 1;有什么错?那么 short s1 = 1; s1 += 1;呢?有没有错误?
对于short s1=1; s1=s1+1; 由于s1+1运算的时候会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时候,编译器件将报告需要强制转换类型的错误。
对于short s1=1; s1+=1; 由于+=是java语言规定的运算符,java编译器会对它进行特殊的额处理,因此可以正确的编译。
12. Integer 和 int 的区别?
1.Integer是int的包装类,int则是java的一种基本数据类型
2.Integer变量必须实例化后才能使用,而int变量不需要
3.Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
4.Integer的默认值是null,int的默认值是0
基本类型包装类的概述:
- 将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据。
- 常用的操作之一:用于基本数据类型与字符串之间的转换。
自动装箱:
把基本类型转换为包装类类型
自动拆箱:
把包装类类型转换为基本类型
13. switch 语句能否作用在 byte 上,能否作用在 long 上,能否作用在 String 上?
- switch可作用于char byte short int
- switch可作用于char byte short int对应的包装类
- switch不可作用于long double float boolean,包括他们的包装类
- switch中可以是字符串类型,String(jdk1.7之后才可以作用在string上)
- switch中可以是枚举类型
14.final、finally、finalize 的区别
final表示不可变的,它可以用来修饰类,方法和变量。
当它修饰类的时候表示该类是不能被继承的,因为抽象类就是用来被继承的,所以abstract关键字和final关键字不能共存。
当它修饰方法的时候表示该方法是不能被重写的。
当它修饰变量的时候表示该变量的值不能发生变化也就是该变量为一个常量。对于用final修饰的变量我们必须在申明它的时候赋值或者是在构造函数中给它赋值。
finally是异常处理中的一个关键字
它一般用于资源释放,比如我们可以在finally块中关闭数据库连接,在这个结构中不管异常有没有发生finally中的代码都会执行。
但是finally中的代码不是一定会被执行。在以下情况下不会执行finally中的代码:
- 在进入try块之前程序发生异常。
- 在try块中调用了System.exit(0)终止了虚拟机的运行。
- 在try块或catch块中程序被中断,比如说死机。
finalize: 是Object类的一个方法
因为所有的类都继承自Object类,所以所有的类都有finalize方法。 在垃圾收集器将该对象清除出内存之前会先调用它的finalize方法,做一些内存清理工作,通常这部分内存是非java内存,如socket,finalize只会被调用一次。
15. == 和 equals 的区别?
== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况
- 情况 1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 情况 2:类覆盖了 equals() 方法。一般,我们都覆盖equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
16.hashCode 0基础java工作 与 equals(重要)
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals时必须重写 hashCode 方法?”
hashCode()介绍
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整 数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有 hashCode
我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。 如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
hashCode()与 equals()的相关规定
- 如果两个对象相等,则 hashcode 一定也是相同的
- 两个对象相等,对两个对象分别调用 equals 方法都返回 true
- 两个对象有相同的 hashcode 值,它们也不一定是相等的
- 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
17.Java 中的参数传递时传值呢?还是传引用?
当基本数据类型作为参数传递时,传递的是实参值的副本,即传的是值,无论在函数中怎么操作这个副本,实参的值是不会被改变的
在Java中对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。如果修改的话,原对象的值是会改变的

18.如何实现对象的克隆?
- 实现 Cloneable 接口并重写 Object 类中的 clone()方法;
- 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真
19.深克隆和浅克隆的区别?
浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
深克隆不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
20.什么是 Java 的序列化,如何实现 Java 的序列化?
序列化:把Java对象转换为字节序列的方法。
反序列化:把字节序列恢复到Java对象的过程。
首先我们要把准备要序列化类,实现 Serializabel接口。其次,使用对象输入流ObjectInputStream和对象输出流ObjectOutputStream来完成对象的反序列化和序列化的任务。
对于不想进行序列化的变量,使用 transient 关键字修饰。
21.什么情况下需要序列化?
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。
22.Java 的泛型是如何工作的 ? 什么是类型擦除 ?
1、泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
2、泛型通过类型擦除来将变量变为一个类型,编译器在编译时擦出了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
3、泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
23.什么是泛型中的限定通配符和非限定通配符 ?
限定通配符: 对类型进行了限制。有两种限定通配符:
一种是它通过确保类型必须是T的子类来设定类型的上界,
另一种是它通过确保类型必须是T的父类来设定类型的下界。
泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。
非限定通配符: 表示了非限定通配符,因为可以用任意类型来替代。
限定通配符包括两种:
- 表示类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类
- 表示类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类
非限定通配符: 类型为,可以用任意类型来替代。
24.Java 中的反射是什么意思?有哪些应用场景?
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
应用场景:
反射是框架设计的灵魂
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架,也是利用CGLIB 反射机制才得以实现,下面就举例最常见的两个例子,来说明反射机制的强大之处:
JDBC 的数据库的连接
在JDBC 的操作中,如果要想进行数据库的连接,则必须按照以下的几步完成
- 通过Class.forName()加载数据库的驱动程序 (通过反射加载,前提是引入相关了Jar包)
- 通过 DriverManager 类进行数据库的连接,连接的时候要输入数据库的连接地址、用户名、密码
- 通过Connection 接口接收连接
Spring 框架的使用
在 Java的反射机制在做基础框架的时候非常有用,行内有一句这样的老话:反射机制是Java框架的基石。一般应用层面很少用,不过这种东西,现在很多开源框架基本都已经封装好了,自己基本用不着写。 典型的除了hibernate之外,还有spring也用到很多反射机制。最经典的就是xml的配置模式。 Spring 通过 XML 配置模式装载 Bean 的过程:
- 将程序内所有 XML 或 Properties 配置文件加载入内存中
- Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息
- 使用反射机制,根据这个字符串获得某个类的Class实例
- 动态配置实例的属性
Spring这样做的好处是:
- 不用每一次都要在代码里面去new或者做其他的事情
- 以后要改的话直接改配置文件,代码维护起来就很方便了
- 有时为了适应某些需求,Java类里面不一定能直接调用另外的方法,可以通过反射机制来实现
25.反射的优缺点?
反射的优点:
反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创和控制任何类的对象,无需提前硬编码目标类
反射的缺点:
性能问题,使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用
26.动态代理是什么?有哪些应用?
动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法。
Java 中实现动态的方式:JDK 中的动态代理 和 Java类库 CGLib。
应用场景如:
- 统计每个 api 的请求耗时
- 统一的日志输出
- 校验被调用的 api 是否已经登录和权限鉴定
- Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程
27.怎么实现动态代理?
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
28.static 关键字的作用?
static是一个修饰符,用于修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能。
- static修饰成员方法
static修饰的方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都必须依赖具体的对象才能够被调用。
但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
- static修饰成员变量
static修饰的变量也称为静态变量,静态变量和非静态变量的区别是:静态变量被所有对象共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。
- static修饰代码块
static关键字还有一个比较重要的作用就是用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来依次执行每个static块,并且只会执行一次。
static块可以优化程序性能,是因为它的特性:只会在类被初次加载的时候执行一次。
29.super 关键字的作用?
super相当于是指代当前的对象的父类,调用父类的属性、方法和构造方法
(1)super.变量/对象名;
使用这种方法可以直接访问父类中的变量或对象,进行修改赋值等操作
(2)super.方法名();
直接访问并调用父类中的方法
(3)super();
调用父类的初始化方法,其实就是调用父类中的public xxx()方法,通常第三种指代的是super()的省略写法,系统会默认添加此句。
特殊情况:如果父类没有无参的构造函数,所以子类需要在自己的构造函数中显示的调用父类的构造函数,即不能使用系统默认的“super()”,而需要显性写出super(xxx)
super的另外一个作用是调用父类的protected函数。只有通过"super"这个魔咒,我们才能操作父类的protected成员,别无它法。
30.字节和字符的区别?
(一)“字节”的定义
字节(Byte)是一种计量单位,表示数据量多少,它是计算机信息技术用于计量存储容量的一种计量单位。
(二)“字符”的定义
字符是指计算机中使用的文字和符号,比如1、2、3、A、B、C、~!·#¥%……—*()——+、等等。
(三)“字节”与“字符”的区别
它们完全不是一个位面的概念,所以两者之间没有“区别”这个说法。
31.String 为什么要设计为不可变类?
1. 字符串常量池的需要
字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
2. 允许String对象缓存HashCode
Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。
字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码. 在String类的定义中有如下代码:
private int hash;//用来缓存HashCode
3. 安全性
String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。
String不可变的原因包括 设计考虑,效率优化问题,以及安全性这三大方面
32.String、StringBuilder、StringBuffer 的区别?
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
String 中的 String 类中使用 final 关键字修饰字符数组来保存字符串,privatefinal char value[] ,String 对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
小结:
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据用 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据用 StringBuilder。
33.String 字符串修改实现的原理?
原理:jdk1.8之后字符串拼接底层就是创建了一个StringBuilder,然后调用append方法,最后调用toString转化成String
34.String 类的常用方法都有那些?
下面列举了20个常用方法。
1、和长度有关:
2、和数组有关:
3、和判断有关:
4、和改变内容有关:
5、和位置有关:
35.字节流和字符流有什么区别?
字节流按 8 位传输,以字节为单位输入输出数据;
字符流按 16 位传输,以字符为单位输入输出数据。
但是不管文件读写还是网络发送接收,信息的最小存储单元都是字节。
36.Java 中的 IO 流的分类?说出几个你熟悉的实现类?
分为两种:


37.BIO、NIO、AIO 有什么区别?
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO
38.Threadloal 的原理
ThreadLocal:为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过 threadlocal 保证线程的安全性。
其实在 ThreadLocal 类中有一个静态内部类ThreadLocalMap(其类似于 Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap 中元素的 key 为当前ThreadLocal 对象,而 value 对应线程的变量副本。
ThreadLocal 本身并不存储值,它只是作为一个 key 保存到 ThreadLocalMap中,但是这里要注意的是它作为一个 key 用的是弱引用,因为没有强引用链,弱引用在 GC的时候可能会被回收。这样就会在ThreadLocalMap 中存在一些 key 为 null 的键值对 (Entry)。因为 key 变成 null 了,我们是没法访问这些 Entry 的,但是这些 Entry 本身是不会被清除的。如果没有手动删除对应 key 就会导致这块内存即不会回收也无法访问,也就是内存泄漏。
使用完 ThreadLocal 之后,记得调用 remove 方法。在不使用线程池的前提下,即使不调用 remove 方法,线程的"变量副本"也会被 gc 回收,即不会造成内存泄漏的情况
39.同步锁、死锁、乐观锁、悲观锁
同步锁:
当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。Java 中可以使用 synchronized 关键字来取得一个对象的同步锁
死锁:
何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
乐观锁:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS 算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_conditio 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。
40.说一下 synchronized 底层实现原理?
synchronized 可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进 入到临界区,同时它还可以保证共享变量的内存可见性。
Java 中每一个对象都可以作为锁,这是 synchronized 实现同步的基础:
- 普通同步方法,锁是当前实例对象
- 静态同步方法,锁是当前类的 class 对象
- 同步方法块,锁是括号里面的对象
41.synchronized 和 volatile 的区别是什么?
volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
42.synchronized 和 Lock 有什么区别?
首先 synchronized 是 java 内置关键字,在 jvm 层面,Lock 是个 java 类;
synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁;
synchronized 会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发 生异常会释放锁),Lock 需在 finally 中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
用 synchronized 关键字的两个线程 1 和线程 2,如果当前线程 1 获得锁,线程 2 线程 等待。如果线程 1 阻塞,线程 2 则会一直等待下去,而 Lock 锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
synchronized 的锁可重入、不可中断、非公平,而 Lock 锁可重入、可判断、可公平(两者皆可);
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/5237.html