第一章:第四讲:编写你的第一个Spring程序
1 如何通过spring.io去创建基础的spring项目框架
2 怎么运行一个简单的web程序
Terminal:curl http://localhost:8080/hello
3 spring-boot的maven打包工具的简单使用
Terminal:mvn clean package -Dmaven.test.skip(跳过测试)
Terminal:->target->java -jar helloworld-0.0.1-SNAPSHOT.jar(跳过测试)
4 在pom文件中如果不使用spring-boot自带的parent节点要怎么处理
java面试准备
1.HashMap 底层实现原理是什么
数组特点:
链表特点:
哈希表特点:
2.Java 的多线程
3.线程池,以及实现固定大小线程池底层是如何实现的
4.Redis 为什么这么高效,使用的场景是什么
5.分布式服务
6.幂等概念
7.数据库如何实现 rollback
8.TCP/IP 协议是如何保证数据可靠性的
一、java面试基础
1.JDK 和 JRE 有什么区别
JDK:Java Development Kit(java开发工具包),java语言编写的程序所需要的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译Java源码的编译器Javac,还包含了很多Java程序调试和分析的工具。简单的说JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境,SDK(Software Development Kit,指软件开发包,可以包括函数库、编译程序等)。
JRE:Java Runtime Enviroment(java的运行环境,是面向java程序的使用者),包含了java虚拟机、java基础类库等。
2.== 和 equals 的区别是什么?
==:比较运算符,比较数值类型时,是比较它们的值;比较引用类型时,是比较两个变量指向的内存地址
equals():Object类的方法,默认实现是返回两个对象==的比较结果,但是需要注意的是equals()可以被重写
3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
不对,在散列表中,hashCode()相等即两个键值对的哈希值相等,而哈希值相等,并不一定能得出键值对相等
hashCode():用于返回对象的哈希码值
4.final 在 java 中有什么作用?
final在Java中是修饰符关键字,final可以修饰变量、方法和类
final修饰的变量,这个变量是不可修改的。如果该变量是值变量,那么该变量的值不能修改;如果该变量是引用变量,那么不允许再给该变量赋值新的对象
final修饰方法,禁止子类继承的时候重写该方法;同时,final修饰后的方法执行速度会更快,因为在调用方法的时候,直接将方法插入到方法调用的位置。
5.java中math的常用方法
(1)取整
Math.floor():向下取整
Math.round():四舍五入取整
Math.ceil():向上取整
(2)java求绝对值
Math.abs()
(3)java随机数
Math.random():随机取0~1的数
(4)java幂函数
Math.pow(a,b):a的b次方
(5)java开根号
Math.sqrt()
6.String类是基本数据类型吗?
String类不是基本的数据类型,是final修饰的java类,java中的基本类型一共有8个
(1)字符类型:char
(2)整数类型:byte, short,int,long
(3)浮点型:float, double
(4)布尔类型:boolean
7.java 中操作字符串都有哪些类?它们之间有什么区别?
String、StringBuffer和StringBuilder:三个类都是以char[]的形式保存的字符串
(1)String:final修饰,字符串是不可变的,对String类型的字符串做修改操作相当于重新创建对象
(2)StringBuilder:可在原有对象的基础上进行操作,非线程安全,但是速度快,单线程环境下推荐使用
(3)StringBuffer:可在原有对象的基础上进行操作,线程安全,多线程环境下推荐使用
效率比较:StringBuilder>StringBuffer>String
8.java如何实现字符串反转
(1)StringBuilder或StringBuffer的reverse():new StringBuilder(str).reverse().toString()
(2)将字符串转换成字符数组,再倒叙拼接
(3)通过charAt(int index)返回char值再进行字符串拼接:reverse = s.charAt(i)+reverse
9.String 类的常用方法都有那些
(1)求字符串长度
String str = new String("abcdefg");
int strLength = str.length();
(2)求字符串某一位置字符
String str = new String("abcdefg");
char ch = str.charAt(1);
(3)提取子串
substring(int beginIndex);substring(int beginIndex, int endIndex);
(4)字符串比较
compareTo(), compareToIgnore() 忽略大小写
equals(),equalsIgnoreCase() 忽略大小写
(5)字符串拼接
concat():"aa".concat("bb");效果等价于"+"
(6)字符串中单个字符查找
indexOf(int ch/String str)
indexOf(int ch/String str, int fromIndex):从fromIndex位置向后查找
lastIndexOf(int ch/String str)
lastIndexOf(int ch/String str, int fromIndex):从fromIndex位置向前查找
(7)字符串中大小写转换
toUpperCase()
toLowerCase()
(8)字符串替换
replace(char oldChar, char newChar):返回新的字符串
replaceFirst(String regex, String replacement):返回新的字符串
replaceAll(String regex, String replacement):返回新的字符串
(9)其他类方法
trim():去除字符串两端的空格
startsWith(String prefix) ,endWith(String prefix):比较起始字符串或者终止字符串是否相同,返回boolean类型
contains(String str):判断参数是否被包含在字符串中,返回boolean类型
10.抽象类是否一定要有抽象方法
不一定。抽象类必须有关键字abstract来修饰。抽象类可以不包含有抽象方法,如果一个类包含抽象方法,则该类必须是抽象类。
11.普通类和抽象类有哪些区别
抽象类不能被实例化
抽象类可以有抽象方法,抽象方法只需申明,无需实现
含有抽象方法的类必须申明为抽象类
抽象类的子类必须实现抽象类中的所有抽象方法,否则这个子类也是抽象类
抽象方法不能被声明为静态,不能用private修饰,不能用final修饰
12.接口和抽象类有什么区别
一个类只能继承一个抽象类,但可以实现多个接口
抽象类中可以包含抽象方法和非抽象方法,而接口中的所有方法都试抽象的
子类继承抽象类必须实现抽象类中的所有抽象方法,否则子类也必须是抽象类;而子类实现接口则必须实现接口中的所有抽象方法
13.java 中 IO 流分为几种
按照流的流向划分,可以分为输入流和输出流
按照操作单元划分,可以分为字节流和字符流
按照流的角色划分,可以分为节点流和处理流
InputStream/Reader:所有的输入流的基类,前者是字节输入流,后者是字符输入流
OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流
14.BIO, NIO, AIO有什么区别
BIO:同步阻塞式IO,实现模型为一个连接就需要一个线程去处理,缺点是资源的浪费
NIO:同步非阻塞IO,有效请求时,才会使用一个线程去处理
AIO:异步非阻塞IO,也被称为NIO2.0,AIO在进行读写操作时,直接调用API的read和write方法即可,这两种均是异步的方法,且完成后会主动回调函数。
15.Files的常用方法都有哪些
Files.exists():检测文件路径是否存在
Files.createFile():创建文件
Files.createDirectory():创建文件夹
Files.delete():删除一个文件或目录
Files.copy():复制文件
Files.move():移动文件
Files.size():查看文件个数
Files.read():读取文件
Files.write():写入文件
二、容器
1.java容器都有哪些?
java容器分为Collection和Map两大类,其下又有很多子类:
Collection:一个独立元素的序列,这些元素都服从一条或多条规则
Map:一组成对的“键值对”对象,允许你是用键来查找值
2.Collection和Collections有什么区别?
Collection是一个接口,它是Set,List等容器的父接口;Collections是一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等
3.List,Set,Map之间的区别?
元素有序 | 允许元素重复 | ||
List | 是 | 是 | |
Set | AbstractSet | 否 | 否 |
HashSet | |||
TreeSet | 是(用二叉树排序) | ||
Map | AbstractMap | 否 | key值必须唯一,value可重复 |
HashMap | |||
TreeMap | 是(用二叉树排序) |
List的实现类(ArrayList,Vector,LinkedList):
ArrayList和Vector内部是线性动态数组结构,所以查询效率上会高很多,Vector是线程安全的,ArrayList是非线程安全的,所以性能会稍慢一些
LinkedList:是双向链表的数据结构存储数据,在做查询时会按照序号索引数据进行向前和向后遍历,查询效率偏低,但插入数据时只需要记录本项的前后项即可,所以插入速度快
4.HashMap和Hashtable的区别
存储:HashMap运行key和value为null,而Hashtable不允许;HashMap无论主键还是值都可以存放null,但是主键要求唯一,所以只能有一个null,Hashtable对null零容忍
线程安全:Hashtable是线程安全的,而HashMap是非线程安全的。推荐在单线程环境下使用HashMap,多线程使用ConcurrentHashMap
5.红黑树
红黑树是一种含有红黑结点并能自平衡的二叉查找树:
(1)每个结点要么是黑色,要么是红色
(2)根结点是黑色
(3)每个叶子结点(NIL)是黑色
(4)每个红色结点的两个子节点一定都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色结点)
(5)任意一结点到每个叶子结点的路径都包含数量相同的黑色结点(即一个结点存在黑子结点,那么该结点肯定有两个子结点)
6..如何决定使用 HashMap 还是 TreeMap
TreeMap的key值是要求实现java.lang,Comparable,所以迭代的时候TreeMap默认是按照Key值升序排序的;TreeMap的实现是基于红黑树结构,适用于按自然顺序或者自定义顺序遍历键。
HashMap的key值实现散列hashCode(),分布是散列的、不均匀的、不支持排序;数据结构主要是桶(数组),链表或红黑树,适用于在Map中插入、删除和定位元素。
7.如何实现数组和 List 之间的转换
List转换为数组:
ArrayList list = new ArrayList();
String[] strs = new String[list.size()];
list.toArray(strs);
数组转为list:
String[] s = {"a","b","c"};
List list = java.util.Arrays.asList(s);
8.在 Queue 中 poll()和 remove()有什么区别?
队列是一个典型的先进先出(FIFO)容器。队列的实现方式Queue: LinkedList PriorityQueue
(1)add()和offer()都是向队列中添加一个元素。但是如果想再一个满的队列中加入一个新元素,调用add()方法会抛出一个unchecked异常,而调用offer()方法会返回false
(2)peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,调用element()方法会抛出NoSuchElementException异常
(3)poll()和remove()都将移除并且返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常
9.哪些集合类是线程安全的?
java中线程安全的集合类有Stack(栈,继承于Vector)、Vector、Hashtable、ConcurrentHashMap(高效但是线程安全的集合)
java中的Stack类实现了基于后进先出(LIFO)原理的堆栈数据结构。
10.迭代器 Iterator
Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可以迭代ArrayList和HashSet集合
java.lang.Iterable接口被java.util.Collection接口继承,java.util.Collection接口的iterator()方法返回一个Iterator对象
next():获得集合中的下一个元素
hasNext():检查集合中是否还有元素
remove():将删除集合中Iterator指向位置后面的元素
forEachRemaining(Consumer super E> action):遍历所有元素
11.Iterator 和 ListIterator 有什么区别?
ListIterator迭代器包含的方法有
hasNext():以正向遍历列表时,如果列表迭代器后面还有元素,则返回true,否则返回false
next():返回列表中ListIterator指向位置后面的元素
hasPrevious():以逆向遍历列表,如果列表迭代器后面还有元素,则返回true,否则返回false
previous():返回列表中ListIterator指向位置前面的元素
nextIndex():返回列表中ListIterator所需位置后面元素的索引
previousIndex():返回列表中ListIterator所需位置前面元素的索引
remove():从列表中删除next()或previous()返回的最后一个元素(删除ListIterator指向位置后面的元素或者前面的元素)
set(E e):从列表中将next()或previous()返回的最后一个元素更改为指定元素e
add(E e):将指定元素插入列表,插入位置为迭代器当前位置之前
Iterator 和 ListIterator相同点:都是迭代器,当需要对集合中元素进行遍历切不需要干涉其遍历过程时,两种迭代器都可以使用
Iterator 和 ListIterator不同点:
(1)使用范围不同:Iterator可以应用于所有的集合,Set,List和Map和这些集合的子类型,而ListIterator只能应用于List及其子类型
(2)ListIterator有add方法,可以向List中添加对象,而Iterator不能
(2)ListIterator可以实现顺序向后遍历,还可以实现逆向(顺序向前)遍历
(3)ListIterator可以定位当前所有的位置
(4)都可以实现删除操作,但是ListIterator可以实现对象的修改,通过set()方法实现;Iterator仅能遍历,不能修改
12.怎么确保一个集合不能被修改?
(1)通过Collections.unmodifiableCollection(Collection c):Collections是工具类,在add、remove等修改方法中会抛出UnsupportedOperationException异常,将原本的集合的值copy了一份,定义为一个新的集合,而且是一个新类型的集合
(2)通过Arrays.asList创建的集合:Arrays.asList创建的ArrayList中没有重写其父类AbstractList的add、remove方法,所以不支持新增和删除,若果强行调用,会抛出UnsupportedOperationException异常
三、多线程
1.并行和并发有什么区别?
并发:一个处理器可以同时处理多个任务。这是逻辑上的同时发生(在单处理器和多处理器系统中都存在)
并行:多个处理器同时处理多个不同的任务。这是物理上的同时发生(在多处理器系统中存在)
2.线程和进程的区别
进程:正在进行的程序,进程是操作系统控制的基本运行单元
线程:进程中独立运行的子任务就是一个线程。如QQ.exe运行时的聊天线程,下载文件线程等
3.什么是守护线程以及作用
定义:当JVM中不存在任何一个正在运行的非守护线程时,则JVM进程即会退出
作用:守护线程拥有自动结束本身生命周期的特性,而非守护线程不具有这个特色,如垃圾回收线程
4.创建线程有哪几种方式
(1)继承Thread类创建线程类
a.定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了该线程要完成的任务
b.创建Thread子类的实例,即创建了线程对象
c.调用线程对象的start()方法来启动该线程
Public class ThreadTest extends Thread {
int i = 0;
// 重写run方法
public void run() {
for(; i < 100; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
for(int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
if (i == 50) {
new ThreadTest().start();
}
}
}
}
(2)通过Runnable接口创建线程类
a.定义runnable接口的实现类,并重写该接口的run()方法
b.创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
c.调用线程对象的start()方法来启动该线程
Public class RunnableThreadTest implements Runnable{
private int i = 0;
public void run() {
for(; i < 100; i++) {
System.out.println(Thread.currentThread.getName() + " " + i);
}
}
public static void main(String[] args) {
for(int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 50) {
RunnableThreadTest test = new RunnableThreadTest();
new Thread(test, "新线程1").start();
new Thread(test, "新线程2").start();
}
}
}
}
(3)通过Callable和Future创建线程
a.创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值
b.创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
c.使用FutureTask对象作为Thread对象的target创建并启动新线程
d.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class CallableThreadTest implements Callable {
public static void main(String[] args) {
CallableThreadTest test = new CallableThreadTest();
FutureTask ft = new FutureTask<>(test);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值 " + i);
if (i == 50 ) {
new Thread(ft, "有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值: " + ft.get());
} catch (InterrupetedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Interger call() throws Exception {
int i = 0;
for(; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}
采用实现Runnable、Callable接口的方式创建多线程:
优势是:线程类只是实现了Runnable接口或Callable接口,还可以继承其它类;
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想
劣势:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法
使用继承Thread类的方式创建多线程:
优势:编写简单,如需要访问当前线程,直接使用this即可获得当前线程
劣势:线程类已经继承了Thread类,所以不能再继承其它父类
5.runnable 和 callable的区别
(1)Runnable接口run()方法无返回值;Callable接口call()方法有返回值,支持泛型
(2)Runnable接口run()方法只能抛出运行时异常,且无法捕获处理;Callable接口call()方法允许抛出异常,可以获取异常信息
6.线程有哪些状态
(1)新建状态(New):新创建了一个线程对象
(2)就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待CPU的使用权
(3)运行状态(Running):就绪状态的线程获取了CPU,执行程序代码
(4)阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态
a.等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中,进入这个状态后,不能自动唤醒,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒
b.同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中
c.其他阻塞;运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时时、join()等待线程终止或超时,或者I/O处理完毕时,线程重新转入就绪状态
(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
7.sleep() 和 wait() 有什么区别
sleep:线程类Thread定义的方法,表示线程休眠,会自动唤醒,但是调用sleep方法不会释放对象锁
wait:Object中定义的方法,需要调用notify()或者notifyAll()方法
8.notify()和 notifyAll()有什么区别
notify()和 notifyAll()都是用来唤醒调用wait()方法进入等待锁资源队列的线程,区别在于:
notify():唤醒正在等待此对象监视器的单个线程。如果有多个线程在等待,则选择其中一个随机唤醒(由调度器决定),唤醒的线程享有公平竞争资源的权利
notifyAll():唤醒正在等待此对象监视器的所有线程,唤醒的所有线程公平竞争资源
9.线程的 run()和 start()有什么区别
系统通过调用线程类的start()方法来启动一个线程,此时该线程处于就绪状态,等待调度,即就是这个线程可以被JVM来调度执行。在调度过程中,JVM底层通过调用线程类的run()方法来完成实际的操作,当run()方法结束后,此线程就会终止。
start()方法能够异步的调用run()方法,但是直接调用run()方法是同步的
10.创建线程池有哪几种方式
(1)newCachedThreadPool():用来处理大量短时间工作任务的线程池
特点:a.缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程
b.线程闲置时间超过60s,则被终止并移除缓存
c.长时间闲置时,这种线程池,不会消耗什么资源
d.其内部使用SynchronousQueue作为工作队列
(2)newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程
特点:a.如果任务数量超过了活动线程数目,将在工作队列中等待空闲线程出现
b.如果工作线程退出,将会有新的工作线程被创建,以补足指定数目nThreads
(3)newSingleThreadExecutor():单线程化的线程池
特点:所有的任务都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免改变线程数目
(4)newSingleThreadScheduledExector()和newScheduledThreadPool(int corePoolSize):创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程
(5)newWorkStealingPool(int parallelism):内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序
11.线程池都有哪些状态
(1)RUNNING:线程池的初始化状态。接收新的任务,处理等待队列中的任务。线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
(2)SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。调用线程池的shutdown()方法时,线程池由RUNNING -> SHUTDOWN
(3)STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用线程池的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN)-> STOP
(4)TIDYING:所有的任务都销毁了,workCount为0,线程池的状态在转换为TIDYING状态时,会执行钩子方法terminated()。因为terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池变成TIDYING时进行相应的处理,可以通过重载terminated()函数来实现
当线程池在SHUTDOWN状态下时,阻塞队列为空并且线程池中执行的任务也为空时,就会由SHUTDOWN -> TIDYING。
当线程池状态在STOP状态下时,线程池中执行的任务为空时,就会由STOP -> TIDYING。
(5)TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由TIDYING -> TERMINATED。
12.线程池中 submit()和 execute()方法有什么区别
(1)submit(Callable
(2)execute(Runnable command)归属于Executor接口。ExecutorService继承了Executor
(3)submit()有返回值
(4)execute()没有返回值
13.java程序中怎么保证多线程的运行安全
(1)线程的安全性问题体现在:
(2)导致原因
(3)解决办法
Happens-Before规则如下
14.多线程锁的升级原理是什么
锁的级别从低到高:
无锁--偏向锁--轻量级锁--重量级锁,这几个状态会随着竞争情况逐渐升级,锁可以升级但不能降级
* 没有优化以前,sychronized是重量级锁(悲观锁),使用wait和notify、notifyAll来切换线程状态非常消耗系统资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能
* 所以为了减少获得和缩放锁带来的性能消耗,JVM对sychronized关键字进行了优化,把锁分为无锁、偏向锁、轻量级锁、重量级锁状态
(1)无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功
(2)偏向锁:偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的
(3)轻量级锁:当锁时偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋(做一点其他的事情休息一下)的形式尝试获取锁,线程不会阻塞,从而提高性能
(4)重量级锁:当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态
* 所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资源,导致性能低下。
* 自旋锁:当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环
自旋锁是非公平的,无法满足等待时间最长的线程优先获取锁
自旋锁是非阻塞的,不会使线程状态发生切换,线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
15.什么是死锁?产生死锁的原因及必要条件
(1)死锁:指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
(2)产生死锁的原因:
a.竞争资源
b.进程间推进顺序非法
(3)死锁产生的4个必要条件
16.怎么防止死锁
(1)预防死锁:
a.以确定的顺序获得锁
如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之间获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:
线程A -------------> 锁住a -------------> 尝试锁住b -------------> 永久等待
线程B -------------> 锁住b -------------> 尝试锁住a -------------> 永久等待
如果此时把获得锁的时序改成如下,那么死锁永远也不会发生
线程A -------------> 锁住a -------------> 尝试锁住b -------------> 永久等待
线程B -------------> 锁住a -------------> 尝试锁住b -------------> 永久等待
b.超时放弃
当使用synchronized关键字提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。
线程A -------------> 锁住a -------------> 尝试锁住b -------------> 超时放弃 -------------> 结束
线程B -------------> 锁住b -------------> 尝试锁住a -------------> 超时放弃 -------------> 结束
(2)避免死锁
(3)检测死锁
(4)解除死锁
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
(5)死锁检测
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面,而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
17.ThreadLocal 是什么?有哪些使用场景
(1)ThreadLocal 是什么
(2)有哪些使用场景
经典场景:为每个线程分配一个JDBC连接的Connection。这样可以保证每个线程都在各自的Connection上进行数据库的操作,不会出现线程A关闭了线程B正在使用的Connection
18.synchronized及synchronized底层实现原理
(1)synchronized:synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
synchronized(this)是对象锁,如果有多个对象就有相对应的多个锁(修饰一个代码块)
synchronized(类的名,class)是全局锁,不管有几个对象都共用一把锁(修饰一个类)
(2)synchronized底层实现原理
a.同步代码块底层实现:通过操作系统的两大指令monitorenter(获取锁)和monitorexit(释放锁、解锁)来回去锁的对象的监视器(monitor)
b.同步方法底层实现
当使用synchronized标记方法时,字节码会出现访问标记(ACC_SYNCHRONIZED),该标记就表示在进入该方法时,JVM需要进行monitorenter操作。在退出该方法时,无论是否正常返回,JVM均需要进行monitorexit操作
c.总结同步的底层实现
无论是同步代码块还是同步方法,其底层实现都是应用了monitor机制,只不过,同步代码块是直接执行monitorenter与moniterexit,而同步方法是设置了一个ACC_SYNCHRONIZED访问标记,这个标记的底层还是monitor机制
(3)monitor机制:可以将monitor理解为每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
(4)synchronized锁的可重入性
(5)synchronized锁的是对象的原因
由于synchronized获取的是当前对象的监视器monitor,类中这么多的同步方法都是属于一个对象的,所以锁的是对象
(6)monitor机制的劣势
对象锁(monitor)机制是JDK1.6之前synchronized底层原理,又称为JDK1.6重量级锁,线程的阻塞以及唤醒均需要操作系统由用户态切换到内核态,开销非常之大,因此效率很低
19.synchronized和volatile的区别
(1)volatile:java提供的一种轻量级的同步机制。java语言包含两种内在的同步机制:同步块/方法和volatile变量,相比于synchronized(重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度,但是volatile变量的同步性较差,而且其使用也更容易出错
(2)作用
(3)区别
20.synchronized 和 lock 有什么区别
(1)来源:synchronized是java的一个关键字,是内置的语言实现;lock是一个接口
(2)异常是否释放锁:synchronized在发生异常时会自动释放占有的锁,因此不会发生死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生(所以最后将同步代码用try catch包起来,finally中写入unlock,避免死锁的发生)
(3)是否响应中断:lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断
(4)是否知道获取锁:lock可以通过tryLock来知道有没有获取锁,而synchronized不能
(5)lock可以提高多个线程进行读操作的效率,可以通过ReadWritelock来实现读写分离
(6)性能:当竞争资源不激烈时,两者性能差不多;当竞争资源非常激烈时(即有大量线程同时竞争),此时lock的性能要远远优于synchronized
(7)synchronized使用Object对象本身的wait、notify、notifyAll调度机制,而lock可以使用Condition进行线程之间的调度
21.synchronized 和 ReentrantLock 区别是什么
(1)相同点:
(2)不同点:ReentrantLock是Lock的实现类,是一个互斥的同步器
22.atomic 的原理
Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
Atomic系列的类中的核心方法都会调用unsafe类中的几个本地方法。这个类包含了大量的对C代码的操作,包括很多直接内存分配以及原子操作的调用,而它之所以标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患。unsafe是java提供的获得对对象内存地址访问的类,它的作用就是在更新操作时提供“比较并替换”。
CAS并发原语现在在java语言中就是sun.misc.Unsafe类的各种方法,调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令,这是一种完全依赖硬件的功能,通过它实现了原子操作,由于CAS是一种系统原语,原语属于操作系统用于范畴,由于诺干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU原子指令,不会造成所谓的数据不一致问题。
atomic原子性就是通过:自旋+CAS(乐观锁)
CAS相对于其他锁,不会进行内核态操作,有一些性能的提神,但同时引入自旋,当锁竞争较大的时候,自旋次数会增多,CPU资源会消耗很高。CAS+自旋适合使用在低并发并有同步数据的应用场景
四、反射
1.什么是反射
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性并且能够改变它的属性;这种动态获取信息以及动态调用对象方法的功能成为java语言的反射机制
2.什么是 java 序列化?什么情况下需要序列化
序列化:将java对象转换成字节流的过程
反序列化:将字节流转换成java对象的过程
当java对象需要在网络上传输或者持久化存储到文件中时,就需要对java对象进行序列化处理
序列化的实现:类实现Serializable接口,这个接口没有需要实现的方法。实现Serializable接口是为了告诉JVM这个类的对象可以被序列化
3.动态代理是什么?有哪些应用?
(1)java中常见的代理有如下几种
(2)静态代理
静态代理,即一个代理对应一个被代理对象
静态代理的实现模式:首先创建一个接口(JDK代理都是面向接口的),然后创建具体实现类来实现该接口,再创建一个代理类同样实现这个接口,不同之处在于:具体实现类的方法中需要将接口中定义的方法业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层
public interface Iuser {
void eat(String s);
}
public class UserImpl implements Iuser {
@Override
public void eat(String s) {
System.out.println("我要吃"+s);
}
}
public class UserProxy implements Iuser {
private Iuser user = new UserImpl();
@Override
public void eat(String s) {
System.out.println("静态代理前置");
user.eat(s);
System.out.println("静态代理前后置");
}
}
public class ProxyTest {
public static void main(String[] args){
UserProxy proxy = new UserProxy();
proxy.eat("苹果");
}
}
静态代理的缺点
a.当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式
b.当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护
(2)动态代理
动态代理:在程序运行期间,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强。
代理类在程序运行期间,创建的代理对象称之为动态代理对象。这种情况下,创建的代理对象,并不是实现在Java代码中定义好的,而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的。动态代理可以在不修改方法源码的情况下,增强被代理对象的方法的功能。
(3)JDK动态代理
JDK动态代理是由Java内部的反射机制来实现的,目标类基于统一的接口(InvocationHandler)
(4)CGLib动态代理
CGLib动态代理底层则是借助asm来实现的,CGLib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势
(5)具体代码实现
public interface UserService {
public String getName(int id);
public Integer getAge(int id);
}
public class UserServiceImpl implements UserService {
@Override
public String getName(int id) {
System.out.println("------getName-----");
return "Marry";
}
@Override
public Integer getAge(int id) {
System.out.println("------getAge-----");
return 24;
}
}
public class JDKProxy implements InvocationHandler {
private Object target;
/**
* 绑定委托对象并返回一个代理类
* @param target
* @return
*/
public Object bind(Object target){
this.target = target;
// 取得代理对象
// 要绑定接口,这是一个缺陷,cglib弥补了这个缺陷
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("getName".equals(method.getName())){
System.out.println("------before"+method.getName()+"------");
Object result = method.invoke(target,args);
System.out.println("------after"+method.getName()+"------");
return result;
} else {
Object result = method.invoke(target,args);
return result;
}
}
}
public class JDKProxyTest {
public static void main(String[] args){
JDKProxy proxy = new JDKProxy();
UserService userService = (UserService) proxy.bind(new UserServiceImpl());
System.out.println(userService.getName(1));
System.out.println(userService.getAge(1));
}
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBProxy implements MethodInterceptor {
private Object target;
/**
* 创建代理对象
* @param target
* @return
* @throws Throwable
*/
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("+++++++++before"+methodProxy.getSuperName()+"++++++++");
System.out.println(method.getName());
Object result = methodProxy.invokeSuper(o,objects);
System.out.println("+++++++++after"+methodProxy.getSuperName()+"++++++++");
return result;
}
}
public class CGLIBProxyTest {
public static void main(String[] args){
CGLIBProxy cglibProxy = new CGLIBProxy();
UserService userService = (UserService) cglibProxy.getInstance(new UserServiceImpl());
userService.getName(1);
userService.getAge(1);
}
}
(3) 动态代理的应用场景
(4)动态代理优点
代理逻辑与业务逻辑是互相独立的,没有耦合
五、对象拷贝
1.为什么要使用克隆
当想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆。
深克隆的方式有两种:
a.使用Cloneable接口
b.使用序列化:序列化深拷贝效率较低
例1:浅克隆:对象仅包含基本变量
public class StudentSimple implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone(){
StudentSimple stu = null;
try {
stu = (StudentSimple)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return stu;
}
}
public class SimpleCloneTest {
public static void main(String[] args){
StudentSimple stu1 = new StudentSimple();
stu1.setNumber(123456);
StudentSimple stu2 = (StudentSimple)stu1.clone();
System.out.println("学生1:"+stu1.getNumber());
System.out.println("学生1:"+stu2.getNumber());
stu2.setNumber(654321);
System.out.println("学生1:"+stu1.getNumber());
System.out.println("学生1:"+stu2.getNumber());
System.out.println(stu1==stu2);
}
}
例2:浅克隆:对象包含引用变量
public class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
public class StudentSimple implements Cloneable{
private int number;
private Address address;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public Object clone(){
StudentSimple stu = null;
try {
stu = (StudentSimple)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return stu;
}
}
public class SimpleCloneTest {
public static void main(String[] args){
Address address = new Address();
address.setAdd("武汉市");
StudentSimple stu1 = new StudentSimple();
stu1.setNumber(123456);
stu1.setAddress(address);
StudentSimple stu2 = (StudentSimple)stu1.clone();
System.out.println("学生1:"+stu1.getNumber()+",地址:"+stu1.getAddress().getAdd());
System.out.println("学生1:"+stu2.getNumber()+",地址:"+stu2.getAddress().getAdd());
stu2.setNumber(654321);
address.setAdd("黄石市");
System.out.println("学生1:"+stu1.getNumber()+",地址:"+stu1.getAddress().getAdd());
System.out.println("学生1:"+stu2.getNumber()+",地址:"+stu2.getAddress().getAdd());
System.out.println(stu1==stu2);
}
}
例3:深克隆:克隆对象包含引用变量
public class Address implements Cloneable{
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone(){
Address address = null;
try {
address = (Address)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return address;
}
}
public class StudentSimple implements Cloneable{
private int number;
private Address address;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public Object clone(){
StudentSimple stu = null;
try {
// 浅复制
stu = (StudentSimple)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
stu.address = (Address) address.clone();
return stu;
}
}
public class SimpleCloneTest {
public static void main(String[] args){
Address address = new Address();
address.setAdd("武汉市");
StudentSimple stu1 = new StudentSimple();
stu1.setNumber(123456);
stu1.setAddress(address);
StudentSimple stu2 = (StudentSimple)stu1.clone();
System.out.println("学生1:"+stu1.getNumber()+",地址:"+stu1.getAddress().getAdd());
System.out.println("学生1:"+stu2.getNumber()+",地址:"+stu2.getAddress().getAdd());
stu2.setNumber(654321);
address.setAdd("黄石市");
System.out.println("学生1:"+stu1.getNumber()+",地址:"+stu1.getAddress().getAdd());
System.out.println("学生1:"+stu2.getNumber()+",地址:"+stu2.getAddress().getAdd());
System.out.println(stu1==stu2);
}
}
例4:使用Serializable实现深拷贝
public class CloneUtil {
private CloneUtil(){
throw new AssertionError();
}
public static T clone(T obj) throws Exception{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
}
}
public class Car implements Serializable {
private static final long serilaVersionUID = -5713945027627603702L;
private String brand;
private int maxSpeed;
public Car(String brand, int maxSpeed){
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
@Override
public String toString(){
return "Car [brand="+brand+", maxSpedd="+maxSpeed+"]";
}
}
public class Person implements Serializable {
private static final long serilaVersionUID = -9102017020286042305L;
private String name;
private int age;
private Car car;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
@Override
public String toString(){
return "Person [name="+name+", age"+age+", car="+car+"]";
}
}
public class SerialCloneTest {
public static void main(String[] args) {
try{
Person p1 = new Person("张三", 30, new Car("Benz", 300));
// 深度克隆
Person p2 = CloneUtil.clone(p1);
p2.getCar().setBrand("BYD");
System.out.println(p1);
System.out.println(p2);
}catch (Exception e){
e.printStackTrace();
}
}
}
六、Java Web
1.session 和 cookie 的区别
2.session的工作原理
客户端登录完成之后,服务器会创建对应的session,session创建完成之后,会把sessionid发送给客户端,客户端再存储到浏览器中,这样客户端每次访问服务器时,都会带着sessionid,服务器拿到sessionid之后,在内存找到与之对应的session,这样就可以正常工作
3.客户端禁止cookie,session还能用吗
一般默认情况下,在会话中,服务器存储session的sessionid是通过cookie存到浏览器里。如果浏览器禁用了cookie,浏览器请求服务器无法携带sessionid,服务器无法识别请求中的用户身份,session失效。
但是可以通过其他方法在禁用cookie的情况下,可以继续使用session:
4.如何避免sql注入
5.什么是XSS攻击?如何避免?
(1)XSS攻击:即跨站脚本攻击,它是Web程序中常见的漏洞。原理是攻击者往Web页面里插入恶意的脚本代码(css代码、Javascript代码等),当用户浏览该页面时,嵌入其中的脚本代码会被执行,从而达到恶意攻击用户的目的,如盗取用户cookie、破坏页面结构、重定向到其他网站等
(2)XSS防御的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码
a.对输入和URL参数进行过滤(白名单和黑名单)
将容易导致XSS攻击的边角字符替换成全角字符;对于每一个输入,在客户端和服务器端还要进行各种验证,验证是否合法字符,长度是否合法,格式是否正确。
b.对输出进行编码
在输出数据之前对潜在的威胁的字符进行编码、转义是防御XSS攻击十分有效的措施
6.什么是CSRF攻击?如何避免?
(1)CSRF攻击:即跨站请求攻击,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品等)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
(2)预防措施
a.检查Referer字段
HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。通常来说Referer字段应和请求的地址位于同一域名下,服务器可识别恶意的访问。
这种办法简单易行,但是有局限性,因其完全依赖浏览器发送正确的Referer字段,无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段,并且也存在攻击者攻击某些浏览器,篡改其Referer字段的可能
b.添加token校验
token最大特别就是随机性,不可预测
七、异常
1.throw和throws的区别
a.throws:跟在方法声明后面,后面跟的是异常类名
throw:用在方法体内,后面跟的是异常类对象名
public static void method() throws ArithmeticException {
int a = 10;
int b = 0;
if (b == 0) {
throw new ArithmeticException();
} else {
System.out.println(a/b);
}
}
b.throws:可以跟多个异常类名,用逗号隔开
throw:只能抛出一个异常对象名
c.throws:表示抛出异常,由该方法的调用者来处理
throw:表示抛出异常,由该方法体内的语句来处理
d.throws:表示有出现异常的可能性,并不一定出现这些异常
throw:抛出了异常,执行throw一定出现了某种异常
2.final,finally,finalize的区别
(1)final:用于声明属性、方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承
(2)finally:异常处理语句结果的一部分,finally结构使代码总会被执行,而不管无异常发生。使用finally可以维护对象的内部状态,并可以清理非内存资源。特别是在关闭数据库连接这方面,将数据库连接的close()方法放到finally中,会大大降低程序出错的几率
finally语句块是在程序退出方法之前被执行的(先于return语句执行);finally语句块是在循环被跳过(continue)和中断(break)之前被执行的
(3)finalize:是一个方法,属于java.lang.Object类,finalize()方法是GC(garbagecollector)运行机制的一部分,finalize()方法是在GC清理它所从属的对象时被调用的,如果执行它的过程中抛出了无法捕获的异常(uncaughtexception,GC将终止对该对象的清理,并且该异常会被忽略;直到下一次GC开始清理这个对象时,它的finalize()会被再次调用
3.常见异常类有哪些
八、网络
1.http响应码301和302代表的是什么?有什么区别?
2.forword和redirect的区别
forword和redirect是servlet的两种主要的跳转方式。forword又叫转发,redirect叫做重定向
a.从地址栏显示来说
forword是服务器内部的重定向,服务器直接访问目标地址的url网址,把里面的东西读取出来,但是客户端并不知道,因此用forword的话,客户端浏览器的网址是不会发生变化的
redirect:是服务器根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,所以地址栏显示的是新的地址
b.从数据共享来说
由于在整个定向的过程中用的是同一个request,因此forword会将request信息带到被重定向的jsp或servlet中使用,即可以共享数据
redirect不能共享
c.从运用的地方来说
forword一般用户用户登录的时候,根据角色转发到相应的模块
redirect一般用于用户注销登录时返回主页面或者跳转到其它网站
d.从效率来说
forword效率高,而redirect效率低
e.从本质来说
forword转发是服务器上的行为,而redirect重定向是客户端的行为
f.从请求的次数来说
forword只有一次请求,而redirect有两次请求
3.简述TCP和UDP的区别以及优缺点
TCP是面向连接的通讯协议,通过三次握手建立连接,通讯完成时四次挥手
优点:TCP在数据传输过程中,有保证数据可靠传输的机制,较为可靠
缺点:TCP相对于UDP传输速度慢,要求系统资源较多
UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息
优点:UDP速度快、操作简单、要求系统资源较少,由于通讯不需要连接,可以实现广播发送
缺点:UDP传输数据前不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,没有保证数据可靠传输的机制
4.TCP为什么要三次握手?
第一次握手
客户端像服务器发送链接请求报文段。该报文段的头部中SYN=1,ACK=0,seq=x。请求发送后,客户端便进入SYN-SENT状态
第二次握手
服务端收到连接请求报文段后,如果同意连接,则会发送一个应答:SYN=1,ACK=1,seq=y,ack=x+1。该应答发送完成后便进入SYN-RCVD状态
第三次握手
当客户端收到连接同意的应答后,还要向服务端发送一个确认报文段,表示:服务端发送来的连接同意应答已经成功收到。该报文段的头部为:ACK=1,seq=x+1,ack=y+1.
客户端发送完这个报文段后便进入ESTABLISHED状态。服务端收到这个应答后也进入ESTABLISHED状态,此时连接的建立完成
(2)为什么连接建立需要三次握手,而不是两次握手?
防止失效的连接请求报文段被服务端接收,从而产生错误
(3)TCP四次挥手:TCP连接的释放
第一次挥手
若A认为数据发送完成,则它需要向B发送连接释放请求。该请求只有报文头,头中携带的主要参数为:FIN=1,seq=u。此时,A将进入FIN-WAIT-1状态
第二次挥手
B收到连接释放请求后,会通知相应的应用程序,告诉它A向B这个方向的连接已经释放,此时B进入CLOSE-WAIT状态,并向A发送连接释放的应答,其报文头包含:
ACK=1,seq=v,ack=u+1
A收到该应答,进入FIN-WAIT-2状态,等待B发送释放连接请求
第二次挥手完成后,A到B方向的连接已经释放,B不会再接收数据,A也不会再发送数据,但是B到A方向的连接仍然存在,B可以继续向A发送数据
第三次挥手
当B向A发送完所有数据,向A发送连接释放请求,请求头:FIN=1,ACK=1,seq=w,ack=u+1。B便进入LAST-ACK状态
第四次挥手
A收到释放请求后,向B发送确认应答,此时A进入TIME-WAIT状态。该状态会持续2MSL时间,若该时间段内没有B的重发请求的话,就进入CLOSED状态,当B收到确认应答后,也便进入CLOSED状态,撤销TCB
5.get和post请求有哪些区别
6.如何实现跨域
跨域:当浏览器执行脚本时会检查是否同源,只有同源的脚本才会执行,如果不同源即为跨域。
如何实现跨域请求
a.jsonp
利用了