面试:面经

 一、java基础

1、什么是面向对象

1)与面向过程相比,是两种不同处理问题的角度。

面向过程更注重每一步的步骤及顺序;而面向对象更注重有哪些参与者、需要做哪些事。

如:洗衣机洗衣服

面向过程:人拿衣服,放进洗衣机,放入洗衣粉,洗衣机洗衣服,洗衣机烘干衣服,人晒衣服

面向对象:人:拿衣服,放衣服,放洗衣粉,晒衣服

洗衣机:洗衣服,烘干衣服。

面向过程更加高效,一步到位;面向对象易于复用、扩展与维护。

2)面向对象有三大特性:封装、继承、多态

封装:标识出允许外部使用的所有函数以及数据项;内部细节对外透明,外部无需修改和关心内部实现。

如:javabean的属性私有,只提供对外的get,set方法。因为属性的赋值和获取只能由javabean本身来决定,不能由外部胡乱修改。

orm框架,在操作数据库时,不需要在意sql时如何执行的,只需要导入mybatis,调用mybatis已经封装好的方法即可。

继承:extends 继承基类的方法,并作出自己的改变或拓展。

子类中共性的方法和属性直接调用父类中的,子类只需拓展自己的个性

多态:基于对象所属类的不同,外部对同一方法的调用,实际执行的业务逻辑不同

条件:继承,重写父类方法,父类引用指向子类对象

缺点:无法调用子类特有的功能

2、jvm、jre、jdk三者之间的区别与联系

jdk是开发工具,jre是java运行环境,jvm是java虚拟机

jdk=jre+java工具(java、javac)

jre=jvm(bin目录)+核心类库(lib目录)

源码的执行:.java文件(源码文件)通过java工具javac编译成.class文件,jvm在得到.class文件后,会根据java类库去解释.class文件,翻译成机器码再映射到操作系统上,在调用操作系统让程序跑起来。

3、==和equals

==:比较的栈中的值;若是基本数据类型比较,则比较的是变量值;若是引用类型比较,则比较的是堆中的地址;

equals:再object类中,equals方法也是采用的==来进行比较的,通常情况下是会进行重写

如:String类中的equals方法

判断两个字符串是否相等,equals方法做出了如下处理;先判断比较的对象是否是同一个对象,在判断是否是String类型,在判断长度是否相等,最后在判断每个字符是否相等,都相等才相等。

4、 final

1)简述final的作用

修饰类:表示类不可被继承

修饰方法:表示方法不可被重写

修饰变量:表示是变量一旦被赋值就不可再改变

1、修饰成员变量:

(1)类变量:静态代码块中、声明变量时给定初始值

(2)成员变量:非静态代码块、构造器、声明变量时给定初始值

2、修饰局部变量:

系统不会对局部变量进行初始化赋值,需要程序员显示赋值;所在final修饰局部变量时,可以给上默认值,也可以不给默认值,但后续程序一旦赋值就不可再改变

3、修饰基本数据类型和引用类型

若修饰基本数据类型,则之后不能再改变

若修饰引用类型,初始话后不能再让其指向另一个对象,但是引用的对象中的值可以改变

2)为什么局部内部类和匿名内部类只能访问局部的final变量?

首先内部类和外部类是同属于一个级别的,不会因为内部类定义在方法中而随着方法的执行完毕就销毁。当外部类的方法执行完毕是,其局部变量就会销毁,而内部类对象只有在无处引用时才会被回收;这就会有个问题,内部类访问了一个不存在的变量,为了解决这个问题,就copy了一份局部变量到内部类中;而为了使内部类中的变量和局部变量的值同一,就用到了final关键字,让其一旦赋值就不可再被修改

5、String、StringBuffer、StringBuilder的区别及引用场景

String是由final关键字修饰的,不可变,每次操作都会生成新的对象;StringBuffer、StringBuilder则是在原对象上就行修改

StringBuilder是非线程安全的

StringBuffer是线程安全的,因为其中的方法都被synchronized修饰

性能:StringBuilder>StringBuffer>String

再频繁操作字符串是,通常使用StringBuilder,在有多线程共享变量时,再考虑StringBuffer

6、重载和重写的区别 

重载:发生在同一个类中,方法命必须的相同,参数列表必须不同(参数的类型,个数,顺序不同),方法的修饰符,返回值可以不同。

重写:发生在父子类中,方法名、参数列表必须相同,返回值、抛出异常咸鱼等于父类,修饰符大于等于父类;当父类方法的修饰符时private时,该父类方法不可被重写

7、接口和抽象类的区别

抽象类中的函数可以存在普通函数;接口中的函数必须有public abstract修饰

抽象类中的变量可以时各种类型;接口中的变量必须是public state final

抽象类只能继承一个;接口能继承多个

抽象类设计的目的是实现代码的复用;可以将子类所有共性的代码抽取到抽象类中,加以实现。

接口则是对行为的一种约束,约束子类中的方法。

抽象类的设计难度更高,因为每个类只能继承一个抽象类,所以抽象类中,要包含子类中所有的共性;接口的功能没有接口的功能那么强大,但其设计难度更低

8、list和set的区别

list:有序,可重复,可通过迭代器和下标获元素;有序指的是按对象的插入顺序保存值,可存在多个null值

set:无序,不可重复,只可通过迭代器获取元素;只能存在一个null值

9、hashcode和equals

hashcode介绍

hashcode方法的作用是获取散列码,返回一个int整数,这个散列码的作用是快速找到堆中的对象。

为什么要有hashcode

以hashset来检测重复来说明:当一个对象加入hashset时,会先计算对象的hashset值来判断对象所要存储的位置;假设这个位置上已经有了一个对象,则会通过equals方法来判断这两个对象相不相等;要是相等,hashset则不会让新加入的对象加入成功;要是不相等,则是发生了hashset冲突,加入该位置链表的尾部或者形成树结构。因为equals方法比较吃性能,所以在equals方法前多加一步hashcode判断,来减少equals方法的调用

两个对象相等,则hashcode一定相等;

两个对象相等,equals方法都返回true;

两个对象hashcode相等,两个对象也不一定相等;

equals方法被覆盖,hacode方法也要覆盖

hashcode的默认行为就是为堆中对象产生一个独特的值,要是没有重写hashcode方法,两个对象是永远也不会相等的

10、arraylist和linkedlist的区别

arraylist:动态数组,需要连续内存存储,适合下表访问,有扩容机制;

扩容机制:因为数组长度固定,当超出数组长度时需要新建数组,将老数组中的值赋值到新数组中;如果插入的数据不是尾插法,则涉及到数组中的元素移动(复制);因为扩容和元素移动比较吃性能,所以在有些情况下,我们给定合适的数组长度和利用尾插法,arraylist的性能可能超过linkedlist(需要创建大量的node对象)

linkedlist:链表,可以存储在分散的内存中,适合删除插入,不适合查询;遍历数组时,使用iterator,不要使用for循环,因为for循环每次get(i)获取一个元素都需要重新遍历一次数组,非常吃性能。

11、hashmap和hashtable的区别?底层实现是什么?

区别:

hashmap方法没有synchronized修饰,所以是非线程安全的,hashtable是线程安全的

hashmap允许null键null值,hashtable不允许;当key为null时,存在下标0的位置

底层实现:

jdk8→链表高度大于8,数组长度超过64,链表将会转换为红黑树;内部元素以node节点的形式存在;

计算key的hash值,再与数组长度取模,得到数组下标;

如果下标无元素,则直接创建node存入数组;

如果有元素,则先进行equals判断,相同则取代该元素,不同则判断链表高度插入链表;当链表高度大于8,数组长度大于64,链表转为红黑树;长度小于6时,树又转成链表;

和arraylist一样,存在数组扩容

12、concorrenthashmap原理,jdk7和jdk8有啥区别?

jdk7:

数据结构:reenctrantlock+sgement+hashentry;每一个sgement都包含一个hashentry数组,每一个hashentry又是一个链表结构;

元素查询:有两次hash;第一次→定位sgement;第二次→定位到元素所在的链表头部;

锁:sgement分段锁继承自reenctrantlock;锁定操作的sgement,其他的sgement不受影响;并发度为sgement的个数,个数可以通过构造函数指定;数组扩容不会影响到其他的sgement。

查询时get方法无需加锁,由volatile保证线程可见(node中的val和next加了)。

jdk8:

数据结构:sychronized+CAS+node+红黑树;node中的val和next都加了volatile关键字来修饰保证线程可见

CAS是乐观锁,再加sychronized是因为正常情况下使用CAS,在碰到并发时在使用sychronized来保证线程安全;

锁:锁链表的node节点,不影响其他元素的读写,锁的粒度更细,效率更高;扩容时,阻塞所有的读写操作,并发扩容;

读操作无锁:

node中的val和next都加了volatile关键字来修饰保证线程可见;

数组加上volatile,保证扩容时,能被读线程感知。

二、JVM

1、什么是字节码?采用字节码的好处是什么?

1、什么是字节码→也就是指java的编译器和解释器

在java中,编译的程序和机器之间添加了一个虚拟机的感念,虚拟机对任何平台的编译程序都提供一个共同的接口,所以编译的代码只要面向虚拟机即可。编译的代码就是字节码。而java能实现跨平台得益于虚拟机中不同的解释器,每一种平台都对应不同的解释器;虚拟机在接收到字节码后,会将字节码交由解释器翻译成特定机器上的机器码,然后再由机器运行。

源代码→编译器→字节码→jvm→解释器→机器码→机器运行程序

2、好处:一定程度上解决了传统解释型语言效率低下的问题(传统:解释一句执行一句;java:先编译成.class文件,在进行解释);跨平台。

2、java类加载器

jdk自带的由三个类加载器:

1、应用类加载器 AppClassLoader : 负责加载我们在当前工程下自己编写的类

2、扩展类加载器 ExtClassLoader : 负责加载ext文件夹下所有的那些类

3、启动类加载器 BootStrapClassLoader : 负责加载JDK里所有系统使用到的类 Object java.lang.Object

4、自定义类的加载器 rt.jar,继承ClassLoader来实现

3、双亲委派机制

向上委派:查看上一级缓冲中是否已经加载过这个类,没有就继续网上一级缓存中查找

向下查找:查找这个类的加载路径,有就加载并返回,没有就交给下一级

应用类加载器→扩展类加载器→启动类加载器(从小到大,小的类中有一个parent属性记录自己的父类)

好处:

安全,避免自己写的类替换掉java的核心类;避免了重复加载

4、GC如何判断对象可以被回收

1、两法:引用计数法和可达性分析法;

引用计数法:每个对象有一个引用计数属性,每增加一个引用时,计数属性就会加一;引用释放就会减一;为零就会回收。好处,快,只需判断是否为零就行;坏处,当A引用B,B引用A,两个对象的计数属性都为一,永远不可回收(python)

可达性分析法:从GCRoot开始向下检索,所走的路径被称为引用链;当没有任何引用相连时,这个对象就可回收

2、GCRoot可以是的对象:虚拟机栈中的引用对象;方法区中的类静态属性引用对象;方法区中的常量引用对象;本地方法栈中引用的对象

3、无引用链相连,这个对象就会被回收吗?

不是的,对象有一次自我拯救的机会 ;判断一个对象是否可以回收需要两个步骤:可达性分析,无引用链相连;虚拟机创建Finalizer队列将对象加入,后续判断对象的finalize()是否被覆盖,覆盖了则调用finailze()方法,要是方法中有引用别的对象,则这个即将被回收的对象即可复活,没有引用的对象就回收;要是对象没有覆盖finalize()方法,那么也直接回收。

每个对象只触发一次finalize()方法;finalize()方法运行代价昂贵,无法保证个对象的调用顺序,建议不使用。

5、堆和栈的区别

堆:物理分配地址不连续;内存运行期确认,存储对象实例和数组;进程可见

栈:物理分配地址连续;内存编译期确认,存储局部变量,操作数栈,返回结果;线程可见

6、JVM的组成和作用,细说运行时数据区域(jvm内存)

1、JVM包含两个子系统和两个组件:

两个子系统为Class loader(类装载子系统)、Execution engine(执行引擎);

两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口);

2、作用:

Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到运行时数据区域中的方法区。

Execution engine(执行引擎):执行classes中的指令。

Native Interface(本地接口):与方法库交互,是其它编程语言交互的接口。

Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

3、jvm内存:

分为两个区域:共享区和非共享区

共享区:方法区、堆

方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

堆区:Java 虚拟机中内存最大的一块,几乎所有的对象实例都在这里分配内存,还有数组。

非共享区:java虚拟机栈、程序计数器、本地方法栈

java虚拟机栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

程序计数器:当前线程所执行的字节码的行号指示器。

本地方法栈:和java虚拟机栈差不多。

7、类的加载步骤

加载→链接→初始化→使用→卸载

链接又可分为三部:验证→准备→解析

加载:将 Java 类的字节码文件加载到机器内存中

验证:确保加载的类信息符合JVM规范,没有安全方面的问题

准备:为类的静态变量分配内存,并将其初始化为默认值

解析:将类、接口、字段和方法的符号引用转为直接引用

初始化:为类的静态变量赋予正确的初始值

使用:

卸载:

8、垃圾回收算法

1、现有四种算法:标记清除法,复制算法,标记压缩法、分代算法

标记清除法:标记无用对象,然后清除回收。缺点:效率低,无法清除垃圾碎片

复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后把已使用的内存删除。缺点:内存使用率不高,只有原来的一半。

标记压缩法:标记无用对象,将存活的对象移到一边,清除边界以外的内存。

分代算法:根据对象的存活中的周期不同,将内存分为几个区域,分别是老年代和年轻代,老年代使用标记压缩法,年轻代使用复制算法。

2、java使用分代算法;老年代:新生代=2:1

新生代又分为:eden,from,to;比例为8:1:1

一个对象先进入eden区,eden满了就将存活的复制到from区,eden清空;下次eden区满了,就将eden区存活的和from区存活的放入to区,清空eden区和form区,from区改名为to,to改为from,一直这样来回倒腾。minor GC

每一次from到to,对象的年龄就加一,当年龄到达15,对象就会进入老年代;大对象会直接进入老年代。

老年代的空间到达某个值时也会进行垃圾回收,major GC;major GC非常吃性能。

9、JVM调优的方式

1、jdk自带的两个监控工具,在jdk的bin目录下,分别是jconsole、jvisualvm

jconsole:用于对 JVM 中的内存、线程和类等进行监控

jvisualvm:可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等

2、jvm的调优参数

-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。

三、异常

1、java中的异常体系

java所有的异常的顶级父类:Throwable;Throable有两个子类:error和exception

error:程序无法处理的错误,程序停止;如内存溢出OOM

exception:分为运行时异常runexception和编译时异常checkedexception

runexception:程序运行时出现的错误,如空指针异常

checkedexception:程序编译时会报的异常(javac),通常idea都能帮助识别到,如赋值时类型写错了,int类型拿字符串接。

四、并发

1、线程的生命周期,线程有哪些状态?

状态:创建,就绪,运行,阻塞,死亡。

阻塞分三种:等待阻塞,同步阻塞,其他阻塞

等待阻塞:调用现成的wait()方法,阻塞(等待池)的同时释放所有资源,不能自动唤醒,必须依靠其他线程调用notify或者notifyAll方法;wait是Object类中的一个方法。

同步阻塞:sychronized锁,未抢到对象的同步锁的线程进入锁池;

其他阻塞:运行的线程调用sleep方法、join方法、发出I/O请求,线程会被置于阻塞;sleep超时,join超时或者终止,I/O处理完毕,线程会自动进入就绪状态;sleep是Thread类的方法。

创建:创建一个线程;

就绪:创建完线程,其他线程调用这个线程对象的start方法,这个线程就进入就绪状态,等待CPU分配使用权;

运行:就绪的线程获得了CPU的使用,开始执行代码;

阻塞:线程放弃CPU的使用,停止运行;需进入就绪状态,该线程才有可能再次运行;

死亡:线程执行完毕或者出现了异常推出了run方法,结束了该线程的声明周期。

2、sleep(),wait(),join(),yield()方法的区别

1、池:锁池,等待池

锁池:所有需要需要竞争同步锁的线程都会放到锁池;一个线程拿到锁,其他线程等待,锁被释放,锁池中的线程都会去竞争同步锁,某个线程拿到锁,其他线程又会进入等待。

等待池:调用wait方法后,线程进入等待车,不会争抢同步锁,只有在被其他线程调用notify()和notifyAll()后才会进入锁池,争抢同步锁。

2、sleep和wait的区别

sleep是Thread类的静态本地方,wait是Object类的本地方法

sleep不会释放lock,wait会释放,并且会加入到等待队列中

sleep不依赖于同步器synchronized,但是wait依赖(防止并发程序执行紊乱,他不会自己醒;1、线程一判断队列有没有数据,2、线程一休眠,3、线程二添加数据,唤醒线程一,4、线程一被唤醒,返回队列中的数据。要是2,3顺序交换,队列中的数据将永远无法取出,因为线程一无法再被唤醒)

sleep不需要被唤醒,wait需要

sleep多用于当前线程休眠,暂停工作,wait多用于多线程之间的通信

sleep会让出CPU的执行时间,且强制上下文切换,wait不一定,他可能刚wait之后立马还没换人又是他抢到了锁

3、yield和join的区别

yield方法执行后,线程立马进入就绪状态,马上释放cpu执行权,但有可能下次cpu调度,还是这个线程

join是立马执行调用该方法线程,当前线程进入阻塞状态

3、对线程安全的理解

线程安全,也可以称之为内存安全,堆是共享的,在Java中,对象是在堆中的,以java对象为例,当多个线程访问同一个对象时,如果不进行而外的同步操作或者协调操作,调用这个对象的行为都可以获得一个正确的结果,这就是线程安全。

堆:堆是进程和线程所共有的空间

栈:每个线程所独有的空间

现在主流的系统都是多任务的,即多个进程同时进行,为了线程安全,每个进程都只能访问分配给自己的存空间,不能访问别的,这是操作系统保障的。

而每个进程的内存空间都会有一块特殊的公共区域,堆。进程内的所有线程都可以访问到这个区域,这就是问题的所在

4、Thread和Runable的区别

Thread类和Runable接口实质是继承关系,没什么可比性,无论是两者的哪个都需要new Thread,然后执行run方法。用法上复杂线程操作时,使用Thread,毕竟实现了Runable类,拥有的功能更多,更好操作;简单的执行任务时可以选者Runable。

5、说说对守护线程的理解

守护线程是为所有非守护线程服务的线程,用户线程执行完了,守护线程也会退出;守护线程的终止时自身无法控制的,所以不要把重要的操作分配给他,如I/O,文件读取等,但守护线程也有一定的作用,如垃圾回收可以交由守护线程来执行

守护线程的子线程也是守护线程;

在线程池中,守护线程会被转化为用户线程,所以守护线程不能使用java的线程池。

6、ThreadLocal的原理和使用场景

每个Thread对象都含有一个ThreadLocalMap类型的成员变量threadlocals,他存储本线程中所有的ThreadLocal对象(key)及其对应的值(value),ThreadLocalMap对象是由一个一个entry组成,entry又是由threadlocal和object组成;

当执行set方法时,先获取当前线程对象,在根据当前线程对象获取ThreadLocalMap对象,再以当前ThreadLocal为对象,再将值存入ThreadLocalMap中

当执行get方法时,也是先获取当前线程对象,再根据当前线程对象获取ThreadLocalMap对象,再根据当前ThreadLoacl对象来获取value。

每一条线程都有一条私有的ThreadLocalMap容器,这些容器相互对立互不影响,因此不存在线程安全的问题。

使用场景:

在进行对象跨服传递时,使用ThreadLocal可以避免层传层的多次传递;

线程间数据隔离;

事务操作,存储线程事务信息;

数据库连接,session会话管理。

7、ThreadLocal内存泄漏问题,如何避免

ThreadLocal内存泄漏的根源:由于ThreadLocalMap的生命周期跟Thread一样长,要是没有手动删除对应key的一些强引用,就会导致内存泄漏的问题;而不是TreadLocal是弱引用的原因

正确使用:

每次使用完ThreadLocal都调用他的remove方法

将ThreadLocal变量定义成privative static,以确保能够访问到entry中的值,进而删除。

8、并发、并行、串行

串行:时间上不可重叠,钱一个任务每完成,后一个任务只能等着;

并行:时间上是重叠的,两个任务在同一时刻互不干扰的同时执行

并发:允许两个人五互相干扰,同一时间,只有一个任务运行,交替执行

9、并发的三大特性

以方法中得cout++为例:

将count从主存读到工作内存中的副本

+1的运算

将计算的结果写道工作内存

将工作内存刷回主存

原子性:要不全执行完成,要么都不执行,如前三步为一个整体

有序性:虚拟机比一定会按照我们写的代码顺序来执行,有可能将代码重排列

可见性:当多个线程访问同一个变量时,一个线程修改了这个变量,其他线程能够立即看到修改的值。如第三步,改了工作内存就能看到,不一定要刷回主存。

10、为什么用线程池?解释线程池的参数

1、降低资源消耗;线程的创建和销毁非常吃性能

2、提高相应速率;线程池中已创建好了线程,那用直接就可以使用

3、提高线程的可管理性;达到线程可控,不会让线程一直创建下去

corePoolSize:核心线程数(最少线程数)。

maxinumPoolSize:最大线程数(最多线程数)。

keepAliveTime、unit:空闲存活时间;setKeepAliveTime来设置空闲时间。

workQueue:存放待执行任务的队列;核心线程放完后,会优先把多余的线程放入队列中,队列拍完,才会区线程池中创建线程,直至最大线程数。

threadFactory:线程工厂;用于生产线程→执行任务。

Handler:拒绝策略;当到达最大线程数时,就可以使用拒绝策略;调用shutdown等方法。

11、简述线程池的一个处理流程(跟10)

线程池执行任务:这个任务指的就是别人需要一个线程,你怎么给他

1、先判断核心线程数是否放满,未满给核心线程;满了往下。

2、判断队任务队列是否放满,未满放入队列;满了往下。

3、判断是否到达最大线程数,未到达创建临时线程给他;满了根据拒绝策略处理任务

12、线程中阻塞队列的作用?为什么时先添加队列而不是先创建最大线程?(跟11)

作用:

一般的队列只能保证队列有限长度的缓冲区,超出部分无法保留;但是阻塞队列可以。

阻塞队列可以保证没有任务时,阻塞获取任务的线程,进入wait状态,释放cpu资源

阻塞队列自带阻塞和唤醒的功能

原因:

创建线程时,要获取全局锁,其他线程都得阻塞,影响整体效率。

13、线程池中线程复用的原理

线程池将线程和任务进行解耦,任务是任务,线程是线程;

原先我们一个线程对应一个任务,继承Thread类,重写run方法,将任务写入,然后start;

现在,由于线程池对Thread进行了封装,创建完线程后,不断让线程循环判断是否有任务需要执行,由则直接调用任务的run方法执行。(原先start执行,现在不用start,直接线程run)

五、http协议

1、TCP三次握手和四次挥手以及11种状态,及如何操作的

closed,listen,syn_sent,syn_recd,estabilshed,

fin_wait1,fin_wait2,close_wait,last_ack,time_wait,closing

三次握手:

  1. 一开始,建立连接之前服务器和客户端的状态都为CLOSED;(closed)
  2. 服务器创建socket后开始监听,变为LISTEN状态;(listen)
  3. 客户端请求建立连接,向服务器发送SYN报文,客户端的状态变味SYN_SENT;(syn_sent)
  4. 服务器收到客户端的报文后向客户端发送ACK和SYN报文,此时服务器的状态变为SYN_RCVD;(syn_rcvd)
  5. 然后,客户端收到ACK、SYN,就向服务器发送ACK,客户端状态变为ESTABLISHED;(established)
  6. 服务器端收到客户端的ACK后变为ESTABLISHED。此时3次握手完成,连接建立!(established)

四次挥手:

  1. 客户端先向服务器发送FIN报文,请求断开连接,其状态变为FIN_WAIT1;(fin_wait1)
  2. 服务器收到FIN后向客户端发送ACK,服务器的状态围边CLOSE_WAIT;(close_wait)
  3. 客户端收到ACK后就进入FIN_WAIT2状态,此时连接已经断开了一半了。如果服务器还有数据要发送给客户端,就会继续发送;(fin_wait2)
  4. 直到发完数据,就会发送FIN报文,此时服务器进入LAST_ACK状态;(last_ack)
  5. 客户端收到服务器的FIN后,马上发送ACK给服务器,此时客户端进入TIME_WAIT状态;(time_wait)
  6. 再过了2MSL长的时间后进入CLOSED状态。服务器收到客户端的ACK就进入CLOSED状态。(closed)

至此还有一个状态没出来,CLOSING状态表示: 客户端发送了FIN,但是没有收到服务器的ACK,却收到了服务器的FIN,这种情况发生在服务器发送的ACK丢包的时候,因为网络传输有时会有意外。

六、Spring框架

1、如何实现一个IOC容器

1、配置文件配置一个包扫描路径

2、递归包扫描获取.class文件

3、反射、确定需要交给IOC托管得类

4、对需要注入的类进行依赖注入

2、Spring是什么

轻量级的J2EE框架(J2SE,J2EE,J2ME);是容器框架,存放javabean(java对象);中间层框架,可以将其他技术进行整合,如mybatis,hibernate。

轻量级的控制反转(IOC)和面向切面(AOP)框架

3、谈谈对AOP的理解

说到AOP,就要先说说OOP

OOP是面向对象编程,允许从上往下定义,但是不适合从左往右定义;举个例子如:OOP适合做一个类中有哪些功能;但对于分散的类,对其引入公共行为时,不行;如日志,事务管理等

AOP是面向切面编程;它可以将程序中交叉的业务逻辑(日志,事务,安全),封装成一个切面,然后注入到目标对象。相当于对某个对象或者某个对象的功能进行一个增强,在某个方法执行前,或者执行后做一些额外的事情。

4、谈谈对IOC的理解

有三点:容器概念,控制反转,依赖注入

IOC容器概念:实际上就是一个map,里面存的都是各种对象;项目启动先读取配置文件中的bean节点,跟根据全限定类名反射创建对象放入map中;或者根据读取配置文件中的包扫描路径扫描包,将包中类上带@repository,@service,@controller,@component反射创建对象放入map

控制反转:没有IOC容器之前,A对象依赖于B,当A程序运行到某一点时,自己必须主动去创建B对象;B对象的创建和使用都在自己手里。

引入IOC容器之后,A对象不在依赖于B,当A程序运行到某一点需要B对象时,IOC容器会主动创建B对象注入到A对象需要的位置

依赖注入:依赖注入是实现IOC(控制反转)的方法;就是IOC容器在运行期间,动态的将某种依赖关系注入到对象当中。

5、BeanFactory和ApplicationContext有什么区别?

ApplicationContext是BeanFactory的子接口

1、ApplicationContext提供了更完整的功能:

继承了MessageSource,因此支持国际化

统一的资源文件访问方式;resource

提供在监听器中注册bean事件

同时加载多个配置文件

载入多个(有继承关系)上下文,使每一个上下文都专注于一个特定的层次,比如:应用的web层

2、BeanFactory是采用延迟加载的方式来注入bean;只有在使用到某个bean时才会去对该bean进行实例化。这不利于程序员检查错误,如我们的bean的属性注入有问题,只有在我们使用到这个bean时才能发现bean的配置有问题。

ApplicationContext不同于BeanFactory,他是在容器启动时就一次性将所有的bean创建好;这样在我们容器启动时就能发现我们的配置有问题;使用起来也方便,不需要等待,直接取就行。但是相比于BeanFactory也有不足,就是比较占内存,当bean比较多时,程序启动较慢

3、BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用;但是BeanFactory需要手动注册,ApplicationContext则是自动注册。

6、描述一下Spring Bean的生命周期

1、解析类得到BeanDefinition;

2、如果有多个构造方法,则要推断构造方法;

3、确定好构造方法后,进行实例化得到一个对象;

4、对对象中加了@AutoWired注解的属性进行属性填充;

5、回调Aware方法,如:BeanNameAware,BeanFactoryAware;

6、调用BeanPostProcessor的初始化前的方法;

7、调用初始化方法;

8、回调BeanPostProcessor的初始化后的方法,在这里会进行AOP;

9、如果当前创建的bean是单例,则会把bean放入单例池;

10、使用bean;

11、Spring容器关闭时调用DisposableBean中的destory()方法,销毁。

7、Spring支持的几种bean作用域

singletion:默认,每个容器只有一个bean的实例

prototype:每一个bean请求提供一个实例,新建一个对象;

request:每个http请求中创建一个单例对象;

session:每一个session有一个单例对象;

application:在ServletContext的生命周期中有一个单例对象;

websocket:在WebSocket的生命周期中有一个单例对象

8、Spring框架中的单例Bean是线程安全的吗?

不是线程安全的,因为Bean默认是单例模式,但Spring框架并没有对bean进行多线程封装处理。如果想让他变成线程安全的,那就不能使用单例模式,需将bean的作用域singleton改成protopyte。

9、Spring框架中都用到了哪些设计模式

1、简单工厂(这个不是在23中设计模式中,但也属于一种设计模式):工厂类根据传入的参数,动态决定应该创建哪一个产品类。 如Spring的BeanFactory,根据传入唯一标识,来获取Bean对象,但是对象的创建是在传入参数之前还是之后,这个要根据具体情况来定。

2、工厂方法:实现了FactoryBean接口的bean;Spring在调用getBean()方法时,返回的不是实现了FactoryBean接口的这个bean,而是这个bean中的getObject()方法的返回值。

3、单例模式:确保一个类只有一个实例,并提供一个访问它的全局访问点;全局访问点BeanFactory。

4、适配器模式:Spring定义了一个适配器接口,使得每一个Controller都有一个适配器实现类,让适配器代替Controller取执行相应方法(springMVC中的HanderAdater,可以查看SpringMVC的执行流程)

5、装饰器模式:动态的给一个对象添加一些额外的职责。就增强功能来说,装饰器模式比生成子类更灵活

6、动态代理:SpringAOP容器会为目标对象动态的创建一个代理对象。

10、Spring事务的实现方式和原理以及隔离级别

1、实现方式:两种→编程式事务;申明式事务,Spring的事务只是基于数据库的事务进行扩展而已

编程式事务:自己程序中手动处理,调用commit方法,rollback方法等

申明式事务:方法上加@Transactional注解

2、原理:以@Transactional为例,当方法上加了注解,Spring会基于这个类生成代理对象,当使用代理对象的方法时,这个方法存在@Transaction注解,那么代理逻辑,会将事务的自动提交设置为false,然后再去执行原本的业务逻辑;要是执行的业务逻辑没有出现异常,代理逻辑会将事务进行提交;要是出现了异常,就会将事务进行回滚。

3、隔离级别:Spring的隔离级别就是数据库的隔离级别,外加一个默认级别

read uncommitted(未提交读):允许可以读取未提交事务的数据;会产生脏读:B修改→A读→B回滚,A读到不存在的数据。

read committed(提交读):一个事务要等另一个事务提交后才能读取数据;会产生不可重复读:A读→B修改→B提交→A读,两次读数据不一样。

repeatable read(重复读):开启事务,不再允许修改操作;会产生幻读:与提交读差不多,他只限制了修改,但是新增删除没限制。

seriazlizable(可串行化):最高隔离级别,事务串行化,锁表,吃数据库性能,效率低下。

11、Spring事务传播机制

有七种方式:方法A不知道加不加@Transactional,方法B加@Transaction,A调用B。

required(默认):如果A有事务,则B加入A的事务中;如果A没有事务,B自己新建一个事务;

supports:如果A有事务,则B加入A的事务中;如果A没有事务,那B也不要事务;

mandatory:如果A有事务,则B加入A的事务中;如果A没有事务,那就抛异常;

requires_new:A有没有事务,B都新建事务,然后各管各的,两者互不干扰;

not_supproted:如果A有事务,A的事务先挂起,先跑B的事务;如果A没有事务,没有就算了;

never:A不能存在事务,A有事务B就抛异常;

nested:嵌套事务,A为父事务,B为子事务;A事务要是回滚,B子事务也回滚;B子事务回滚,A父事务不一定回滚,A父事务可通过catch将异常捕获。

12、Spring的事务什么时候会失效?

Spring事务的原理就是AOP,进行了切面增强。

失效原因:

1、发生了自调;也就是this,this调用的是本类的方法,而不是代理类

2、方法不是public;@Transactional只能作用于public上

3、数据库不支持事务;Spring的事务是基于数据库事务进行扩展的

4、没有被Spring管理;加了@Transactional的方法的类要注入到IOC容器中

5、异常被捕获,事务不会回滚;

13、什么是bean的自动装配

自动装配的开启,需使用到xml配置文件中的<bean>标签的autowire属性

autowire的属性有五种装配方式:

1、no        缺省,通过ref标签手动配置

2、byname        根据bean的属性名称进行自动装配(id)

3、bytype        根据bean的类型进行装配(class)

4、constructor        根据构造器的参数的参数类型装配

5、autodetect        bytype和constructor的结合,有默认的构造器则用constructor,否则使用bytype。

14、Spring Boot、Spring MVC、Spring有什么区别

Spring:是一个IOC容器,用来管理bean;使用依赖注入实现控制反转,使用依赖注入也很方便的整合各种框架;提供AOP机制,来弥补OOP代码重复的问题,更方便的为不同的类不同方法中的共处抽取成切面,通过代理对方法进行增强,如日志、异常等。

Spring MVC:是Spring对Web框架的一个解决方案;提供了一个总的前端控制器,用来接收请求,再通过一系列的策略:映射,适配,解析,渲染等流程,将渲染完成的视图展现给前端。

Spring Boot:是Spring提供的一个快速开发工具包,让程序员更快,更方便的开发Spring+Spring MVC应用,约定大于配置。

15、Spring MVC的工作流程

1、用户发送请求至前端控制器dispatcherServlet;

2、前端控制器收到请求,调用处理器映射器HanderMapping(一个map)

3、处理器映射器根据请求(url作为key)找到对应的处理器适配器,将处理器适配器返回给前端控制器;要是返回时还有处理器拦截器,就连同其一并返回给前端控制器;

4、前端控制器调用处理器适配器HanderAdapter;

5、处理器适配器再根据适配,调用具体的后端控制器controller;

6、后端控制器执行完成后返回视图模型ModelAndView;

7、处理器再将视图模型返回给前端控制器;

8、前端控制器再将视图模型传给视图解析器ViewReslover;

9、视图解析器再将视图模型进行解析,得到视图View;

10、前端控制器再根据视图进行渲染,最后返回给用户。

16、SpringMVC的主要组件

七、Linux系统

1、列举出Linux系统常用指令,和起作用

pwd:显示当前所在目录

whoami:显示当前账户名称

cd:切换目录

ls:查看当前目录下的所有文件

mkdir:创建一个空目录

rmdir:删除一个空目录

touch:创建文件

cp:复制

mv:移动文件

cat:查看文件内容

yum:一个专门为了解决包的依赖关系而存在的软件包管理器

rpm:软件管理程序

tar:压缩和解压缩

ps:显示当前进程的状态,类似于 windows 的任务管理器

top:持续监听进程运行状态

chkconfig:可以给每个服务的运行级别设置自动启动/关闭

netstat:监控网络状态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值