Java基础

1、Java基础

JVM虚拟机

虚拟机的结构、类的加载机制、双亲委派机制

虚拟机由方法区、虚拟机栈、本地方法栈、堆、程序计数器、运行时常量池、直接内存构成。

Java 虚拟机栈:线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

本地方法栈:区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。
程序计数器:内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。

如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。
重点:此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。 
Java 堆:对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。

方法区:属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
运行时常量池:属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。
直接内存:非虚拟机运行时数据区的部分

图解

java类的加载过程:分为 5 个阶段:载入、验证、准备、解析和初始化。

Java 类加载器可以分为三种。

1)启动类加载器(Bootstrap Class-Loader),加载 jre/lib 包下面的 jar 文件,比如说常见的 rt.jar。

2)扩展类加载器(Extension or Ext Class-Loader),加载 jre/lib/ext 包下面的 jar 文件。

3)应用类加载器(Application or App Clas-Loader),根据程序的类路径(classpath)来加载 Java 类。

双亲委派机制:程序员可以自定义类加载器(继承 java.lang.ClassLoader 类),如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。

垃圾回收机制、垃圾回收算法

引言:垃圾回收机制的引入可以有效的防止内存泄露、保证内存的有效使用,也减轻了 Java 程序员的对内存管理的工作量。

垃圾检测算法:

1.引用计数法(Reference Counting Collector

引用计数是垃圾收集器中的早期策略。此方法中,堆中的每个对象都会添加一个引用计数器。每当一个地方引用这个对象时,计数器值 +1;当引用失效时,计数器值 -1。任何时刻计数值为 0 的对象就是不可能再被使用的。

缺点:无法解决对象之间相互引用的关系

2.可达性分析算法:

可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

  • 虚拟机栈中引用的对象(本地变量表)
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中引用的对象(Native对象)

垃圾收集算法:

1.标记-清除(Mark-Sweep)算法 

  • 标记阶段:标记出需要被回收的对象。
  • 清除阶段:回收被标记的可回收对象的内部空间。

优缺点:标记-清除算法实现较容易,不需要移动对象,但是存在较严重的问题,会产生大量内存碎片。

2.复制(Copying)算法

为了解决标志-清除算法的缺陷,由此有了复制算法。

复制算法将可用内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。

优缺点:实现简单,不易产生内存碎片,每次只需要对半个区进行内存回收。但内存空间缩减为原来的一半;算法的效率和存活对象的数目有关,存活对象越多,效率越低。
3.标记-整理(Mark-Compact)算法
此算法结合了“标记-清除”和“复制”两个算法的优点。
该算法标记阶段和“标志-清除”算法一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

4. 分代收集(Generational Collection)算法

分代收集算法是目前大部分 JVM 的垃圾收集器采用的算法。
核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代Tenured Generation)和新生代Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

GC 类型:

Minor GC(新生代 GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生熄灭的特点,所以 Minor GC 十分频繁,回收速度也较快。

Major GC(老年代 GC):指发生在老年代的垃圾收集动作,当出现 Major GC 时,一般也会伴有至少一次的 Minor GC(并非绝对,例如 Parallel Scavenge 收集器会单独直接触发 Major GC 的机制)。 Major GC 的速度一般会比 Minor GC 慢十倍以上。

Full GC:清理整个堆空间—包括年轻代和老年代

产生Full GC的条件:
年老代被写满。
持久代被写满。
System.gc() 被显示调用。
上一次 GC 之后 Heap 的各域分配策略动态变化。

Java的基础语法、流程控制语句 (掌握)

if(条件表达式){ 语句块; } 
if(条件表达式1){ 语句块1; }elseif(条件表达式2){ 语句块2; }else{ 语句块3; }  
while(条件表达式) {    语句块;  }
do {    语句块;  }while(条件表达式);    //至少循环一次
switch(表达式){
    case 值1: 语句1;
    break;
    case 值2: 语句2;
    break;
    default:  语句3;
    break;
}

基本数据类型以及包装类之间的转换

char-character  byte-Byte  short-Short   int-Integer   long-Long   boolean-Boolean   float-Float  double-Double

 

对象创建过程中JVM中堆与栈中分别存储了什么?

1.加载class文件到class内容区域,加载静态方法和静态变量到静态区(同时加载的)
2.调用main方法到栈内存
3.在栈内存中为a变量(A对象的引用)开辟空间
4.在堆内存为A对象申请空间
5.给成员变量进行默认初始化(此时 i=0),同时有一个方法标记,在方法区中创建一个A的方法区,将A的方法区的地址0x01给方法标记
6.给成员变量进行显示初始化(此时 i=1)
7.将A对象的地址值给变量a

栈中保存了A对象的引用(即变量A),堆中保存了A对象。

面向对象(掌握)

什么是面向对象?特性?与面向过程的好处与区别?

面向对象就是一种思想,任何事物都可以看作是一个对象

面向对象,优势 特征:
1.代码开发模块化,便于维护。
2.代码复用性强
3.代码的可靠性和灵活性。
4.代码的可读性和可扩展性。
1.面向过程的基本概念
- 面向过程(Procedure Oriented)是一种以过程为中心的编程思想。
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。是一种思考问题的基础方法。
- 简单理解:面向过程就是任何事情都亲力亲为,很机械,像个步兵。

2.面向对象的基本概念
- 面向对象(Object Oriented)是软件开发方法中的一种;是一种对现实世界理解和抽象的方法;是思考问题相对高级的方法。
- 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
- 简单理解:面向对象就像战场上的指挥官,指挥战斗而不必知道具体执行的过程。

区别:
1.面向对象是相对面向过程而言的,面向对象包含了面向过程的思想。
2.面向过程就是分析出解决问题所需要的步骤,关注的是解决问题需要那些步骤。
3.面向对象是把构成问题事务分解成各个对象,关注的是解决问题需要那些对象。

总结:面向对象更符合人们思考习惯

接口以及抽象(掌握)

接口与抽象的特点以及区别?

抽象类:在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。

抽象类特点:
a、抽象类不能被实例化只能被继承;
b、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;
c、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;
d、一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;
e、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。

接口特点:
a、接口可以包含变量、方法;变量被隐式指定为public static final,方法被隐式指定为public abstract(JDK1.8之前);1.8之后可以存在static与default修饰的带方法体的方法。
b、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;
c、一个类可以实现多个接口;
d、JDK1.8中对接口增加了新的特性:
(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;
(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。

相同点:
(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点:
(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

什么是面向接口编程?什么是面向抽象编程?有什么好处?

面向抽象:抽象类的设计就是只关心操作,不关心具体实现,这样可以使得程序设计者在将主要精力放在程序设计上,而不必拘泥于细节的实现。
面向接口:接口中只有抽象方法,不像抽象类中可以有普通方法。接口只关心操作,而不关心具体实现细节可以把主要精力放在程序设计上,内容细节由实现接口的类去完成。
总结:面向抽象编程与面向接口编程都是将主要精力放在程序设计,而先不管具体实现细节。

集合框架(数据结构) (重点掌握)

1、Iteraor接口
(迭代器接口)用于遍历集合中元素的接口,主要包含三种方法:
boolean hasNext()    //判断是否存在下一个元素
E next()             //返回下一个元素
void remove()        //移除当前元素
子类ListIterator新增方法:
void add()        
E previous()        //访问前一个元素
boolean hasPrevious()
扩展 Iterator和Iterable的区别:
1). Iterator是迭代器接口,而Iterable是为了只要实现该接口就可以使用foreach进行迭代。
2). Iterable中封装了Iterator接口,只要实现了Iterable接口的类,就可以使用Iterator迭代器了。
3). 集合Collection、List、Set都是Iterable的实现类,所以他们及其他们的子类都可以使用foreach进行迭代。
4). Iterator中核心的方法next()、hasnext()、remove()都是依赖当前位置,如果这些集合直接实现Iterator,则必须包括当前迭代位置的指针。当集合在方法间进行传递的时候,由于当前位置不可知,所以next()之后的值,也不可知。而实现Iterable则不然,每次调用都返回一个从头开始的迭代器,各个迭代器之间互不影响。

2、Collection (集合的最大接口)继承关系;
  (集合中存储的都是对象的引用(地址))
(1)List:有序,可以存放重复的内容
    子类:  ——ArrayList:线程不安全,查询速度快。底层都是基于数组来储存集合元素,封装了一个动态的Object[]数组,是一种顺序存储的线性表。
            ——Vector:线程安全,但速度慢,已被ArrayList替代。
            ——LinkedList:线程不安全,增删速度快,没有同步方法,是一个链式存储的线性变,本质上是一个双向链表。
            ——Stack: (已经不用,可由LinkedList代替)。
(2)Set:无序,不能存放重复的内容,所以的重复内容靠hashCode()和equals()两个方法区分,与List不同的是,在Set中的对象元素不能重复,也就是说你不能把同样的东西两次放入同一个Set容器中。它的常用具体实现有HashSet和TreeSet类。
            HashSet能快速定位一个元素,但是你放到HashSet中的对象需要实现hashCode()方法,它使用了前面说过的哈希码的算法。
            TreeSet则将放入其中的元素按序存放,这就要求你放入其中的对象是可排序的,这就用到了集合框架提供的另外两个实用接口Comparable和Comparator。(一个类是可排序的,它就应该实现Comparable接口)
     子类: ——HashSet:底层数据结构由HashMap的键来实现。不保证集合中元素的顺序,即不能保证迭代的顺序与插入的顺序一致。是线程不安全的。
            ——TreeSet:有序的存放,线程不安全,可以对Set集合中的元素进行排序,由红黑树来实现排序,TreeSet实际上也是SortedSet接口的子类,其在方法中实现了SortedSet的所有方法,并使用comparator()方法进行排序。
            ——LinkedHashSet:底层由链表实现,按照元素插入的顺序进行迭代,即迭代输出的顺序与插入的顺序保持一致
(3)Queue:队列接口
(4)SortedSet:可以对集合中的数据进行排序

3、Map Map是一种把键对象和值对象进行关联的容器
        实现Map接口的子类:
            ——Hashtable:Hashtable继承Dictionary<K,V>类,实现了Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。是同步的。
            ——HashMap:HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
                JDK1.7和1.8的主要区别在于头插和尾插方式的修改,头插容易导致HashMap链表死循环,并且1.8之后加入红黑树对性能有提升
            ——LinkedHashMap:是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现。Key和Value都允许空;Key重复会覆盖、Value允许重复;非线程安全;有序。
            ——TreeMap:底层是二叉树数据结构。线程不同步。可以用于给map集合中的键进行排序。

多线程环境怎么使用Map呢?ConcurrentHashmap了解过吗?

多线程环境可以使用Collections.synchronizedMap同步加锁的方式,还可以使用HashTable,但是同步的方式显然性能不达标,而ConurrentHashMap更适合高并发场景使用。
ConcurrentHashmap在JDK1.7和1.8的版本改动比较大,1.7使用Segment+HashEntry分段锁的方式实现,1.8则抛弃了Segment,改为使用CAS+synchronized+Node实现,同样也加入了红黑树,避免链表过长导致性能的问题。

1.7分段锁
从结构上说,1.7版本的ConcurrentHashMap采用分段锁机制,里面包含一个Segment数组,Segment继承与ReentrantLock,Segment则包含HashEntry的数组,HashEntry本身就是一个链表的结构,具有保存key、value的能力能指向下一个节点的指针。
实际上就是相当于每个Segment都是一个HashMap,默认的Segment长度是16,也就是支持16个线程的并发写,Segment之间相互不会受到影响。

 

put流程
其实发现整个流程和HashMap非常类似,只不过是先定位到具体的Segment,然后通过ReentrantLock去操作而已,后面的流程我就简化了,因为和HashMap基本上是一样的。
计算hash,定位到segment,segment如果是空就先初始化
使用ReentrantLock加锁,如果获取锁失败则尝试自旋,自旋超过次数就阻塞获取,保证一定获取锁成功
遍历HashEntry,就是和HashMap一样,数组中key和hash一样就直接替换,不存在就再插入链表,链表同样

get流程
get也很简单,key通过hash定位到segment,再遍历链表定位到具体的元素上,需要注意的是value是volatile的,所以get是不需要加锁的。

 

1.8CAS+synchronized
1.8抛弃分段锁,转为用CAS+synchronized来实现,同样HashEntry改为Node,也加入了红黑树的实现。主要还是看put的流程。

 

put流程
首先计算hash,遍历node数组,如果node是空的话,就通过CAS+自旋的方式初始化
如果当前数组位置是空则直接通过CAS自旋写入数据
如果hash==MOVED,说明需要扩容,执行扩容
如果都不满足,就使用synchronized写入数据,写入数据同样判断链表、红黑树,链表写入和HashMap的方式一样,key hash一样就覆盖,反之就尾插法,链表长度超过8就转换成红黑树

get查询
get很简单,通过key计算hash,如果key hash相同就返回,如果是红黑树按照红黑树获取,都不是就遍历链表获取。

 

Collections和Collection有什么区别?

Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。
它有两个常用的子接口:
——List:对元素都有定义索引。有序的。可以重复元素。
——Set:不可以重复元素。无序。

Collections是集合框架中的一个工具类。该类中的方法都是静态的。提供的方法中有可以对list集合进行排序,二分查找等方法。通常常用的集合都是线程不安全的。因为要提高效率。如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。

了解集合体系、底层结构,哪些是线性安全?哪些是线性不安全?对象线性不安全时在多线程并发时要注意哪些问题?

如何避免线性不安全带来的数据安全问题?

1、线程安全的集合对象
Vector     HashTable    StringBuffer
2、非线程安全的集合对象
ArrayList LinkedList    HashMap    HashSet    TreeMap    TreeSet    StringBulider

常见集合比较:(想要安全必然会牺牲效率)
Vector、ArrayList、LinkedList
Vector和ArrayList在使用上非常相似,都可以用来表示一组数量可变的对象应用的集合,并且可以随机的访问其中的元素。
   a)Vector:Vector与ArrayList一样,是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
   b)ArrayList:
       1、当操作是在一列数据的后面添加数据而不是在前面或者中间,并需要随机地访问其中的元素时,使用ArrayList性能比较好。
       2、ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
   c)LinkedList
       1、当对一列数据的前面或者中间执行添加或者删除操作时,并且按照顺序访问其中的元素时,要使用LinkedList。 
       2、LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

HashTable、HashMap、HashSet
   HashTable和HashMap采用的存储机制是一样的,不同的是
   a)HashMap:(jdk1.7存储数据采用头插法,数组+链表结构;jdk1.8采用尾插法,数组+链表+红黑树结构)
       1、采用数组方式存储key-value构成的Entry对象,无容量限制;
       2、基于key hash查找Entry对象存放到数组的位置,对于hash冲突采用链表的方式去解决; 
       3、在插入元素时,可能会扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数中;
       4、是非线程安全的;
       5、遍历使用的是Iterator迭代器;
   b)HashTable:
       1、是线程安全的;
       2、无论是key还是value都不允许有null值的存在;在HashTable中调用Put方法时,如果key为null,直接抛出NullPointerException异常; 
       3、遍历使用的是Enumeration列举;
   c)HashSet:
       1、基于HashMap实现,无容量限制; 
       2、是非线程安全的; 
       3、不保证数据的有序;

TreeSet、TreeMap
   TreeSet和TreeMap都是完全基于Map来实现的,并且都不支持get(index)来获取指定位置的元素,需要遍历来获取。另外,TreeSet还提供了一些排序方面的支持,例如传入Comparator实现、descendingSet以及descendingIterator等。
   a)TreeSet:
       1、基于TreeMap实现的,支持排序;
       2、是非线程安全的;
       3、TreeSet中不可以添加两种不同类型的元素 (重)
   b)TreeMap:
​​​​​​​       1、典型的基于红黑树的Map实现,因此它要求一定要有key比较的方法,要么传入Comparator比较器实现,要么key对象实现Comparator接口;
       2、是非线程安全的;

StringBuffer和StringBulider
​​​​​​​StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串。
       1、在执行速度方面的比较:StringBuilder > StringBuffer ; 
       2、他们都是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度快; 
       3、StringBuilder:线程非安全的;(jdk1.5出现,为提升性能)
       4、StringBuffer:线程安全的;

异常

error与运行时异常的区别?

异常的处理机制、自定义异常、常见的异常有哪些?

Throwable在异常类的层次结构的顶层
1.检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
2.运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
3.错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

Error:Error类对象由 Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
例如,Java虚拟机运行错误(VirtualMachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。

Exception:在Exception分支中有一个重要的子类RuntimeException(运行时异常)。
常见运行时异常:ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
非运行时异常:类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

如何自定义异常:
1、所有的异常必须是Throwable的子类。
2、如果想写一个检查异常,需要继承Exception类。
3、如果想编写一个运行时异常,则需要继承RuntimeException类。
4、异常类与任何其他类一样,可以包含字段和方法。
抛出异常:    throw new InsufficientFundsException(异常信息);

IO流、异步IO、NIO、BIO(掌握)

从系统吞吐效率考虑,异步IO与阻塞IO的区别,以及如何实现

1.同步
用户进程触发IO操作并等待或者轮询的去查看IO操作是否完成
2.异步
用户触发IO操作以后,可以干别的事,IO操作完成以后再通知当前线程继续处理
3.阻塞
当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务
4.非阻塞
当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。

同步阻塞IO(BIO)
我们熟知的Socket就是BIO,每一个socket套接字需要使用一个线程来处理。建立连接、进行读写操作的时候都可能阻塞。在服务器端如果要支持并发的连接时,需要更多的线程。连接不做任何事情的时候会造成不必要的线程开销,可通过线程池来改善。

同步非阻塞IO(NIO)
New IO是对BIO的改进,基于Reactor模型。我们知道,一个socket连接只有在特定时间才会发生数据传输IO操作,大部分时间这个“数据通道”是空闲的,但还是占用着线程。NIO作出的改进就是“多个连接一个线程”,在连接到服务端的众多socket中,只有需要进行IO操作的才能获取服务端的处理线程进行IO。这样就不会因为线程不够用而限制了socket的接入。客户端的socket连接到服务端时,就会在事件分离器注册一个 IO请求事件 和 IO 事件处理器。在该连接发生IO请求时,IO事件处理器就会启动一个线程来处理这个IO请求,不断尝试获取系统的IO的使用权限,则通知这个socket进行IO数据传输。
NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

异步阻塞IO(AIO)
AIO是发出IO请求后,由操作系统自己去获取IO权限并进行IO操作;NIO则是发出IO请求后,由线程不断尝试获取IO权限,获取到后通知应用程序自己进行IO操作。

BIO,NIO,AIO可以简述如下:
BIO是同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO是同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO是异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高
NIO方式适用于连接数目多且连接比较短的架构,可充分利用服务器资源
AIO方式使用于连接数目多且连接比较长的架构,充分调用OS参与并发操作

熟悉各种输入输出流的用法

字节输入流:
InputStream  FileInputStream   BufferedInputStream(用到装饰者模式)
字节输出流:
OutputStream  FileOutputStream   BufferedOutputStream
字符输入流:
reader      FileReader
字符输出流:
writer      FileWriter

多线程以及JUC(重点掌握)

多线程
线程和进程概念
1.程序:是一段静态的代码。
2.进程:是程序的一次执行过程,或者是正在运行的一个程序。
3.线程:是进程内部的一条具体的执行路径。若一个程序可同一时间执行多个线程,就是支持多线程的。

使用线程操作的好处:
1.将耗时间的任务放到后台去执行,例如载入、加载,实际上后台在疯狂的读数据。
2.提高计算机系统CPU的利用率。

线程安全及解决方式
多个线程同时共享同一个资源时,可能会出现数据冲突。
解决方式是加锁…
lock(手动锁)或者synchronized(自动锁)
==>线程同步,多个线程共享同一个资源环境下,每个线程工作时不会受到其他线程的干扰称为线程的同步。

JUC
JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的
提供了可调,灵活的线程池。
链接:https://www.jianshu.com/p/1f19835e05c0

实现多线程的三种方法?掌握消费者与生产者问题

线程的创建方式
3.1、继承Thread类
3.2、实现Runnable接口
    —隐匿内部类实现接口,可以使用lambda表达式。
3.3、使用Callable接口
    需要借助FutrueTask类。
3.4、使用线程池
创建线程池的两种方式:
1.直接通过ThreadPoolExecutor实现类,
2.通过工厂类Executors中的方法创建,工厂创建,底层也是通过1。

锁机制、各种锁的基本概念、自旋锁、原子引用,Java的内存模型,内存可见性等等

乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
悲观锁
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。java 中的悲观锁就是 Synchronized,AQS 框架下的锁则是先尝试 cas 乐观锁去获取锁,获取不到,才会转换为悲观锁,如ReentrantLock。
自旋锁
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。
    自旋锁的优缺点
    自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换! 
    但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cup 的线程又不能获取到 cpu,造成 cpu 的浪费。所以这种情况下我们要关闭自旋锁;
Synchronized 同步锁
synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。
Synchronized 作用范围
    1.作用于方法时,锁住的是对象的实例(this);
    2.当作用于静态方法时,锁住的是 Class 实例,又因为 Class 的相关数据存储在永久带 PermGen(jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
    3.synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
ReentantLock 
ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法
ReadWriteLock 读写锁
为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由 jvm 自己控制的,你只要上好相应的锁即可。
读锁
如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁
写锁
如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
锁粗化
原则上为了提高运行效率,锁的范围应该尽量小,减少同步的代码,但是这不是绝对的原则,试想有一个循环,循环里面是一些敏感操作,有的人就在循环里面写上了synchronized关键字。这样确实没错不过效率也许会很低,因为其频繁地拿锁释放锁。要知道锁的取得(假如只考虑重量级MutexLock)是需要操作系统调用的,从用户态进入内核态,开销很大。于是针对这种情况也许虚拟机发现了之后会适当扩大加锁的范围(所以叫锁粗化)以避免频繁的拿锁释放锁的过程。
锁消除
通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),或者同步块内进行的是原子操作,而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。
可重入锁(递归锁)
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在 JAVA 环境下 ReentrantLock 和 synchronized 都是 可重入锁。
公平锁与非公平锁
公平锁(Fair)
    加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁(Nonfair)
    加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
    非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列 
    Java 中的 synchronized 是非公平锁,ReentrantLock 默认的 lock()方法采用的是非公平锁。
共享锁和独占锁
独占锁
    独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。
共享锁 
    共享锁则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。
偏向锁
在大多数的情况下,锁不仅不存在多线程的竞争,而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁的概念。偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁,那么持有偏向锁的线程再次进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作。偏向锁可以通过 -XX:+UseBiasedLocking开启或者关闭
偏向锁的获取
偏向锁的获取过程非常简单,当一个线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,表示哪个线程获得了偏向锁,结合前面分析的Mark Word来分析一下偏向锁的获取逻辑
    1 首先获取目标对象的Mark Word,根据锁的标识为和epoch去判断当前是否处于可偏向的状态
    2 如果为可偏向状态,则通过CAS操作将自己的线程ID写入到MarkWord,如果CAS操作成功,则表示当前线程成功获取到偏向锁,继续执行同步代码块
    3 如果是已偏向状态,先检测MarkWord中存储的threadID和当前访问的线程的threadID是否相等,如果相等,表示当前线程已经获得了偏向锁,则不需要再获得锁直接执行同步代码;如果不相等,则证明当前锁偏向于其他线程,需要撤销偏向锁。
偏向锁的撤销
当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放偏向锁,撤销偏向锁的过程需要等待一个全局安全点(所有工作线程都停止字节码的执行)。
    1 首先,暂停拥有偏向锁的线程,然后检查偏向锁的线程是否为存活状态
    2 如果线程已经死了,直接把对象头设置为无锁状态
    3 如果还活着,当达到全局安全点时获得偏向锁的线程会被挂起,接着偏向锁升级为轻量级锁,然后唤醒被阻塞在全局安全点的线程继续往下执行同步代码


 

原子引用

AtomicReference与volatile 的区别
AtomicReference是作用是对”对象”进行原子操作。 
volatile是java中关键字用于修饰变量,AtomicReference是并发包java.util.concurrent.atomic下的类。
首先volatile作用,当一个变量被定义为volatile之后,看做“程度较轻的 synchronized”,具备两个特性:
1.保证此变量对所有线程的可见性(当一条线程修改这个变量值时,新值其他线程立即得知)
2.禁止指令重新排序
注意volatile修饰变量不能保证在并发条件下是线程安全的,因为java里面的运算并非原子操作。
java.util.concurrent.atomic工具包,支持在单个变量上解除锁的线程安全编程。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。

java内存模型

Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
总结:JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。

JUC包下的常用线性安全的类

JUC中常用类汇总
JUC的atomic包下运用了CAS的AtomicBoolean、AtomicInteger、AtomicReference等原子变量类
JUC的locks包下的AbstractQueuedSynchronizer(AQS)以及使用AQS的ReentantLock(显式锁)、ReentrantReadWriteLock
        附:运用了AQS的类还有:Semaphore、CountDownLatch、ReentantLock(显式锁)、ReentrantReadWriteLock
JUC下的一些同步工具类:CountDownLatch(闭锁)、Semaphore(信号量)、CyclicBarrier(栅栏)、FutureTask
JUC下的一些并发容器类:ConcurrentHashMap、CopyOnWriteArrayList
JUC下的一些Executor框架的相关类: 线程池的工厂类->Executors  线程池的实现类->ThreadPoolExecutor/ForkJoinPool
JUC下的一些阻塞队列实现类:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue

反射(掌握)

什么是反射?反射的基本用法?(几乎所有的框架底层都使用了反射,进阶分析源码必备)

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
创建反射对象:
1.Class c1 = 类名.class;
2.Class c2 = 类名.getClass();
3.Class c3 = Class.forName(类全路径);
常用方法:
c3.newInstance()构建对象    invoke()调用方法  getPackage()获取此类所属的包  getSuperclass() 获取父类对应的Class对象
getFiled(String name) 获得类的指定成员变量   getMethods() 获得类的public类型的方法     getMethod(String name, Class[] args) 获取类的指定方法

暴力反射过程:
        //1 创建实例
		Class Userclass = Class.forName("com.zxf.User");
		Object obj = Userclass.newInstance();
		//2 调用方法 --getDeclaredMethod() 获得声明的私有方法
		Method method = Userclass.getDeclaredMethod("info");
		//2 获得私有构造方法
		Constructor cons = Userclass.getDeclaredConstructor(int.class , String.class);
		//设置可访问权限
		method.setAccessible(true);
		method.invoke(obj);

常用类以及工具类的使用(面试题可能会手写代码)(掌握)

String类、StringBuffer、StringBuilder、Date等等

String类的常用方法
1.获取:
        1)获取字符串str长度
                int i = str.length();
        2)根据位置(index)获取字符
                char  c = str.charAt(index);
        3)获取字符在字符串中的位置
                int i =str.indexOf(char ch);  //获取的是第一次出现的位置
                int i =str.indexOf(char ch ,int index);  //从位置index后获取ch出现的第一次的位置
                int  i =str.indexOf(str1) ;// 获取str1 在str 第一次出现的位置
                int i=str.indexOf(str1, index0);//获取从index位置后str第一次出现的位置
                int i = str.lastIndexOf(ch或者 str1)  //获取ch或者str1最后出现的位置
2.判断
        1)判断是否以指定字符串str1开头、结尾
                boolean b = str.startWith(str1)  //开头
                boolean b = str.endsWith(str1) //结尾
        2)判断是否包含某一子串
                boolean b = str.contains(str1)
        3)判断字符串是否有内容
                boolean b = str.isEmpty();
        4)忽略大小写判断字符串是否相同
                boolean b = str.equalsIgnoreCase(str1);
3.转换
        1)将字符数组 -char[] ch- 转化成字符串
            i.  String str =new String(ch); //将整个数组变成字符串
            ii. String str =new String(ch,offset,count)
    //将字符数组中的offset位置之后的count个元素转换成字符串  
            1. String str =String.valueOf(ch);
            2. String str =String.copyValueOf(ch,offset,count);
            3. String str =String.copyValueOf(ch);
        2)将字符串转化为字符数组
            char[] ch = str.toCharAarray();
        3)将字节数组转换为字符串
            同上1) 传入类型变为Byte[];
        4)将字符串转换为字节数组
            Byte[] b = str.toByteArray();
        5)将基本数据类型装换成字符串
            String str = String.valueOf(基本数据类型数据);
            若是整形数据可以用 字符串连接符 + "" 
            eg :  String  str = 5+"";
            得到字符串 “5”   
4.替换   replace();
        str.replace(oldchar,newchar)//将str里oldchar变为newchar
        str.replace(str1,str2)//将str中str1,变为str2
5.切割   split();
        String[]  str1 = str.split(","); //将str用 ","分割成String数组
6.子串
        String s = str.substring(begin);
        // s 为 str 从begin位置到最后的字符串
        String s = str.substring(begin,end)
        //s 是 str 从begin 位置到end 位置的字符串
7.转换大小写:
        String s1 = str. toUpperCase(); //将str变成大写字母
        String s2 = str. toLowerCase(); //将str变成小写字母
    除去空格:
        String s =str.trim();
    比较:
        int i = str.compareTo(str1);

StringBuffer常用方法
    /***StringBuffer        是一个容器,长度可变,可以直接操作字符串,用toString方法变为字符串 **/
1.存储
        1)append(); //将指定数据加在容器末尾,返回值也是StringBuffer
        eg:
        StringBuffer sb = new StringBuffer(//可以加str);
        StringBuffer sb1=ab.append(数据) //数据可以任何基本数据类型
    注:此时sb == sb1他们是同一对象,意思是可以不用新建sb1直接 sb.append(数据) 使用时之后接使用sb
        2)insert();// 插入
    sb.insert(index ,数据);
2.删除
        sb.delete(start ,end);  //删除start到end的字符内容
//注意:这里的所有包含index的操作都是含头不含尾的
        sb.deleteCharAt(index);//删除指定位置的字符
//清空StringBuffer缓冲区
        sb=new StringBuffer();
        sb.delete(0,sb.length());
3.获取
    char c = sb.charAt(index);//获取index上的字符
    int i = sb.indexOf(char)://获取char字符出现的第一次位置
    //与 String 中的获取方法一致参考前面
4.修改                  String类中无次操作方法
    sb =sb.replace(start,end,string)//将从start开始到end的字符串替换为string;
    sb.setCharAr(index ,char);//将index位置的字符变为新的char
5.反转     sb.reverse();//将sb倒序
6. getChars(int srcBegin,int srcEnd,char[] ch,int chBegin)
//将StringBuffer缓冲区中的指定数据存储到指定数组中

StringBuilder方法和StringBuffer一样

基础的笔试:(掌握)

冒泡排序、选择排序、二分查找、二叉树、链表的实现、利用集合类的特性去重、排序等等

2、Java Web
该部分会考察网络有关的支持Http基于Tcp\ip协议

TCP为什么三次握手,要四次挥手?

TCP建立连接要进行3次握手,而断开连接要进行4次,这是由于TCP的半关闭造成的,因为TCP连接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭,这个单方向的关闭就叫半关闭.
关闭的方法是一方完成它的数据传输后,就发送一个FIN来向另一方通告将要终止这个方向的连接.当一端收到一个FIN,它必须通知应用层TCP连接已终止了这个方向的数据传送,发送FIN通常是应用层进行关闭的结果.

为什么说TCP是可靠传输?

面向连接 是因为TCP使用传输确认机制,就是说把数据发送到目的方后目的主机还要反馈一个信息告诉源主机信息已接收!源主机发,目的主机确认,所以叫面向连接。使用了面向连接,提高了网络传输的可靠性。这里的可靠性是相对于UDP的,因为UDP不使用确认机制!
TCP相比UDP为什么是可靠的
[1] 确认和重传机制
    建立连接时三次握手同步双方的“序列号 + 确认号 + 窗口大小信息”,是确认重传、流控的基础传输过程中,如果Checksum校验失败、丢包或延时,发送端重传
[2] 数据排序
    TCP有专门的序列号SN字段,可提供数据re-order
[3] 流量控制
    窗口和计时器的使用。TCP窗口中会指明双方能够发送接收的最大数据量
[4] 拥塞控制
    TCP的拥塞控制由4个核心算法组成。
    “慢启动”(Slow Start)
    “拥塞避免”(Congestion avoidance)
    “快速重传 ”(Fast Retransmit)
    “快速恢复”(Fast Recovery)

request请求 请求头、请求体、get、post请求的区别?

HTTP请求报文由3部分组成(请求行+请求头+请求体)

①是请求方法,HTTP/1.1 定义的请求方法有8种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的两种GET和POST,如果是RESTful接口的话一般会用到GET、POST、DELETE、PUT。
②为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL
③是协议名称及版本号。
④是HTTP的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
⑤是报文体,它将一个页面表单中的组件值通过param1=value1&param2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。不但报文体可以传递请求参数,请求URL也可以通过类似于“/chapter15/user.html? param1=value1&param2=value2”的方式传递请求参数。
对照上面的请求报文,我们把它进一步分解,你可以看到一幅更详细的结构图:

比较
请求缓存:GET 会被缓存,而post不会
收藏书签:GET可以,而POST不能
保留浏览器历史记录:GET可以,而POST不能  
用处:get常用于取回数据,post用于提交数据
安全性:post比get安全

请求参数长度限制:get请求长度最多1024kb,post对请求数据没有限制;但实际上浏览器地址栏内容长度有限制

HttpServletRequest的api使用?

存取request域中数据:
   setAtrrbute(key,value)  getAtrrbute(key)    removeAAtrrbute(key)
获取表单数据:    
   getParameter(id值)    getParameterValues(id值)
转发:  forward(req,resp)     包含 include(req,resp)    
request Header:
    getHeader(name)  getHeaderNames()
request Line:
    getMethod()   getRequestURL()

请求的生命周期?

request对象的生命周期是针对一个客户端(说确切点就是一个浏览器应用程序)的一起请求 当请求完毕之后,request里边的内容也将被释放点
而session的生命周期也是针对一个客户端 但是却是在别人设置的会话周期内(一般是20-30分钟) session里边的内容将一直存在 即便关闭了这个客户端浏览器 session也不一定会马上释放掉的

request和session的优点和缺点很明显
request占用资源比较少 安全性也比较高 可是相对来说 缺乏持续性
而session则相对来说 对资源的消耗会大点 安全性相对来说也会稍微低点 可是它能实现比如会话跟踪技术 个有优点和缺点
不过 个人觉得 如果可以使用request的情况下 尽量使用request 因为相对于服务器来

application 生命周期在整个应用程序中 生命周期为:应用程序启动到停止.
session 会话你可以设置他的时间 默认的是30分钟 当你关闭浏览器 结束本次会话  用户开始进行操作就产生一个唯一的session 每个session都分配了一个唯一的Id 
request是获取信息--通过用户提交的表单,查询字符串,cookie等获得信息
session是服务端用来保存一些数据(通常是标记状态的,当然也可以保存别的)
session是服务端的记录变量,可以跟踪记录访问者动作,比如登录,退出等.
request用在数据提交,表单数据等
cookie 的话 它有一个有效期 你也可以设置时间 如一个月 一年等

response响应    请求头、请求体、HttpServletResponse的api使用

addHeader(String name, String value):向HTTP响应头中加入一项内容。
sendError(int sc):向客户端发送一个代表特定错误的HTTP响应状态代码。
sendError(int sc, String msg):向客户端发送一个代表特定错误的HTTP响应状态代码,并且发送具体的错误消息。
setHeader(String name, String value):设置HTTP响应头中的一项内容。如果在响应头中已经存在这项内容,那么原先所做的设置将被覆盖。
setStatus(int sc):设置HTTP响应的状态代码。
addCookie(Cookie cookie):向HTTP响应中加入一个Cookie。
重定向:    redirect()

servlet

什么是servlet? servlet的生命周期?service方法源码?doget\dopost方法?

Servlet是sun公司提供的一门用于开发动态web资源的技术。

servlet生命周期:1、初始化阶段,Servlet容器会创建一个Servlet实例并调用【init()】方法;
2、处理客户端请求阶段,每收到一个客户端请求,服务器就会产生一个新的线程去处理;(service())
3、终止阶段,调用destroy方法终止。

我们查看源码发现,Servlet调用自身的service方法后,将强转后带有Http协议的请求和响应传进来,此时发现两个service出现了,方法名相同参数也相同(方法重写),执行的是子类重写后的方法,而带有Http协议的service方法中,通过请求所携带的信息,获取到请求的方法(get或post),最后调用doGet和doPost方法
因此继承HttpServlet重写service方法,可以间接实现Servlet接口
总结:一个类想通过浏览器访问到,必须直接或间接实现Servlet接口

get和post是http协议的两种方法,另外还有head, delete等 
这两种方法有本质的区别,get只有一个流,参数附加在url后,大小个数有严格限制且只能是字符串。post的参数是通过另外的流传递的,不通过url,所以可以很大,也可以传递二进制数据,如文件的上传。 
在servlet开发中,以doGet()和doPost()分别处理get和post方法。 
首先判断请求时是get还是post,如果是get就调用doGet(), 如果是post就调用doPost()。都会执行这个方法。 
1.doGet
GET调用用于获取服务器信息,并将其做为响应返回给客户端。当经由Web浏览器或通过HTML、JSP直接访问Servlet的URL时,一般用GET调用。GET调用在URL里显示正传送给SERVLET的数据,这在系统的安全方面可能带来一些问题,比如用户登录,表单里的用户名和密码需要发送到服务器端,若使用Get调用,就会在浏览器的URL中显示用户名和密码。
2.doPost
它用于客户端把数据传送到服务器端,也会有副作用。但好处是可以隐藏传送给服务器的任何数据。Post适合发送大量的数据。
3.一般写法是把方法写在doGet()方法中,在doPost()方法中调用执行,这样,无论你提交的是post还是get方法都可以执行

web.xml中servlet的配置、中文乱码过滤的三种方法?

1、自定义过滤器
package org.example.filter;
import javax.servlet.*;
import java.io.IOException;

//自己写的乱码过滤器
public class EncodingFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
chain.doFilter(request,response);
    }
public void destroy() {
    }
}

2、使用SpringMVC中的乱码过滤器

3、使用网上大神写的增强版自定义过滤器

package org.example.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
//大佬写的过滤器
/**
* 解决get和post请求 全部乱码的过滤器
*/

public class GenericEncodingFilter implements Filter {
public void destroy() {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//处理response的字符编码

HttpServletResponse myResponse=(HttpServletResponse) response;
myResponse.setContentType("text/html;charset=UTF-8");
// 转型为与协议相关对象
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 对request包装增强
HttpServletRequest myrequest = new MyRequest(httpServletRequest);
chain.doFilter(myrequest, response);
}

public void init(FilterConfig filterConfig) throws ServletException {
}
}

//自定义request对象,HttpServletRequest的包装类

class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
//是否编码的标记
private boolean hasEncode;
//定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
public MyRequest(HttpServletRequest request) {
super(request);// super必须写
this.request = request;

}
// 对需要增强方法 进行覆盖
@Override
public Map getParameterMap() {
// 先获得请求方式
String method = request.getMethod();

if (method.equalsIgnoreCase("post")) {
// post请求
try {
// 处理post乱码
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();

}

} else if (method.equalsIgnoreCase("get")) {
// get请求

Map parameterMap = request.getParameterMap();

if (!hasEncode) { // 确保get手动编码逻辑只运行一次

for (String parameterName : parameterMap.keySet()) {
String[] values = parameterMap.get(parameterName);

if (values != null) {
for (int i = 0; i < values.length; i++) {
try {
// 处理get乱码

values[i] = new String(values[i]

.getBytes("ISO-8859-1"), "utf-8");

} catch (UnsupportedEncodingException e) {
e.printStackTrace();

}

}

}

}

hasEncode = true;
}

return parameterMap;

}

return super.getParameterMap();

}

//取一个值
@Override
public String getParameter(String name) {
Map parameterMap = getParameterMap();

String[] values = parameterMap.get(name);

if (values == null) {
return null;
}
return values[0]; // 取回参数的第一个值

}

//取所有值

@Override

public String[] getParameterValues(String name) {
Map parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
return values;

}

}

最后在 web.xml 中配置过滤器,使用哪种方式就配置哪种

context上下文

所谓的上下文就是指语境,每一段程序都有很多的外部变量。只有想Add这种简单的函数才是没有外部变量的。一旦写的一段程序中有了外部变量,这段程序就是不完整的,不能独立运行,要想让他运行,就必须把所有的外部变量的值一个一个的全部传进去,这些值的集合就叫上下文。
通俗一点理解就是,当程序从一个位置调到另一个位置的时候,这个时候就叫上下文的切换(因为他要保存现场,各种的压栈,出栈等等),进程之间切换也叫上下文切换,因为也要保存现场,以便切换回之前的线程

session and cookie工作原理?

当用户访问服务器时,如果服务器使用session时,就会为用户创建一个session,在创建session之前,服务器会检查用户发送的请求是否存在session id,如果存在session id,说明用户已经登录过服务器,已经为之创建了session对象,如果不存在,则创建一个新的session对象,并返回客户端。且这个session id 就是cookie。
Session与Cookie的区别
cookie数据存放在客户的浏览器上,session数据放在服务器上。
cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗。
session会在一定时间内存放在服务器上。当访问增多,会比较占用服务器性能。考虑到减轻服务器性能方面,应当使用cookie。
单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点对多保存20个cookie。
将登陆信息等重要信息存放至session中,其他信息如果需要保留,存放在cookie中。

Session:
第一次请求,请求头中没有jsessionid的cookie,当访问到对应的servlet资源时,执行到getSession()会创建HttpSession对象;进而响应时就将session的id作为cookie的value,响应到浏览器 Set-cookie:jsessionid=xxxx;
再一次请求时,http请求中就有一个cookie:jsessionid=xxxx信息,那么该servlet就可以通过getSession()获取到jsessionid在服务器内查找对应的session对象,有就使用,无就创建。
1.3 Session 创建、获取、销毁
// 获取session对象,服务器底层创建Session
HttpSession session = request.getSession();
// 获取session对象的唯一标识:sessionID (JSESSIONID=E925DE1EF00F7944537C01A3BC0E2688)
String jsessionid = session.getId();
// 销毁session对象中的jsessionid
session.invalidate();
1.4 Session 共享范围
http域对象之一,服务器中可跨资源共享数据。

// 往 session 中存储 msg
HttpSession session = request.getSession();
session.setAttribute("msg", "helloSession");
// 获取 msg
HttpSession session = request.getSession();
Object msg = session.getAttribute("msg");
// 删除域对象中的数据
session.removeAttribute("msg");
1.5 Session 生命周期
一般都是默认值 30 分钟,无需更改。
取决于 Tomcat 中 web.xml 默认配置:
<session-config>
    <session-timeout>30</session-timeout>
</session-config>
Session生命周期结束时机:
浏览器关闭:销毁Cookie中的jsessionid=xxx,原session对象会保留默认30min后才销毁,30分钟后为新的session;
session销毁:主动调用 session.invalidate() 方法后,立即将session对象销毁,再次访问时会创建新的session。

Cookie:
1.浏览器向服务器发送请求,服务器需要创建cookie,服务器会通过响应携带cookie,在产生响应时会产生Set-Cookie响应头,从而将cookie信息传递给了浏览器;
2.当浏览器再次向服务器发送请求时,会产生cookie请求头,将之前服务器的cookie信息再次发送给了服务器,然后服务器根据cookie信息跟踪客户端状态
Cookie 创建:
// 用响应创建Cookie,等价于 response.addHeader("set-cookie", "name=value");
Cookie cookie = new Cookie(String name, String value); // Cookie: name=value
cookie.setMaxAge(seconds); // 设置Cookie的生命周期
cookie.setPath("/"); // 设置Cookie的共享范围
response.addCookie(cookie); // 添加1个Cookie
Cookie 获取:
// 用请求获取Cookie
Cookie[] cookies = request.getCookies(); // 获取Cookies返回数组
// 需遍历
cookie.getName(); // 获取键
cookie.getValue(); // 获取值
Cookie 修改:
设置 Cookie 数据生命周期:
// 设置Cookie生命周期,单位s
cookie.setMaxAge(int second); // 7天:7*24*60*60
// 修改Cookie
cookie.setValue(String name);
1.4 Cookie 共享范围
/ 当前项目下所有资源均可共享访问该Cookie对象内容
/project/demo 当前项目下只有资源demo均可共享访问该Cookie对象内容
设置 Cookie 数据共享范围:
// 设置Cookie的共享范围
cookie.setPath("/");
1.5 Cookie 生命周期
<0:浏览器会话结束/浏览器关闭,内存存储(默认)
=0:失效
>0:生效时间,单位s
设置 Cookie 数据生命周期:
// 设置Cookie生命周期,单位s
cookie.setMaxAge(int second); // 7天:7*24*60*60

什么是转发?什么重定向?区别是什么?

转发:由服务端进行的页面跳转。
转发的特点
地址栏不发生变化,显示的是上一个页面的地址
请求次数:只有1次请求
根目录:http://localhost:8080/项目地址/,包含了项目的访问地址
请求域中数据不会丢失
转发使用哪个方法?
request.getRequestDispatcher("/地址").forward(request, response);

重定向:由浏览器进行的页面跳转
重定向的特点
地址栏:显示新的地址
请求次数:2次
根目录:http://localhost:8080/ 没有项目的名字
请求域中的数据会丢失,因为是2次请求

Tomcat的架构?工作原理?基本配置?

源码分析(看不懂)
1.通道(Pipeline)和阀门(Valve)部分:由Valve接口、Pipeline接口及其六个实现子类组成,即图中中间到最下方的这些类。主要功能便是完成Container组件间的通信,这些阈值对象和主要对象的绑定就是在主要对象初始化时绑定的;
2.通信参数:即图中左上角的request和response部分。org.apache.catalina.connector包下的request和response里面都封装了javax.servlet.http包下response和request的实现类,同样也封装了host、context以及wrapper等对象,因此只需要处理完request和response后直接往下传就OK了,处理模式和责任链模式很像;
3.过滤器和Servlet:即图中最右边的四个类。其主要功能便是从StandardWrapperValve调用到过滤器,过滤器最后再调用进Servlet,将收到请求、调用进Container、Valve链调用、过滤器链调用和Servlet整个流程串联起来,形成整个通路。

过滤器

什么是过滤器?如何自定义过滤器?

Filter是Servlet规范的一部分,是Servlet容器(如Tomcat)实现的,并不是spring实现的。Filter接口是在tomcat的jar包中
特点:
可以拿到原始的http请求和响应的信息,但是拿不到真正处理这个请求的方法的信息
存在的问题:
通过Filter只能拿到http的请求和响应,只能从请求和响应中获得一些参数。当前发过来的这个请求实际上真正是由哪个控制器的哪个方法来处理的,在Filter里面是不知道的,因为javax.servlet.Filter是J2EE规范中定义的,J2EE规范里面实际上并不知道与spring相关的任何内容。而我们的controller实际上是spring mvc定义的一套机制。如果你需要这些信息,那么就需要使用拦截器Interceptor

自定义过滤器:
1、web.xml配置方式
 
2、SpringBoot项目配置方式
方式1
可以使用@WebFilter+@ServletComponentScan的方式
方式2
创建一个类去实现 ServletContextInitializer 接口,并把它注册为一个 Bean,Spring Boot 会负责调用这个接口的 onStartup 方法。
@Bean
public ServletContextInitializer servletContextInitializer() {
    return servletContext -> {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        FilterRegistration.Dynamic registration = servletContext.addFilter("filter", filter);
        registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/");
    };
}
方式3
可以使用FilterRegistrationBean 进行API级别的注册,注意,在这种情况下可以对Filter order进行设置,而使用spring的@Order注解是无效的
@Bean
public ServletRegistrationBean asyncServletServletRegistrationBean(){
    ServletRegistrationBean registrationBean =  new ServletRegistrationBean(new AsyncServlet(),"/");
    registrationBean.setName("MyAsyncServlet");
    registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return registrationBean;
}
FilterRegistrationBean其实也是通过 ServletContextInitializer 来实现的,它实现了 ServletContextInitializer 接口

方式4
直接实现Filter接口,并使用@Component注解标注为组件自动注入bean。
package com.demo.aop;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
 
@Component
public class DemoFilter implements Filter {
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        System.out.println("DemoFilter:" + request.getRequestURL().toString());
        //执行
        filterChain.doFilter(servletRequest, servletResponse);
    }
 
    @Override
    public void destroy() {

    }
}

原文链接:https://blog.csdn.net/qq_38586496/article/details/111563548

拦截器

什么是拦截器?如何自定义拦截器?

1.什么是拦截器以及其作用?    (SpringMVC)
   springmvc中主要是interceptor类进行拦截,在用户进行请求的时候,将该请求进行拦截并做出相应的处理,从而达到我们想要的一些效果。比如在拦截中,判断用户是否登录,登录的时候是否写入日志等等。

2.如何定义拦截器?
   实现自定义拦截器,一般有两种方式。第一种是实现HandlerInterceptor接口,第二种是实现webRequestInterceptor。
handlerInterceptor里面主要实现三个方法:
(1)preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法
         表示在进行进入controller里面的方法之前进行处理。如果返回为false,则不进入controller,如果为true,则进入controller。
(2)postHandle (HttpServletRequest request, HttpServletResponse response, Objecthandle, ModelAndView modelAndView) 方法
    这个方法则跟preHandler方法恰恰是相反的,也就是执行了controller里面的方法之后,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。
(3 )afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle,Exception ex) 方法,该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。

spring的xml进行配置需要拦截的类,此处只设置了一个,如果有多个,则可以进行多个interceptor,

<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="/**/"/>
			<bean class="com.system.interceptors.SysLogInterceptor"></bean>
		</mvc:interceptor>
	</mvc:interceptors>
下面是对应实现的SysLogInterceptor
public class SysLogInterceptor implements HandlerInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
		return true;      
	}
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
       //处理日志
	}
	@Override
	public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { 
	}
}

maven使用,idea使用,mysql数据库相关?

???

大厂面试题:

浏览器地址栏中输入地址回车,到页面渲染都发生了什么?

1、浏览器(客户端)进行地址解析。
2、将解析出的域名进行dns解析。
3、通过ip寻址和arp,找到目标(服务器)地址。
4、进行tcp三次握手,建立tcp连接。
5、浏览器发送数据,等待服务器响应。
6、服务器处理请求,并对请求做出响应。
7、浏览器收到服务器响应,得到html代码。
8、渲染页面。

http 1.0 与http 2.0的区别? http 与 https 区别

HTTP 2.0 的出现,相比于 HTTP 1.x ,大幅度的提升了 web 性能
HTTP1.0和HTTP2.0的区别
HTTP/2采用二进制格式而非文本格式
HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行;多路复用允许单一的 HTTP/2 连接同时发起多重的请求-响应消息
首部压缩,使用报头压缩,HTTP/2降低了开销
HTTP2支持服务器推送;HTTP/2让服务器可以将响应主动“推送”到客户端缓存中

HTTP和HTTPS的区别
HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
两者主要区别如下:
    1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
  2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

进程和线程的区别?

进程是程序的一次执行,是系统进行资源分配和调度的独立单位,他的作用是是程序能够并发执行提高资源利用率和吞吐率。
由于进程是资源分配和调度的基本单位,因为进程的创建、销毁、切换产生大量的时间和空间的开销,进程的数量不能太多,而线程是比进程更小的能独立运行的基本单位,他是进程的一个实体,可以减少程序并发执行时的时间和空间开销,使得操作系统具有更好的并发性。
线程基本不拥有系统资源,只有一些运行时必不可少的资源,比如程序计数器、寄存器和栈,进程则占有堆、栈。

synchronized原理

synchronized是java提供的原子性内置锁,这种内置的并且使用者看不到的锁也被称为监视器锁,使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,他依赖操作系统底层互斥锁实现。他的作用主要就是实现原子性操作和解决共享变量的内存可见性问题。

执行monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其他竞争锁的线程则会进入等待队列中。

执行monitorexit指令时则会把计数器-1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。

synchronized是排它锁,当一个线程获得锁之后,其他线程必须等待该线程释放锁后才能获得锁,而且由于Java中的线程和操作系统原生线程是一一对应的,线程被阻塞或者唤醒时时会从用户态切换到内核态,这种转换非常消耗性能。

从内存语义来说,加锁的过程会清除工作内存中的共享变量,再从主内存读取,而释放锁的过程则是将工作内存中的共享变量写回主内存。

实际上大部分时候我认为说到monitorenter就行了,但是为了更清楚的描述,还是再具体一点。

如果再深入到源码来说,synchronized实际上有两个队列waitSet和entryList。

当多个线程进入同步代码块时,首先进入entryList
有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器+1
如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁
如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null

锁的优化机制了解

从JDK1.6版本之后,synchronized本身也在不断优化锁的机制,有些情况下他并不会是一个很重量级的锁了。优化机制包括自适应锁、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁。
锁的状态从低到高依次为无锁->偏向锁->轻量级锁->重量级锁,升级的过程就是从低到高,降级在一定条件也是有可能发生的。
自旋锁:由于大部分时候,锁被占用的时间很短,共享变量的锁定时间也很短,所有没有必要挂起线程,用户态和内核态的来回上下文切换严重影响性能。自旋的概念就是让线程执行一个忙循环,可以理解为就是啥也不干,防止从用户态转入内核态,自旋锁可以通过设置-XX:+UseSpining来开启,自旋的默认次数是10次,可以使用-XX:PreBlockSpin设置。
自适应锁:自适应锁就是自适应的自旋锁,自旋的时间不是固定时间,而是由前一次在同一个锁上的自旋时间和锁的持有者状态来决定。
锁消除:锁消除指的是JVM检测到一些同步的代码块,完全不存在数据竞争的场景,也就是不需要加锁,就会进行锁消除。
锁粗化:锁粗化指的是有很多操作都是对同一个对象进行加锁,就会把锁的同步范围扩展到整个操作序列之外。
偏向锁:当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,之后这个线程再次进入同步块时都不需要CAS来加锁和解锁了,偏向锁会永远偏向第一个获得锁的线程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,反之,当有其他线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁。可以用过设置-XX:+UseBiasedLocking开启偏向锁。
轻量级锁:JVM的对象的对象头中包含有一些锁的标志位,代码进入同步块的时候,JVM将会使用CAS方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,当前线程就尝试自旋来获得锁。
整个锁升级的过程非常复杂,我尽力去除一些无用的环节,简单来描述整个升级的机制。
简单点说,偏向锁就是通过对象头的偏向线程ID来对比,甚至都不需要CAS了,而轻量级锁主要就是通过CAS修改对象头锁记录和自旋来实现,重量级锁则是除了拥有锁的线程其他全部阻塞。


3、spring
什么是spring?
Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的
什么是控制反转?
什么是依赖注入?如何实现依赖注入?

控制反转(IoC=Inversion of Control)IoC,用白话来讲,就是由容器控制程序之间的(依赖)关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在:(依赖)控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。
   IoC还有一个另外的名字:“依赖注入 (DI=Dependency Injection)”  ,即由容器动态的将某种依赖关系注入到组件之中

1.构造方法注入
构造器注入:保证了一些必要的属性在Bean实例化时就设置,并且确保了bean实例在实例化后就可以使用.
在类中,不用为属性设置setter方法,只需提供构造方法即可
在构造文件中配置该类bean,并配置构造器,在配置构造器中用
//ApplicationContext.xml
<bean id="action" class="com.action.UserAction">
    <constructor-arg index ="0" name="name" value="Murphy"></constructor-arg>
</bean>
提供构造方法
public class UserAction {
 private  String name;
   public UserAction(String name) {
         this.name = name;
    }
}
2.setter注入
根据property标签的name属性的值去找对应的setter方法.
例如: name= “aa” 对应的就是setAa方法.
由于属性注入具有可选性和灵活性高的优点,是实际上最常用的注入方式.
属性注入要求bean提供一个默认的构造函数,并为需要注入的属性提供对应的setter方法.spring先调用bean默认的构造函数实例化bean对象,然后通过反射机制的方法调用setter方法注入属性值.
还有一点需要注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。
 //ApplicationContext.xml
<bean id="action" class="com.action.UserAction">
       <property name="name" value="Murphy"/>
</bean>
提供setting方法
public class UserAction {
      private String name;
   public String getName() {
        return name;
    }   
   public void setName(String name) {
        this.name = name;
    } 
 }
3.注解注入
@Autowired(构造,接口,方法)
自动装配,默认根据类型注入
—属性Required

@Autowired(required=true):当使用@Autowired注解的时候,其实默认就是@Autowired(required=true),表示注入的时候,该bean必须存在,否则就会注入失败
@Autowired(required=false):表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。
required属性含义和@Required一样,只是@Required只适用于基于XML配置的setter注入方式,只能打在setting方法上。
public class AutowiredAction {

	private String name;
	private List<String> list;
	
	@Autowired
	private AutowiredAction(String name) {
		this.name=name;
	}
	public String getName() {
		return name;
	}
	@Autowired
	public void setName(String name) {
		this.name = name;
	}	
	@Autowired(required = true)
	private void initName(String name,List<String> list) {
		this.name = name;  
        this.list = list;
	}
}
接口

public interface AutowiredIn {

	@Autowired
	 void initName(String name,Integer age);

@Resource 默认按照名称装配
可以标注在字段或属性的setter方法上。默认按照字段的名称去Spring容器中找依赖对象,如果没有找到,退回到按照类型查找
如果配置了属性name
那么只能按照名称找依赖对象

public class ResourceAction {
	@Resource(name="name")
	private String name;	
	@Resource
	private List<String> list;
		
	public String getName() {
		return name;
	}
	@Resource
	public void setName(String name) {
		this.name = name;
	}	
	public List<String> getList() {
		return list;
	}
	public void setList(List<String> list) {
		this.list = list;
	}
}


什么是AOP面向切片?

面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP的实现方法?

如何使用Spring AOP

可以通过配置文件或者编程的方式来使用Spring AOP。
配置可以通过xml文件来进行,大概有四种方式:

1.        配置ProxyFactoryBean,显式地设置advisors, advice, target等
2.        配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
3.        通过<aop:config>来配置
4.        通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点

https://blog.csdn.net/zhangliangzi/article/details/52334964

反向代理:JDK代理 CGLIB代理

jdk动态代理
特点
Interface:对于JDK Proxy,业务类是需要一个Interface的,这是一个缺陷;
Proxy:Proxy类是动态产生的,这个类在调用Proxy.newProxyInstance()方法之后,产生一个Proxy类的实力。实际上,这个Proxy类也是存在的,不仅仅是类的实例,这个Proxy类可以保存在硬盘上;
Method:对于业务委托类的每个方法,现在Proxy类里面都不用静态显示出来。
InvocationHandler:这个类在业务委托类执行时,会先调用invoke方法。invoke方法在执行想要的代理操作,可以实现对业务方法的再包装。
总结:
JDK动态代理类实现了InvocationHandler接口,重写的invoke方法。
JDK动态代理的基础是反射机制(method.invoke(对象,参数))Proxy.newProxyInstance()

cglib动态代理
特点
原理是对指定的目标生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
注意:jdk的动态代理只可以为接口去完成操作,而cglib它可以为没有实现接口的类去做代理,也可以为实现接口的类去做代理。

spring的注解开发?配置版开发?

Component(@Repository @Service @Controller)
Repository:data access object层
Service:service层
Controller:controller层
-------
相当于<bean id="address" class="com.ckh.pojo.User"/>

    <!--开启注解支持-->
    <context:annotation-config></context:annotation-config>    
    <!--让包里注解生效-->
    <context:component-scan base-package="com.ckh.pojo"/>
-------
@Component
public class User {
    private int id;

    @Value("ckh")
    private String name;
    @Autowired
    private Address address;
等于:
    <bean id="address" class="com.ckh.pojo.User">
        <property name="name" value="ckh"/>
    </bean>
-------
自动装配装配(@Autowired)
输入下面,自动添加xml约束

    <!--开启注解支持-->
<context:annotation-config></context:annotation-config>
public class User {
    private int id;
    private String name;
    @Autowired
    private Address address;
}
    <bean id="address" class="com.ckh.pojo.Address"/>
    <bean name="user" class="com.ckh.pojo.User"/>
@Autowired先类型在名字
多个类型指定名字@Qualifier(“id”)    //针对接口有多个实现类的情况    
    @Autowired
    @Qualifier("address")
    private Address address;
也可以使用java自带注解resource
-------
javaCofig替代xml
@Configuration
public class CkhConfig {
    @Bean
    public User getUser(){
        return new User();
    }
}
public class MyTest {
    public static void main(String[] args) {
        //获取上下文
        ApplicationContext context = new AnnotationConfigApplicationContext(CkhConfig.class);
        User user = (User) context.getBean("getUser");
        System.out.println(user.getName());
    }
}

javaBean的自动装配?属性注入?
Autoword与resource的区别?

@Resource
(1) 默认按照byName方式装配,属于JavaEE自带注解,没有指定name时,name默认是变量名;
(2) 如果name一致,根据type区分,如果还一样报错;
@Autowired
(1) 默认按byType方式装配,spring注解,默认不允许为null,需要为null时,将required设置成false;
(2) 当一个接口存在多个实现类时,想要不报错,有一下几种方式:
A、在实现类上加上@Primary注解,表示优先注入该实现类;
B、使用@Qualifier指定注入的实例;
C、 当Autowired按byType查出多个时,会按byName来匹配;name表示变量名成;
两者加载的时候,如果无法注入唯一对象,就抛出异常;

注入方式有哪些?

1、配置形式:
①.基于xml文件
②.基于注解

静态代理以及实现?

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

动态代理以及实现?

动态代理其实就是java.lang.reflect.Proxy类动态的根据您指定的所有接口生成一个class byte,该class会继承Proxy类,并实现所有你指定的接口(您在参数中传入的接口数组);然后再利用您指定的classloader将 class byte加载进系统,最后生成这样一个类的对象,并初始化该对象的一些值,初始化之后将对象返回给调用的客户端。这样客户端拿到的就是一个实现你所有的接口的Proxy对象
动态代理就是在运行时生成一个类,这个类会实现你指定的一组接口,而这个类没有.java文件,是在运行时生成的,你也不用去关心它是什么类型的,你只需要知道它实现了哪些接口即可.

声明式事务
集成mybatis
spring 涉及到的设计模式?

1.工厂设计模式:
Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象
2.单例设计模式:
在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
3.代理设计模式:
代理模式在 AOP 中的应用
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性;
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理
4.模板方法:
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式
Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式
5.观察者模式:
观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题
6.适配器模式:
适配器模式(Adapter Pattern) 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)
spring AOP中的适配器模式
我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter 。Advice 常用的类型有:BeforeAdvice(目标方法调用前,前置通知)、AfterAdvice(目标方法调用后,后置通知)、AfterReturningAdvice(目标方法执行结束后,return之前)等等。每个类型Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter、AfterReturningAdviceInterceptor。Spring预定义的通知要通过对应的适配器,适配成 MethodInterceptor接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)
spring MVC中的适配器模式
在Spring MVC中,DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。
7.装饰者模式:
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能

Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多

4、spring mvc
springMvc的工作原理?请求的生命周期?

1.用户点击某个请求路径,发起一个request请求,此请求会被前端控制器DispatcherServlet处理。
2.前端控制器请求处理器映射器去查找Handler。可以依据注解或者XML配置去查找。
3.处理器映射器根据配置找到相应的Handler(可能包含若干个Interceptor拦截器),返回给前端控制器。
4.前端控制器请求处理器适配器去执行相应的Handler处理器(常称为Controller)。
5.处理器适配器执行Handler处理器。
6.Handler处理器执行完毕之后会返回给处理器适配器一个ModelAndView对象(SpringMVC底层对象,包括Model数据模型和View视图信息)。
7.处理器适配器接收到Handler处理器返回的ModelAndView后,将其返回给前端控制器。
8.前端控制器接收到ModelAndView后,会请求视图解析器(ViewResolver)对视图进行解析。
9.视图解析器根据View信息匹配到相应的视图结果,反馈给前端控制器。
10.前端控制器收到View具体视图后,进行视图渲染,将Model中的模型数据填充到View视图中的request域,生成最终的视图(View)。
11.前端控制器向用户返回请求结果。

在这里插入图片描述
springMvc的注解版开发?
MVC三层架构?

ssm三大框架的集成(spring\springmvc\mybatis)

什么是三层架构:
1、视图层(View)
存放接收用户提交请求的代码
2、服务层(Service)
存放系统的业务逻辑代码
3、持久层(Dao)
存放直接操作数据库的代码

什么是MVC:
Model(模型)承载数据,并对用户提交请求进行计算。分为两类:
    数据承载Bean(实体类)
    业务处理Bean(Service或Dao对象)
View(视图)接受用户的请求,可以是表单请求、超链接请求、AJAX请求登。
Controller(控制器)调度(将用户的请求转发给Model进行处理,并根据其计算结构向用户提供相应)

三层架构与MVC的关系:
三层架构中的View层是跟用户发生直接关系的层。
MVC中的V和C就是这样的存在,所以MVC中的V和C均属于三层架构的View层。
同时,MVC中的M(Model)包括了数据承载Bean和业务处理Bean,其中业务处理Bean分为Service或Dao对象,分别对应业务逻辑处理和数据库操作,相应的,它们对应的是三层架构中的Service层和Dao层

SSM与三层架构的关系:
SSM即Spring+SpringMVC+MyBatis框架
SpringMVC作为View层的实现者,完成用户的请求接收功能。SpringMVC的Controller作为整个应用的控制器,完成用户请求的转发及对用户的响应。
MyBatis作为Dao层的实现者,完成对数据库的增删改查。
Spring以大管家的身份出现,管理所有Bean的生命周期,即整个应用中所有对象的创建、初始化、销毁,以及对象间关联关系的维护均由Spring管理

5、spring boot
场景启动器
自动配置原理(面试高频重点)必须了解源码
集成各种数据库redis、mongodb等
集成中间件kafka、druid等
静态资源配置等

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值