引入
大厂的面试的基础要求就是算法和八股文,在一面无论如何都是要拿出来背一背的。
目前博主正在学习,内容持续更新中,想跟着博主的步伐一起学习的童鞋,可以关注一波,发个评论,大更新的时候我踢你一脚
文章目录
- 引入
- JAVA基础问题
- 基本语法问题
- 基本数据结构
- 基本数据类型有哪些?
- byte类型127+1等于多少?
- Arraylist和Linkedlist的区别?
- 说一说hashCode()和equals()的关系
- String类有哪些方法?
- String、StringBuffer、StringBuilder有什么区别?
- 对字符串拼接的理解
- 将字符串反转
- 高并发中的集合有哪些问题?
- Java的接口和抽象类的区别有哪些?
- java中有哪些容器?
- List和Set的区别
- hashCode与equals区别
- HashMap和HashSet的区别
- Collection和Collections有什么区别?
- list和set的区别?
- Java中的容器,线程安全和线程不安全的分别有哪些?
- HashMap和HashTable有什么区别?
- 说一下HashMap的实现原理?
- 迭代器iterator怎么使用?
- iterator和ListIterator有什么区别?
- 怎么确保一个集合不能被修改?
- 队列和栈的区别是什么?
- Java的一些机制
- Java多线程编程
- 谈谈ReadWriteLock和StampedLock
- 线程的run()和start()有什么区别?
- 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
- Synchronized用过吗,其原理是什么?
- JVM对Java的原生锁做了哪些优化?
- 为什么wait(),notify()和notifyAll()必须在同步方法或者同步块中被调用?
- Java如何实现多线程之间的通讯和协作?
- 为什么说Synchronized是非公平锁?
- Synchronized是一种什么样的锁?
- 乐观锁又是什么?
- 对比一下Synchronized和ReentrantLock的异同
- Java8开始ConcurrentHashMap,为什么舍弃分段锁?
- Java数据库基础问题
- JavaWeb基础问题
JAVA基础问题
为什么Java代码可以实现一次编写、到处运行?
JVM是Java跨平台的关键,同一份Java源代码在不同的平台上运行,它不需要做任何的改变,并且只需要编译一次。而编译好的字节码,是通过JVM这个中间的“桥梁”实现跨平台的,JVM是与平台相关的软件,它能将统一的字节码翻译成该平台的机器码。
JDK和JRE有什么区别?
JDK是Java的开发工具包;JRE是Java的运行环境。
JDK中包含JRE,JDK中有一个名为jre的目录,里面包含两个文件夹bin和lib,bin就是JVM(Java虚拟机),lib就是JVM工作所需要的类库。
一个Java文件里可以有多个类吗?
一个java文件里可以有多个类,但最多只能有一个被public修饰的类
如果这个java文件中包含public修饰的类,则这个类的名称必须和java文件名一致
- 在修饰成员变量/成员方法时:
- private:该成员可以被该类内部成员访问
- default:该成员可以被该类
- protected:该成员可以被该类内部成员访问
- public:该成员可以被任意包下,任意类的成员进行访问
- 在修饰类时:
- default:该类可以被同一包下其他的类访问
- public:该类可以被任意包下,任意的类所访问
JVM内存问题
大多数JVM将内存区域划分为Method Area(None-Heap)方法区,Heap(堆) ,Program Counter Register(程序计数器),VMStack(虚拟机栈,JAVA方法栈),Native Method Stack(本地方法栈),其中Method Area和Heap是线程共享的,
VM Stack,Native Method Stack和Program Counter Register是非线程共享的。
- 原因:JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。
- 在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。
Java面向对象有哪些特征?
面向对象的三大特征:封装、继承、多态。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
原则: 将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。
封装的操作步骤
1. 使用 private 关键字来修饰成员变量。
2.对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法。
封装的意义在于明确标识出允许外部使用的所有成员函数和数据项,内部细节对外部调用透明,外部调用无需修改或者关心内部实现。
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继
承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的
重要手段。
好处
1. 提高代码的复用性。
2. 类与类之间产生了关系,是多态的前提。
继承的特点
1. Java只支持单继承,不支持多继承。
2. Java支持多层继承(继承体系)。
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。
简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时, B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。
运行时的多态是面向对象最精髓的东西,要实现多态需要做
两件事:
1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);
2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
多态的三个条件:继承、方法重写、父类引用指向子类对象。
面向过程比较直接高效,面向对象更易于复用、扩展和维护。
Java1.8的新特性有哪些?
实现了lanbda表达式(这个极大的简化了代码量)
支持多重注解
基本语法问题
==和equals的区别是什么?
- 对于基本类型,==比较的是值
- 对于引用类型,==比较的是地址
- equals不能用于基本类型的比较,如果没有重写equals,equals就相当于==
- 如果重写了equals方法,比较的是对象的内容
final在Java中有什么作用?
- 用来修饰一个引用
- 如果引用为基本数据类型,则该引用为常量,该值无法修改
- 如果引用为引用数据类型,该对象本身可以修改,但指向该对象的地址的引用不能修改
- 如果引用为类的成员变量,则必须当场赋值
- 用来修饰一个方法
这个方法将成为最终方法,无法被子类重写,但是可以被继承 - 用来修饰类
这个类成为最终类,无法被继承
遇到过异常吗,如何处理?
在Java中,可以按照三个步骤处理异常:
- 捕获异常
将业务代码包裹在try块内部,当业务代码中发生任何异常时,系统都会为此异常创建一个异常对象。 - 处理异常
在catch块中处理异常时,应该先记录日志,便于以后追溯这个异常;然后根据异常的类型、结合当前的业务情况,进行相应的处理。 - 回收资源
如果业务代码打开了某个资源,比如数据库连接、网络连接、磁盘文件等,则需要在这段业务代码执行完毕后关闭这项资源。将关闭资源的代码写在finally块内,可以满足这种需求,即无论是否发生异常,finally块内的代码总会被执行。
throw和throw是的区别?
- throw
作用在方法内,表示抛出具体异常,由方法体内的语句处理 - throws
作用在方法的声明上,表示声明异常,代表该方法可能出现异常
在finally中return会发生什么?
在通常情况下,不要在finally块中使用return、throw等导致方法终止的语句,一旦在finally块中使用了return、throw语句,将会导致try块、catch块中的return、throw语句失效。
注:
当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含finally块,如果没有finally块,程序立即执行return或throw语句,方法终止;如果有finally块,系统立即开始执行finally块。只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句;如果finally块里也使用了return或throw等导致方法终止的语句,finally块已经终止了方法,系统将不会跳回去执行try块、catch块里的任何代码。
List<? super T>和List<? extends T>有什么区别?
- ?是类型通配符,List <?>可以表示各种泛型List的父类,意思是元素类型未知的List
- List<? super T>用于设定类型通配符的下限,此处?代表一个未知的类型,但它必须是T的父类
- List<? extends T>用于设定类型通配符的上限,此处?代表一个未知的类型,但它必须是T的子类
说一说Java的四种引用方式
Java对象的四种引用方式分别是强引用、软引用、弱引用、虚引用:
- 强引用:程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象。当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收。
- 软引用:当一个对象只有软引用时,它可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象。软引用通常用于堆内存敏感的程序中。
- 弱引用:弱引用的引用级别低于软引用。当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收弱引用的对象所占用的内存。
- 虚引用:虚引用完全类似于没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,必须和引用队列联合使用。
当垃圾回收器准备回收一个对象时,如果发现它还有引用,就会在回收对象之前,把这个引用加入到引用队列中;程序可以通过判断引用队列中是否加入了引用,来判断被引用的对象是否将要被垃圾回收,这样可以在对象被回收之前采取一些操作。
深拷贝和浅拷贝的区别是什么?
- 浅拷贝仅仅克隆基本类型变量,不克隆引用类型变量
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 。 - 深拷贝即克隆基本类型变量,又克隆引用类型变量
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象。
- 浅拷贝基本类型之前互不影响;深拷贝引用类型其中一个对象改变了地址,就会影响另一个对象
在Java中,为什么不允许从静态方法中访问非静态变量?
静态方法也属于类本身,但是此时没有类的实例,内存中没有非静态变量,所以无法调用。
在Java中,什么时候用重载,什么时候用重写?
- 重载是多态的集中体现,在类中,要以统一的方式处理不同类型数据的时候,可以用重载
- 重写是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写
注:重载是为了提高程序的多样性和健壮性,以适配不同场景使用时,使用重载进行扩展
重写是为了在不修改原方法及源代码的基础上对方法进行扩展或增强时,使用重写
实例化对象的几种方式
- new
- clone
- 通过反射机制创建
- 序列化反序列化
基本数据结构
基本数据类型有哪些?
数据类型 | 含义 |
---|---|
byte | 字节 |
short | 短整型 |
int | 整型 |
long | 长整型 |
float | 单精度浮点型 |
double | 双精度浮点型 |
char | 字符型 |
boolean | 布尔型 |
没有string!!!
没有string!!!
没有string!!!
string是Java设计人员封装的一个final类,不属于基本数据类型,属于引用数据类型,用起来和数组更像。
byte类型127+1等于多少?
byte的范围是-128~127
字节长度为8位,最左边的是符号位,而127的二进制为01111111
,所以执行+1操作时,01111111
变为10000000
,然而计算机中存储负数是用的补码,左边第一位为符号位,
所以10000000
就变为-128了(最高位是1,是负数,对各位取反得01111111
,转换为十进制就是127,加上负号-1,得-128)
想: 玩英雄联盟的可能知道,bug之王之前刚出的时候有很多bug其中就有被铁男拉进去之后再变成他,就会出现负血量,伤害还高的离谱(估计是破败之王的技能机制涉及到两个不同英雄之间的数据交互,加上铁男大招的属性偷取,同一份数据在同时间的双向交互就让bug之王的数据表崩盘了,估计后端程序员个个都是弄~英雄设计师;这个英雄还有一系列bug这里也不介绍了,大家可以搜一下,站在DBA的角度思考一下还是挺有意思的)
Arraylist和Linkedlist的区别?
底层都是用的list结构,
arraylist的物理结构使用的数组结构;Linkedlist的物理结构使用的链表结构。
说一说hashCode()和equals()的关系
hashCode()用于获取哈希码,eauqls()用于比较两个对象是否相等。
- 如果两个对象相等,则它们必须有相同的哈希码
- 如果两个对象有相同的哈希码,则它们未必相等
- hashCode()与equals()具有联动关系,所以equals()方法重写时,通常也要将hashCode()进行重写,使得这两个方法始终满足相关的约定
hashcode是用来查找的,配合基于散列的集合一样使用,比如HashSet、HashMap、HashTable等
String类有哪些方法?
String类是Java最常用的API,它包含了大量处理字符串的方法,能在面试的时候说一些常用的表现出足够的熟悉就可以了
- char charAt(int index) 返回指定索引处的字符
- String substring(int beginIndex ,int endIndex) 从此字符串中截取出一部分子字符串
- String[] split(String regex) 以指定的规则将此字符串分割成数组
String类由final修饰,不能被继承
String a=“abc”;说一下这个过程会创建什么,放在哪里?
JVM会使用常量池来管理字符串直接量,在执行这句话时,JVM会先检查常量池中是否已经存有"abc",若没有则将"abc"存入常量池,否则就复用常量池中已有的"abc",将其引用赋值给变量a。
new String(“abc”) 是去了哪里,仅仅是在堆里面吗?
在执行这句话时,JVM会先使用常量池来管理字符串直接量,即将"abc"存入常量池。然后再创建一个新的String对象,这个对象会被保存在堆内存中。并且,堆中对象的数据会指向常量池中的直接量。
String、StringBuffer、StringBuilder有什么区别?
- String类是不可变类,即一旦一个String对象被创建后,包含在这个对象中的字符序列是不可改变的,直到这个对象被销毁
- StringBuffer对象则代表一个字符序列可变的字符串,可以通过StringBuffer提供的append()\insert()\reverse()\serCharAt()\setLength()等方法改变。
- StringBuffer是线程安全的,StringBuilder是非线程安全的。
对字符串拼接的理解
拼接字符串有很多种方式,其中最常用的有4种,
- + 运算符:如果拼接的都是字符串直接量,则适合使用+运算符实现拼接
- StringBuilder:如果拼接的字符串中包含变量,并不要求线程安全,则适合使用StringBuilder
- StringBuffer:如果拼接的字符串中包含变量,并且要求线程安全,则适合使用StringBuffer
- String类的concat方法,如果只是对两个字符串进行拼接,并且包含变量,则适合使用concat方法
将字符串反转
将对象封装到stringBuilder中,调用reverse方法反转。
package javase;
public class test{
public static void main(String[] args){
String str = "abc";
StringBuilder stringBuilder =new StringBuilder(str);
String ret = stringBuilder.reverse().toString();
System.out.println(ret);
}
}
高并发中的集合有哪些问题?
集合的历史
-
第一代线程安全集合类:Vector、Hashtable
使用synchronized修饰方法,缺点是性能低下 -
线程非安全集合类:Arraylist、HashMap
线程不安全,但是性能较好 -
第三代线程安全集合类:java.util.concurrent.*;ConcurrentHashMap;CopyOnwriteArrayList;CopyOnWriteArraySet
其使用了lock锁
Java的接口和抽象类的区别有哪些?
- 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法;
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
- 抽象类只能继承一个,接口可以实现多个;
(接口的设计目的是对类的行为进行约束;抽象类的设计目的是代码复用;接口类是对行为的抽象)
容器问题
java中有哪些容器?
Java中的集合类主要由Collection和Map这两个接口派生出来的。
List和Set的区别
List:有序,按对象进入的顺序保存对象,可重复,允许多个Null对象
Set:无序,不可重复,最多允许有一个Null对象
hashCode与equals区别
hashCode{}的作用的获取哈希码,能根据键快速的检索出对应的值;
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们也不一定相等
- hashcode()的默认行为是对堆上的对象产生独特值。
HashMap和HashSet的区别
Collection和Collections有什么区别?
- Colleciton是最基本的集合接口,Collection派生了两个子接口list和set,分别定义了两种不同的存储方式
- Collections是一个包装类,它包含各种有关集合操作的静态方法,不嫩实例化,就像一个工具类,服务于Collection框架
list和set的区别?
list简介
有两种list:底层实现方式不同:数组和链表
- ArrayList:由数组实现的List. 允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢(这和底层实现有关系,数组插入和删除需要大范围的移项操作)
- LinkedList: 对顺序访问进行了优化,向list中间插入与删除的开销并不大.随机访问则相对较慢
Set简介
Set具有与Collection完全一样的接口,因此没有任何额外的功能,只是行为不同: Set不保存重复的元素
Set的一些特性:
- 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素,加入Set的元素必须定义equals()方法以确保对象的唯一性.
- Set与Collection有完全一样的接口
- Set接口不保证维护元素的次序
- HashSet:为了快速查找设计的Set;存入HashSet的对象必须定义hashCode()
- TreeSet:保存次序的Set,底层为数结构;使用它可以从Set中提取有序的序列
list与Set区别
- list和Set都是继承自Collection接口
- 特点对比:
- List特点: 元素有放入顺序、元素可重复;
- Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉
- Set和List特性对比:
- Set的检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变
- List和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低(即使使用链表存放也不会比直接算hash值存放的set快,链表插入的时候要改三个引用域,尾插法就还要一次遍历的时间)
Java中的容器,线程安全和线程不安全的分别有哪些?
java.util包下的集合类大部分都是线程不安全的,例如我们常用的HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap,这些都是线程不安全的集合类,但是它们的优点是性能好。
如果需要使用线程安全的集合类,则可以使用Collections工具提供的synchronizedXxx()方法,将这些集合类包装成线程安全的集合类。
主要的线程安全的集合类:
- Vector:就比ArrayList多了个同步化机制
- Stack:栈,继承于Vector
- Hashtable:比HashMap多了个同步机制
- ConcurrentHashMap:一个线程安全的集合
HashMap和HashTable有什么区别?
- HashMap是线程不安全的;HashTable是线程安全的
- HashMap中允许键和值为null,HashTable不允许
- HashMap的默认容器是16,为2倍扩容;HashTable默认是11,为2倍+1扩容;
说一下HashMap的实现原理?
HashMap是基于Map接口,元素以键值对方式存储,允许有null值,HashMap是线程不安全的
HashMap的存储结构
-
JDK1.7中采用数组+链表的存储形式,采取Entry数组来存储key值,每个键值对组成一个Entry实体;Entry类是一个单向的链表结构。
-
在JDK8中进行了优化,采用数组+链表+红黑树的存储结构,当链表长度超过阈值(8个)时,将链表转换为红黑树的结构,在性能上更高。
迭代器iterator怎么使用?
接口的方法:
- java.lang.iterable 接口被java.util.Collection接口继承,iterator()方法返回一个iterator对象
- next()方法获得集合中的下一个元素
- hasNext()检查集合中是否还有元素
- remove()方法将迭代器新返回的元素删除
iterator和ListIterator有什么区别?
- ListIterator继承Iterator
- ListIterator比iterator多实现了一些方法
- add(E e) 将指定的元素插入列表,插入位置为迭代器当前位置之前
- set(E e) 迭代器返回的最后一个元素替换参数e
- hasPrevious() 迭代器当前位置,反向遍历集合是否含有元素
- previous() 迭代器当前位置,反向遍历集合,下一个元素
- previousIndex() 迭代器当前位置,反向遍历集合,返回下一个元素的下标
- previousIndex() 迭代器当前位置,反向遍历集合,返回下一个元素的下标
- nextIndex() 迭代器当前位置,返回下一个元素的下标
- 使用范围不同, Iterator可以迭代所有集合; ListIterator只能用于List及其子类
怎么确保一个集合不能被修改?
用final关键字? 错!!!
final关键字如果修饰的是引用对象,只能保证这个引用的地址值是不能改变的,但是这个引用所指向的对象的内容还是可以改变的.
我们应该采用Collections包中的unmodifableMap()方法,通过这个方法返回的map是不可修改的.
同样的Collections包中还提供了对list和set的方法
- Collections.unmodifiableList(List)
- Collections.unmodifiableSet(Set)
队列和栈的区别是什么?
这个问题大家应该都像到了队列是先进先出的,栈是先进后出的;
但是他们还有一个区别!! 遍历数据的速度不同
- 栈只能从头部取数据,这导致了在遍历数据的时候要为数据开辟一个临时空间来保持数据的一致性
- 而队列是基于地址指针进行遍历的, 可以从头或者尾部开始遍历(不能同时遍历),不需要开辟临时空间,速度要快的多
Java的一些机制
BIO、NIO、AIO有什么区别?
- 同步阻塞BIO
一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
线程发起IO请求,不管内核是否准备好IO操作,从发起请求起,线程一直阻塞,直到操作完成 - 同步非阻塞NIO
一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
NIO主要想解决BIO的不能大并发的问题,线程发起IO请求立即返回。内核在做好IO操作的准备之后,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成 - 异步非阻塞AIO
一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
线程发起IO请求,立即返回;内存做好IO操作的准备之后,做IO操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做IO操作完成或者失败。
Java的反射机制
所谓反射,是java在运行时进行自我观察的能力,通过class、constructor、field、method四个方法获取一个类的各个组成部分。
在Java运行时环境中,对任意一个类,可以知道类有哪些属性和方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于反射机制。
什么是Java序列化?
-
序列化就是一种用来处理对象流的机制。将对象的内容流化,将流化后的对象传输于网络之间。
-
序列化是将对象转换为容易传输的格式的过程。
序列化是通过实现serializable接口,该接口没有需要实现的方法,只是为了标注该对象是可被序列化的。
例如,可以序列化一个对象,然后通过HTTP通过Internet在客户端和服务器之间传输该对象。在另一端,反序列化将从流中心构造成对象。
一般程序在运行时,产生对象,这些对象随着程序的停止而消失,但我们想将某些对象保存下来,这时,我们就可以通过序列化将对象保存在磁盘,需要使用的时候通过反序列化获取到。 -
对象序列化的最主要目的就是传递和保存对象,保存对象的完整性和可传递性。
譬如通过网络传输或者把一个对象保存成本地一个文件的时候,需要使用序列化。
什么是Java的内存模型?
首先这个问题要先了解,并发编程的三大问题:
- CPU缓存,在多核CPU的情况下,带来的可见性问题
- 操作系统对当前执行线程的切换,带来的原子性问题
- 译指重排优化带来的有序性问题
为了解决并发编程的三大问题,提出了JSR-133的解决方案,在JDK5之后开始使用新的Java内存模型。
- Java内存模型的JVM的一种规范,定义了共享内存在多线程程序中读写操作行为的规范,屏蔽了各种硬件和操作系统的访问差异,保证了Java程序在各种平台下对内存的访问效果一致。
- 解决并发问题采用的方式:限制处理器优化和使用内存屏障
- 增强了三个同步原语的内存语义(synchronized、volatile、final)
- 定义了happens-before规则
Java多线程编程
谈谈ReadWriteLock和StampedLock
ReadWriteLock包括两种子锁:
- ReadWriteLock
ReadWriteLock可以实现多个读锁同时进行,但是读写和写写互斥,只能有一个写锁线程在进行 - StampedLock
StampedLock是JDK8提供的一种读写锁,相比较ReentrantReadWriteLock性能更好,因为ReentrantReadWriteLock在读写之间是互斥的,使用的是一种悲观策略,在读线程特别多是情况下,会造成写线程处于饥饿状态,而StampedLock是提供了一种乐观策略,更好的实现读写分离,并且吞吐量不会下降.
StampedLock包括三种锁:
- 写锁writeLock
writeLock是一个独占锁写锁,当一个线程获得该锁后,其他请求读锁或者写锁的线程阻塞 ,获取成功后,会返回一个stamp(凭据)变量来表示该锁的版本,在释放锁时调用unlockWrite方法传递stamp参数。提供了非阻塞式获取锁tryWriteLock。 - 悲观读锁readLock
readLock是一个共享读锁,在没有线程获取写锁情况下,多个线程可以获取该锁。如果有写锁获取,那么其他线程请求读锁会被阻塞。悲观读锁会认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据进行加锁,这是在读少写多的情况下考虑的。请求该锁成功后会返回一个stamp值,在释放锁时调用unlockRead方法传递stamp参数。提供了非阻塞式获取锁方法tryWriteLock - 乐观读锁tryOptimisticRead
tryOptimisticRead相对比悲观读锁,在操作数据前并没有通过CAS设置锁的状态,如果没有线程获取写锁,则返回一个非0的stamp变量,获取该stamp后在操作数据前还需要调用validate方法来判断期间是否有线程获取了写锁,如果是返回值为0则有线程获取写锁,如果不是0则可以使用stamp变量的锁来操作数据。由于tryOptimisticRead并没有修改锁状态,所以不需要释放锁。这是读多写少的情况下考虑的,不涉及CAS操作,所以效率较高,在保证数据一致性上需要复制一份要操作的变量到方法栈中,并且在操作数据时可能其他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性得到了保证。
线程的run()和start()有什么区别?
- 每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作,run()方法称为线程体,通过调用Thread类的start()方法来启动一个线程.
- start()方法用于启动线程,run()方法用于执行线程的运行时代码.
- run()可以重复调用,而start()只能调用一次
- start()方法来启动一个线程,是真正实现多线程运行. run()方法是这线程里的,只是线程里的一个函数,不是多线程的
为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
new一个Tread时,线程进入了新建状态.
调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了.start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作.
而直接执行run()方法,会把run()方法当成一个main线程下的普通方法去执行.
Synchronized用过吗,其原理是什么?
synchronized是Java多线程中元老级的锁了,是面试的高频考点。
- synchronized的作用
- 原子性
所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程中不会被任何因素打断,要么不执行;呈现为线程调度的最小单位,不可分割 - 可见性
是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的 - 有序性
是指程序执行的顺序按照代码先后执行。
//看一个synchronized使用的经典实例--线程安全的单例模式
public class Singleton {
//保证有序性,防止指令重排
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
- synchronized同步原理
数据同步需要依赖锁,锁的同步又依赖谁呢?
synchronized的解决方案是在软件层面依赖JVM;
仙人指路☞
JVM对Java的原生锁做了哪些优化?
- 自旋锁
在线程进行阻塞的时候,先让线程自旋等待一段时间,可能这段时间其它线程已经解锁,就无需让线程再进行阻塞操作 - 自适应自旋锁
自旋的次数不再固定,又前一次自旋次数和锁的拥有者的状态决定 - 锁消除
在动态编译同步代码块的时候,JIT编译器借助逃逸分析技术来判断锁对象是否只被一个线程访问,而没有其他线程 - 锁粗化
当JIT编译器发现一系列的操作都对同一个对象反复加锁解锁,甚至加锁操作出现在循环中,此时会将加锁同步的范围粗化到整个操作系列的外部
为什么wait(),notify()和notifyAll()必须在同步方法或者同步块中被调用?
Java中,任何对象都可以作为锁,并且wait(),notify()等方法用于等待对象的锁或者唤醒线程,在Java的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中.
Java如何实现多线程之间的通讯和协作?
可以通过中断和共享变量的方式实现线程间的通讯和协作.
Java中通过中断线程通信协作的最常见的两种方式:
- synchronized加锁的线程和Object类的wait()、notify()、notifyAll()
- ReentrantLock类加锁的线程的Condition类的await()、signal()、signAll()
线程间直接数据交换(机组的那几种方式): - 管道机制
- 共享临界区
- message消息
为什么说Synchronized是非公平锁?
因为Synchronized获取锁的行为是不公平的,并非是按照申请对象锁的先后时间分配锁的,每次对象锁被释放时,每个线程都有机会获得对象锁,这样有利于提高执行性能,但是也会造成线程饥饿
公平锁:获取不到锁的时候,会自动加入队列,等待线程释放后,队列的第一个线程获取锁(排队是公平的)
非公平锁:获取不到锁的时候,会自动加入队列,等待线程释放锁后所有等待的线程同时去竞争(这对先来的不公平)
可重入:同一个线程可以反复获取锁多次,然后需要释放多次(上面说了Synchronized是非公平锁,就可能导致我释放后同时在其他等候线程前再次获取)哎~我放了,我又拿起来了,就是玩
Synchronized是一种什么样的锁?
Synchronized是一种悲观锁,它的并发策略的悲观的,不管是否产生竞争,任何数据操作都必须加锁
乐观锁又是什么?
乐观锁认为一个对象的操作不会引发冲突,所以每次操作都不进行加锁,只在最后提交更改的时候验证是否发生冲突,如果冲突则再试一遍,直到成功为止,这个尝试的过程称为自旋.
乐观锁的核心是CAS(即内存值\预期值\新值),只有当内存值等于预期值时,才会将内存值修改为新值.
可能会产生自旋次数过多的问题,有时候反而不如直接加锁的效率高.
对比一下Synchronized和ReentrantLock的异同
- 相似点
它们都是阻塞式的同步,也就是说一个线程获得了对象锁进入代码块,其它访问该同步块的线程都必须阻塞在同步代码外面等待,而进行线程阻塞和唤醒的代码是比较高的 - 功能区别
- Synchronized是java语言的关键字,是原生语法层面的互斥,需要JVM实现;ReentrantLock 是JDK1.5之后提供的API层面的互斥锁,需要lock和unlock()方法配合try/finally代码块来完成。
- Synchronized使用比ReentrantLock 便利一些;
- ReentrantLock锁的细粒度和灵活性强于Synchronized;
- 性能区别
Synchronized引入偏向锁,自旋锁之后,两者的性能差不多了,在这种情况下,官方建议使用Synchronized。
ReentrantLock多提供了一些高级功能:
- 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,通过lock.lockInterruptibly()来实现这一机制
- ReentrantLock可以变为一种公平锁,多个线程等待同一个锁的时候, 必须按照申请锁的时间顺序来获得锁(ReentrantLock默认也是非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好; )
- 锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象(使用Condition类,用来实现分组唤醒需要唤醒的线程,而不用像Synchronized要么随机唤醒一个线程,要么唤醒全部线程)
Java8开始ConcurrentHashMap,为什么舍弃分段锁?
在Java8之前,concurrentHashMap的原理是引用了内部的Segment分段锁,保证在操作不同段map的时候,可以并发执行,操作同段map的时候,进行锁的竞争和等待.从而达到线程安全,且效率大于synchronized
在java8之后,JDK弃用了这个策略,重新使用了synchronized+CAS
- 弃用原因:
- 在生产环境中,map在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
- 为了提高GC的效率
为什么是synchronized,而不是ReentranLock
- 减少内存开销
使用可重入锁来同步,每个节点都需要通过继承AQS来同步;但不是每个节点都需要获得同步支持,只有链表的头节点需要同步,这会带来巨大是内存浪费 - 获得JVM的支持
可重入锁是API级别的,后续的性能优化空间很小;
synchronized是JVM直接支持的
Java数据库基础问题
JDBC
- JDBC指Java数据库连接,是一种标准Java应用编程接口API,用来连接Java编程语言和数据库。
- JDBC提供了Statement、PreparedStatement和CallableStatement三种方式来执行查询语句,其中Statement用于通用查询,PreparedStatement用于执行参数化查询,而CallableStatement用于存储过程
- 对于PreparedStatement来说,数据库可以使用已经编译过及定义好的执行计划,由于PreparedStatement对象已预编译过,所以其执行速度要快于Statement对象
- PreparedStatement可以阻止常见的SQL注入式攻击
spring的事务传播特性
事务属性的种类:传播行为、隔离级别、只读和事务超时
1)传播行为定义了被调用方法的事务边界
基本上根据英文翻译就能知道作用:
- Required:必须的。说明必须要有事务,没有就新建事务。
- supports:支持。说明仅仅是支持事务,没有事务就非事务方式执行。
- mandatory:强制的。说明一定要有事务,没有事务就抛出异常。
- required_new:必须新建事务。如果当前存在事务就挂起。
- not_supported:不支持事务,如果存在事务就挂起。
- never:绝不有事务。如果存在事务就抛出异常
2)隔离级别
在标准的SQL语句中定义了4种隔离级别,分别是未提交读、已提交读、可重复读、可序列化
- ISOLATION_DEFAULT:使用后端数据库默认的隔离级别
- ISOLATION_READ_UNCOMMITTED:允许读取未提交的数据
- ISOLATION_READ_COMMITTED:允许在一个事务种读取另一个已经提交的事务中的数据
- ISOLATION_REPEATABLE_READ:可重复读;一个事务不能更新由另一个事务修改但尚未提交的数据
- ISOLATION_SERIALZABLE:依次顺序执行
3)只读
如果在一个事务中所有关于数据库的操作都是只读的,也就是说,这些操作只读取数据库中的数据,而并不更新数据,那么应将事务设为只读模式( READ_ONLY_MARKER ) , 这样更有利于数据库进行优化 。
4)事务超时
如果一个事务长时间运行,这时为了尽量避免浪费系统资源,应为这个事务设置一个有效时间,使其等待数秒后自动回滚。与设置“只读”属性一样,事务有效属性也需要给那些具有可能启动新事物的传播行为的方法的事务标记成只读才有意义。
JavaWeb基础问题
Servlet的生命周期
Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
- 加载:容器通过类加载器使用servlet类对应的文件加载servlet
- 创建:通过调用servlet构造函数创建一个servlet对象
- 初始化:调用init方法初始化
- 处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求(执行service方法,根据用户请求的方法,执行相应的doGet或是doPost方法)
- 卸载:调用destroy方法让servlet自己释放其占用的资源(仅执行一次,即在服务器停止且卸载servlet时执行该方法)
Servlet与CGI的比较
和CGI程序一样,Servlet可以响应用户的指令(提交一个FORM等等),也可以象CGI程序一样,收集用户表单的信息并给予动态反馈(简单的注册信息录入和检查错误)。
然而,Servlet的机制并不仅仅是这样简单的与用户表单进行交互。传统技术中,动态的网页建立和显示都是通过CGI来实现的,但是,有了Servlet,您可以大胆的放弃所有CGI(perl?php?甚至asp!),利用Servlet代替CGI,进行程序编写。
- 对比一:当用户浏览器发出一个Http/CGI的请求,或者说 调用一个CGI程序的时候,服务器端就要新启用一个进程 (而且是每次都要调用),调用CGI程序越多(特别是访问量高的时候),就要消耗系统越多的处理时间,只剩下越来越少的系统资源,对于用户来说,只能是漫长的等待服务器端的返回页面了,这对于电子商务激烈发展的今天来说,不能不说是一种技术上的遗憾。
而Servlet充分发挥了服务器端的资源并高效的利用。每次调用Servlet时并不是新启用一个进程 ,而是在一个Web服务器的进程共享和分离线程,而线程最大的好处在于可以共享一个数据源,使系统资源被有效利用。 - 对比二:传统的CGI程序,不具备平台无关性特征,系统环境发生变化,CGI程序就要瘫痪,而Servlet具备Java的平台无关性,在系统开发过程中保持了系统的可扩展性、高效性。
- 对比三:传统技术中,一般大都为二层的系统架构,即Web服务器+数据库服务器,导致网站访问量大的时候,无法克服CGI程序与数据库建立连接时速度慢的瓶颈,从而死机、数据库死锁现象频繁发生。而我们的Servlet有连接池的概念,它可以利用多线程的优点,在系统缓存中事先建立好若干与数据库的连接,到时候若想和数据库打交道可以随时跟系统"要"一个连接即可,反应速度可想而知。
Struts1 框架和Struts2框架
JavaWeb学习——struts1框架篇
Struts1和Struts2的区别和对比(完整版)
- Struts1有个核心控制器,只提供一个接口(execute),依赖性比较强
- Struts2是针对拦截器开发的(AOP思想),可以配置多个action;因为请求之前的拦截器有一些注入的操作,速度相对Struts来说慢一点
Struts1与Struts2的对比
- Action类的实现方式不同:Struts1的Action在实现的时候必须扩展Action或者Action的子类,Structs2的Action类实现的时候可以不用实现任何类和接口
- Struts1的Action类是单例模式,必须设计成线程安全的,Struts2则为每个请求产生一个实例
Struts2与WebWork对比
Struts实际上就是WebWork2.3,不过有少许差别
- Struts2不再支持内置IOC容器,改用Spring的IOC容器
- Struts2对于WebWork的一些Ajax的特性的标签改用Dojo进行替换
正在学习:https://www.bilibili.com/video/BV1Eb4y1R7zd?p=10
这种八股文还得背,就当复习Java的编程特性了