0
点赞
收藏
分享

微信扫一扫

Java面试总纲

四月Ren间 2022-03-16 阅读 69

面试

文章目录


自我介绍


At first, Thank you so much for giving me this opportunity for this interview.

My name is XXX, and you can call me Alex Leon which is my English name.

I graduated from Shanghai Maritime University with bachelor’s degree at 2016, I have worked for two companies, have been engaged in Java development for about five years

I passed CET4 during my college years,and I got Java Software Development Special Skill Certificate at 2019 , which is issued by the (MIIT)Ministry of industry and information technology, Now I am studying and preparing for the exam of software designer.

I have good foundation and coding practice of Java, and also I know the skills of Groovy, Mysql and Oracle, Im familiar with popular framework such as Spring, SpringBoot, SpringMVC, SpringCloud, Mybatis, and Grails, and I often use the tools like Kafka, RabbitMq, Redis and Nginx

The latest project I participated is SPDB(Shanghai Pudong Development Bank) ecosystem marketing project, with framework springBoot, springCloud and grails. it is distributed and microserviced.

Im good at learning new technologies, I love coding, I love programming, and I always keep a good self-drive for learning.

CitiBank is a large and international company, and my best friend work in Citibank who recommand me for this interview, on the other hand, I have similar project experience of bank, so I really hope to join Citibank

Thank you so much


各位面试官好, 我叫XXX,上海海事大学毕业, 毕业之后我一直在上海发展,一共呆过两家公司,从事java开发工作4年多接近5年。

我的大概情况是: 17年正式参加工作,前两年从事企业传统项目,主要是ERP\CRM这些生产管理系统,用到的技术点主要是传统的单体框架,SSM框架,数据库是mysql。
后来这两年参与浦发银行生态圈项目,涉及分布式和微服务的架构

我近期参与的项目是浦发银行生态圈营销系统
主要功能是为浦发银行所有生态产品,比如手机银行app、浦惠到家app、浦慧app、甜橘app等,为这些产品提供制券和活动页面配置的管理端系统,以及这些h5活动页面的运行时服务支持
采用微服务分布式架构,开发语言采用的是groovy, 框架使用的是grails框架,包管理工具使用的是gradle, 同时集成了springCloud的相关组件

角色情况,前期作为初级开发工程师,主要以开发功能模块为主,近一年作为开发组长, 带领8个人的小团队,也参与到需求分析评估、项目流程管理,包括ci cd发布流程、以及代码审核的工作。

平时我喜欢看一些源码,会去github或者gitee逛一些开源项目, 也租了服务器购买和备案域名并搭建了一些个人项目,比如主页,在线简历,浏览器搜索页等

19年参加了工信部人才激励计划的java开发工程师的考试并通过, 21年去年我参加了国家软考软件设计师考试, 下午题目考了62发挥不错,上午题目就差5分就能通过
所以今年第一个目标就是换个工作,第二个目标就是能通过软考


基础


int类型的取值范围

-2^31 ~ 2^31 - 1

String底层为什么是final修饰的

  • 1.为了实现字符串池

只有字符串是不可变的,字符串池才有可能实现。不同的字符串变量可以指向池中的同一个字符串,节省heap空间。但如果字符串是可变的,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。

  • 2.为了线程安全

只有字符串是不可变的,多线程才安全,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步,字符串本身就是线程安全的。

  • 3.为了实现String可以创建HashCode不可变性

只有字符串是不可变的,则在它创建的时候HashCode就可以被允许缓存,并且不会在每次调用 String 的 hashcode 方法时重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

  • 4.为了系统安全

而且String类中的很多方法的实现不是Java代码,而是调用操作系统的本地方法来完成的,如果String类不被final修饰,被继承重写方法的话,系统会很不安全。

虽然final修饰代表了不可变,但仅仅是引用地址不可变,并不代表了数组本身不会变

final关键字

final关键字可以修饰类,方法和变量:

  • 被final修饰的类不能被继承,即它不能拥有自己的子类;
  • 被final修饰的方法不能被重写;
  • final修饰的变量,无论是类变量、实例变量、参数变量(形参)还是局部变量,都需要进行初始化操作。

面向对象

重视对象思维,关注每个对象需要做什么,而不是关注过程和步骤

  1. 封装:

    明确标识出允许外部使用的所有成员函数和数据项

    内部细节对外部调用者透明,外部调用无需修改或者关心内部实现的细节

  2. 继承

    继承基类的方法,并作出自己的改变和扩展

    子类共性的方法或者属性(抽取出来)直接使用继承的父类的,不需要自己再定义,只需扩展自己个性化的

  3. 多态

    基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同,使得程序更易扩展

    多态有个条件就是继承,多态和继承是一脉相承的

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

使用引用变量调用的方法实际上是子类重写的方法,而不是父类的

弊端:多态调用方法不能是子类特有/独有的方法,因为能调用的方法必须是重写父类的方法,所以父类中没有的方法不能调用。

向上转型和向下转型

所以我们常说的向上转型,其实就是多态,即父类引用指向子类对象,此时调用方法实际上使用的是子类的实现, 而子类独有的方法是无法调用的

但如果将该变量强制类型转换成子类(向下转型)后,就可以使用子类特有的方法

Java自动类型转换

  1. 两种类型是彼此兼容的
  2. 转换的目的类型占得空间范围一定要大于转化的源类型

正向过程:由低字节向高字节自动转换

byte->short->int->long->float->double

逆向过程:使用强制转换,可能丢失精度。

int a=(int)3.14;

Java数据类型自动提升(注意以下讨论的是二元操作符)

Java定义了若干使用于表达式的类型提升规则:

  1. 所有的byte型. short型和char型运算后将被提升到int型(例外: final修饰的short, char变量相加后不会被自动提升。)
  2. 如果一个操作数是long形 计算结果就是long型;
  3. 如果一个操作数是float型,计算结果就是float型;
  4. 如果一个操作数是double型,计算结果就是double型;

另一种归纳方式(《Java核心技术卷I》P43):

  1. 如果两个操作数其中有一个是double类型,另一个操作就会转换为double类型。
  2. 否则,如果其中一个操作数是float类型,另一个将会转换为float类型。
  3. 否则,如果其中一个操作数是long类型,另一个会转换为long类型。
  4. 否则,两个操作数都转换为int类型。

静态代码块,构造代码块和构造函数的执行顺序

静态代码块:最早执行,类被载入内存时执行,只执行一次。没有名字、参数和返回值,有关键字static。

构造代码块:执行时间比静态代码块晚,比构造函数早,和构造函数一样,只在对象初始化的时候运行。没有名字、参数和返回值。

构造函数:执行时间比构造代码块时间晚,也是在对象初始化的时候运行。没有返回值,构造函数名称和类名一致。

注意:静态代码块在类加载的时候就执行,所以的它优先级高于main()方法。

下面我们看一下有继承时的情况:

public class Parent {
 
    public Parent() {
	System.out.println("Parent的构造方法");
    }
 
    static {
	System.out.println("Parent的静态代码块");
    }
 
    {
    	System.out.println("Parent的构造代码块");
    }
}
 
public class Son extends Parent {
    public Son() {
	System.out.println("Son的构造方法");
    }
 
    static {
	System.out.println("Son的静态代码块");
    }
 
    {
	System.out.println("Son的构造代码块");
    }
 
    public static void main(String[] args) {
	System.out.println("main方法");
	new Son();
    }
}
Parent的静态代码块
Son的静态代码块
main方法
Parent的构造代码块
Parent的构造方法
Son的构造代码块
Son的构造方法

可以看出:父类始终先调用(继承先调用父类),并且这三者之间的相对顺序始终保持不变。

到此貌似没什么问题,但是请看如下变形:

public class B {
    public static B t1 = new B();
    public static B t2 = new B();
 
    {
	System.out.println("构造代码块");
    }
 
    public B() {
	System.out.println("构造函数");
    }
 
    static {
	System.out.println("静态代码块");
    }
 
    public static B t3 = new B();
 
    public static void main(String[] args) {
	new B();
    }
}
构造代码块
构造函数
构造代码块
构造函数
静态代码块
构造代码块
构造函数
构造代码块
构造函数

因为b1、b2、b3用static修饰,与静态块处于同一优先级,同一优先级就按先后顺序来执行。

反射

在运行时动态获取调用或修改类信息,属性,方法。

Java中利用反射获取对象的方式有:

  • a)类名.class,不会加执行态代码块
Class<Object> c1 =Object.class
  • b)Class.forName(“包名.类名”) ,会执行静态代码块
Class<?> c2 = Class.forName("java.lang.Object");
  • c)类的实例对象.getClass(),会执行静态代码块
Class<?> c3 = new Object().getClass();
  • d)Class.forName(“包名.类名”, boolean,loader)
Class<?> c4 = Class.forName("com.java.oop.ClassA", false, ClassLoader.getSystemClassLoader());
  • e)类加载器.load(“包名+类名”) 不会执行静态代码块
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> c5 = loader.loadClass("com.java.oop.ClassA");//不会执行静态代码块。

异常

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2015_异常.png


集合

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2016_%E9%9B%86%E5%90%88.png

ArrayList动态数组扩容机制

在JDK1.8中,如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时(即添加第一个元素时),才真正分配容量,默认分配容量为10;

当容量不足时(容量为size,添加第size+1个元素时),先判断按照1.5倍(位运算)的比例扩容能否满足最低容量要求,若能,则以1.5倍扩容,否则以最低容量要求进行扩容。

执行add(E e)方法时,先判断ArrayList当前容量是否满足size+1的容量;在判断是否满足size+1的容量时,先判断ArrayList是否为空,若为空,则先初始化ArrayList初始容量为10,再判断初始容量是否满足最低容量要求;若不为空,则直接判断当前容量是否满足最低容量要求;若满足最低容量要求,则直接添加;若不满足,则先扩容,再添加。

ArrayList的最大容量为Integer.MAX_VALUE

ArrayList扩容的例子:ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。

假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10 (如下图一);之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15(如下图二);当添加第16个数据时,继续扩容变为15 * 1.5 =22个。

HashMap的结构

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2010_HashMap.png

HashMap是数组➕单向链表的数据结构

数组中保存的不是key value, 严格意义上讲保存的是一个Node实现了Map.Entry接口

我可以围绕它源码的三个主要方法来讲一下
put() get()和resize()

put()方法时, 先对key进行hashCode(), 得到的值再去与数组容量进行与操作,得到一个哈希值

这步操作是为了使得key的哈希值都在数组下标范围内,定位到数组下标的bucket

当这个bucket为空时,直接将这个node放进去,所以多线程下线程不安全,多条线程同时判断到bucket为空,同时放入node导致有些数据没了,解决办法有Collections.Sychronized,或使用concurrentHashMap

当这个bucket中已经有值,说明存在hash冲突,此时遍历链表对比key.equals()

如果链表中已有则覆盖oldValue,如果没有则在链表的尾部(尾插法)进行add,1.8之前是头插法,重新赋值第一个节点然后指向前一个节点,多线程情况下可能导致next节点永不为空从而造成死链

当链表长度大于8时,会转换成红黑树,利用红黑树的左旋右旋来提高效率,当小于6时又会转换成链表

get()方法时,先对key哈希,找到数组的bucket,然后遍历链表查询key.equals()是否存在

resize()方法,数组长度默认起始是16,默认负载因子为0.75f,所以当数组大小超过16*0.75=12时,会对数组进行双倍扩容

hashTable, hashMap, concerrentHashMap

hashtable中不能有null key或者value, hashmap中允许

Hashtable中使用了sycronize同步,效率较低,虽然多线程中相对安全,但也不常使用

因为可以使用Collections.sycronized去实现

或者直接使用concurrentHashMap, 它在hashMap的基础上外层多维护了一个segment

是分段进行加锁的,所以多线程时安全又提高了效率

concurrentHashMap中通过自旋锁和CAS确保不同线程获取到的是同一个segment对象

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2011_ConcurrentHashMap.jpeg

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2012_ConcurrentHashMap.png


JVM

主要实现了Java的跨系统,不同系统由JVM编译处理成不同的机器码,所以不同的系统对应的JVM版本也不同

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2007_JVM.png

主要分为 类装载子系统、字节码执行引擎、运行时数据区

类装载子系统用于加载字节码

字节码执行引擎主要有三个作用

  • 执行字节码

  • 修改程序计数器

  • 创建和管理垃圾回收线程

    最重要的是运行时数据区,主要分为线程公有区和线程私有区

    线程公有区包含堆和方法区(元数据区)

  • 堆是存放对象的

  • 方法区用来存放常量、静态变量、类元信息

    线程私有区包含线程栈、本地方法栈、程序计数器

  • 当程序执行到native关键字修饰的本地方法的时候,会由本地方法栈分配空间

  • 程序计数器用于记录当前字节码执行到的位置,因为线程是交替获取cpu资源进行执行的,需要知道该从哪里执行

  • 线程栈中包含多个栈帧,每个线程分配一个线程栈,而线程中的方法又会分配不同的栈帧

    • 局部变量表:存放方法的局部变量
    • 操作数栈:为加减乘除等运算提供内存空间,变量先复制到操作数栈,运算完再将结果赋给相应变量. 所以i=i++还是等于原来的值, 是因为jvm先执行了压栈,将i压入操作数栈顶,然后执行自增操作,这个时候栈顶的i为1,本地变量i为2,但是又将栈顶的i弹出操作数栈并赋值给本地变量i了,所以本地变量i最终不变
    • 动态链接:我们调用一个方法的时候,那些方法名称包括括号实际上是"符号",比如math.compute(), "compute()"实际上是一种符号,动态链接就是根据这些东西去找到它的内存地址,从而找到对应的代码
    • 方法出口:一个方法执行完后,需要找到调用他的上一个方法中的位置好继续执行

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2008_JVM.png

当伊甸园区满了,会触发minor gc, minor gc会回收整个年轻代

幸存的对象会从伊甸园区 移动到 其中一个幸存区s0, 当再次触发minor gc, 幸存对象又会被挪到另一个空的幸存区s1, 然后s0会被清空

所以当一个对象如果一直幸存,它会在幸存区 s0 和 s1 之间反复横跳

每经历一次gc,对象的分代年龄会加1, 当加到15, 这个对象会被移动到老年代

如果幸存对象在幸存区放不下,gc后也会被直接放到老年代

当老年代放满之后,jvm会再开启一个垃圾回收线程,专门进行full gc, full gc会将年轻代和老年代都回收

当full gc之后还是没法腾出足够空间,就会内存溢出OOM, OutOfMemeryException

可达性分析

GCRoot根结点:线程栈的本地变量、静态变量、本地方法栈的变量。

将GCRoot作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余为标记的对象都是垃圾对象。

双亲委派模型

其实就是一种类加载器的层次关系

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2009_JVM.png


多线程

线程池

通过ThreadPoolExecutor类,可以构造出各种需求的线程池。底层是通过workQueue实现

实际应用中直接用静态类Executor

  • Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
  • Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池
  • Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池

线程死锁

多个线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放。

由于线程被无限期地阻塞,因此程序不可能正常终止,最终导致死锁产生。

学过操作系统的朋友都知道,产生死锁必须具备以下四个条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用;
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源持有不释放;
  3. 不剥夺条件:线程已获得的资源,在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源;
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

同理,只要任意破坏产生死锁的四个条件中的其中一个就可以了:

  1. 破坏互斥条件

    该条件没有办法破坏,因为用锁的意义本来就是想让他们互斥的(临界资源需要互斥访问)

  2. 破坏请求与保持条件

    一次性申请所有的资源;

  3. 破坏不剥夺条件

    占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源;

  4. 破坏循环等待条件

    靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。

wait()和notify()

Object.wait() -挂起一个线程

Object.notify() -唤醒一个线程

唤醒是根据线程优先级来选择的

class Source {
public int count = 0;
public boolean flag = false; // 是否有数据
}

class Producer implements Runnable {

    private Source source;

    public Producer(Source source) {
        this.source = source;
    }

    @Override
    public void run() {
        while (true) {
            synchronized(source) {
                if(!source.flag) {
                    source.count++;
                    source.flag = true;
                    System.out.println("生产商品:"+source.count);
                    source.notify(); // 唤醒另一线程
                } else {
                    try {
                        source.wait(); // 等前线程等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

class Comsumer implements Runnable {
private Source source;

    public Comsumer(Source source) {
        this.source = source;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (source) {
                if(source.flag) {
                    source.flag = false;
                    System.out.println("消费商品:"+source.count);
                    source.notify(); // 唤醒另一线程
                } else {
                    try {
                        source.wait();  // 等前线程等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

public class MQTest {
public static void main(String[] args) {
Source source = new Source();
Producer producer = new Producer(source);
Comsumer comsumer = new Comsumer(source);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(comsumer);
t1.start();
t2.start();
}
}

wait()和sleep()区别

  1. wait()来自Object类,sleep()来自Thread类
  2. 调用 sleep()方法,线程不会释放对象锁。而调用 wait() 方法线程会释放对象锁;
  3. sleep()睡眠后不出让系统资源,wait()让其他线程可以占用 CPU;
  4. sleep(millionseconds)需要指定一个睡眠时间,时间一到会自然唤醒。而wait()需要配合notify()或者notifyAll()使用

sychronized关键字

  • 原子性

synchronized经过编译之后,对应的是class文件中的monitorenter和monitorexit这两个字节码指令。

这两个字节码对应的内存模型的操作是lock(上锁)和unlock(解锁)。因为这两个操作之间运行的都是原子的(这个操作保证了变量为一个线程独占的,也就是说只有获得锁的线程才能够操作被锁定的内存区域),所以synchronized也具有原子性。

这两个字节码都需要一个对象来作为锁。因此,

  1. 如果synchronized修饰的是实例方法,则会传入this作为参数(相当于对调用方法的对象加锁)
  2. 如果修饰的是静态方法,则会传入class类对象作为参数。
  3. 如果只是一个同步块,那么锁就是括号里配置的对象

执行monitorenter字节码时, 如果这个对象没有被上锁,或者当前线程已经持有了该锁,那么锁的计数器会+1,

而在执行monitorexit字节码时,锁的计数器会-1,当计数器为0时,锁被释放。

如果获取对象的锁失败,那么该线程会被阻塞等待,直到之前把这个对象上锁的线程释放这个锁为止。

每个对象都有一个monitor(监视器)与之关联,所谓的上锁,就是获得对象的monitor的独占权(因为只用获得monitor才能访问这个对象)

执行monitorenter字节码的时候,线程就会尝试获得monitor的所有权,也就是尝试获得对象的锁

只有获得了monitor,才能进入同步块,或者执行同步方法

独占对象的本质是独占对象的monitor

  • 可见性

此外,synchronized也具有可见性,因为它调用的unlock解锁这个操作规定,放开对某个变量的锁的之前,需要把这个变量从缓存更新到主内存,因此它也具有可见性

  • 有序性

为什么synchronized无法禁止指令重排,却能保证有序性??

因为在一个线程内部,他不管怎么指令重排,他都是as if serial的

也就是说单线程即使重排序之后的运行结果和串行运行的结果是一样的,是类似串行的语义。

而当线程运行到同步块时,会加锁,其他线程无法获得锁,也就是说此时同步块内的方法是单线程的

根据as if serial,可以认为他是有序的

而指令重排序导致线程不安全是多线程运行的时候,不是单线程运行的时候

因此多线程运行时禁止指令重排序也可以实现有序性,这就是volatile。

volitle关键字

保证了变量在多线程中的可见性、有序性、但不保证原子性

  • 可见性

Volitle修饰的变量,在一个线程中被改变,会立刻通知总线,并通知其他线程

实现原理:如果使用这个修饰符,对该变量进行写操作之后,会立即执行store和write操作(对应的汇编代码中会加上一个lock前缀),立即将该变量从工作内存(或者说缓存)写入主内存,保证了对别的线程立即可见(因为这会导致别的线程的工作内存中该变量的缓存会失效),并且同时其他的cpu的工作内存中的值无效,直接从主内存读取并刷新工作内存。

  • 有序性

第二个特征是禁止指令重排序优化,也就是保证volatile修饰的变量不会被指令重排序优化,从而保证代码的执行顺序和程序顺序相同,保证了有序性。

实现是当变量被声明为volatile时,通过在生成的字节码中插入“内存屏障”,来禁止特定类型的指令重排序(定义了很多情况下禁止指令重排序)。

举个例子:每个volatile变量在写操作之前会有一个“写写屏障”,这表示这个写操作之前的写操作和它禁止重排序,后面会有一个“写读屏障”,这表示这个写操作和后面的读操作不能重排序。

  • 原子性不满足

但volatile关键字不保证对变量操作的原子性(synchronized可以保证原子性)

比如i++操作,这不是一个原子操作,它包括四个字节码指令,首先把i放到操作数栈栈顶,然后把int类型1放到栈顶,两个出栈相加,再入栈;而如果相加之前别线程修改了i的值,栈顶的i就是过期的,会发生错误。因此线程不安全。

也因此,使用volatile而不会引起线程不安全的前提是:1、对该变量的运算不依赖于该变量的值,或者只有一个线程能修改该变量的值。2、变量不需要与其他状态变量共同参与不变约束。

ThreadLocal关键字

使用ThreadLocal声明的变量

ThreadLocal num = new ThreadLocal<Integer>(1);

会在线程中维护一个map, key是这个ThreadLocal对象,value是所真正保存的值

可以保证当前线程获取的这个变量值一定是它自己的,不会获取到其他线程的

但是有个弊端,如果使用线程池的话, 线程处理完业务后并不会被销毁,而是放到线程池中会被再取出用于其他业务处理。虽然数据上不会取到之前的,但是之前的那个ThreadLocal对象一直在被引用没有被销毁掉,可能导致oom, 解决办法处理完业务后,最后要将ThreadLocal修饰的变量手动清除引用,比如赋值为null

Lock

csdn


框架

JDBC

Jdbc的连接过程:

  • 注册驱动,将driver注册到driverManager中
  • 获取链接,driverManager根据url获取数据库连接
  • 得到执行sql的statement(提供了executeQuery,excuteUpdate,execute)
  • 执行sql语句
  • 处理结果集
  • 关闭连接

Mybatis

Mybatis主要是封装了jdbc,对外提供了api接口,对内提供了接口的实现, 不同的厂商都基于这个接口规范,比如mysql,oracle,db2等驱动。

整体流程是这样的,xmlConfigBuilder将mybatis-config.xml中的配置信息封装成一个environment, xmlMapperBuilder将mapper.xml中的sql封装成mappedStatement这两个共同组成了configuration, sqlSessionFactionBuilder基于configuration创建sqlSessionFactory然后创建sqlSession

sqlSession中引用了executor,引用了statementHandler, paramterHandler, resultSetHandler, 执行语句注入参数处理结果集

%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2001
%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2002

Servlet

servlet是一个java程序,面向请求和响应,生成动态的web

Servlet的框架是由两个Java包组成的:javax.servlet与javax.servlet.http

  • Web Client 客户端向Servlet容器(Tomcat)发出Http请求;
  • 接收到之后,找到项目名称和servlet名称,找到class文件的完整路径,基于反射创建HttpServlet对象
  • 同时创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中; 创建一个HttpResponse对象;
  • Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet对象;
  • HttpServlet调用HttpRequest对象的有关方法(doGet/doPost),获取Http请求信息;HttpServlet调用HttpResponse对象的有关方法,生成响应数据;
  • Servlet容器把HttpServlet的响应结果传给Web Client;

SpringMVC

springMVC是以请求为驱动,围绕servlet设计

其核心是dispatcherServlet,它是一个Servlet

  • (1) 客户端(浏览器)发送请求url,直接请求到DispatcherServlet。
  • (2) DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler, 并返回Controller的名字
  • (3) 解析到对应的Handler名字后,开始由HandlerAdapter适配器处理。
  • (4) HandlerAdapter会根据名字来调用真正的处理器ControllerHandler开处理请求,并处理相应的业务逻辑。
  • (5) 处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的View(View的名字)。
  • (6) ViewResolver会根据逻辑View查找实际的View。
  • (7) DispaterServlet把返回的Model传给View,进行渲染。
  • (8) 通过View返回给请求者(浏览器)

%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2003_SpringMVC

Spring

Spring是个轻量级的框架集合, 模块包括IOC,AOP,DAO,ORM,WEB,MVC

其中最主要的是IOC和AOP

IOC实现了bean的周期管理,缓存等

Bean的生命周期

  • 1.实例化- ApplicationContext. Scope=singleton
  • 2.设置属性 - setter。。。
  • 3.如果继承了相应Aware接口,则可选(setBeanName, setApplicationContext,beanPostProcessBeforeInitialization)
  • 4.初始化 - 如果自定义了init-method可以执行自定的初始化操作
  • 5.容器关闭执行destroy, 如果有destroy-method可以执行自定的销毁操作

IOC

ApplicationContext为对象提供一个存储和应用的环境

有两种实现, (Xml)ClassPathXmlApplicationContext和(注解)AnnotationConfigApplicationContext

每个类被包扫描之后,会将它的配置信息存放在beanDefinition容器中

创建好的bean对象被放入beanPool池中

getBean的时候先去beanPool池中取,没有beanFactory再基于beanDefinition中的配置信息创建

%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2004_SpringIOC

Spring依赖注入的四种方式:1.构造器注入,2.setter方法注入,3.静态工厂注入,4.实例工厂注入

循环依赖

即A的属性中依赖B,B的属性中也依赖A

创建bean对象的时候分为3步, 1实例化对象,2注入属性,3初始化

解决依赖注入的条件

  • 1.必须是单例
  • 2.至少有一方是setter方式注入

A先实例化,然后创建一个工厂,将工厂放到三级缓存,然后进行属性注入的时候需要依赖B

B再实例化, 属性注入的时候需要依赖A, 这时候去三级缓存中取工厂,通过工厂获取A的实例对象或者代理对象,然后放入二级缓存并清除三级缓存中的工厂, 然后初始化B并走完后续流程

然后A对象进行初始化,并将bean放入一级缓存并清除二级缓存,结束

%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2005_Spring%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96%E9%97%AE%E9%A2%98

AOP

Aop是基于动态代理来实现的, 在动态代理中通过invoke织入功能

动态代理有两种 1.jdk动态代理。2.cglib动态代理

区别是 jdk动态代理与目标类的关系是同级且组合关系, cglib与目标类的关系是子与父的继承关系

  • @Aspect:声明是一个切面类
  • @Component: 将该类注入到spring中管理
  • @PointCut:配置一个切点
  • @PostConstruct:服务器加载servlet的时候执行一次,可以简单理解为spring容器加载时执行(被注解方法必须是void方法、非、static、不能抛出异常等)

%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2006_SpringAOP


数据库

数据库引擎 Innodb 和 myisam 区别

Myisam使用的是表锁,不支持高并发。

InnoDB使用的是行锁,支持高并发。

Myisam不支持外键。

InnoDB支持外键。

Myizam支持全文索引。

InnoDB不支持全文索引。不过可以通过中间件实现,比如Solr,ElasticSearch.

Myisam不支持事务,innoDB支持事务

Myisam使用的是非聚集索引,也就是树节点存储的是数据的指针(地址),当查找数据的时候,首先找到树节点相对应的指针,再根据指针去数据实际存储的位置查找真正的数据。(索引文件和数据文件是分离的)。

InnoDB使用的是聚集索引,也就是树节点实际存储的就是真实的数据,当查找数据的时候,查找到相对应的叶子结点对应的数据就结束了。(叶节点包含了完整的数据记录)

InnoDB的B+树结构

csdn

数据库连接池只有100个连接,有3000个连接请求

  1. 如果是长期请求都比较多,根据实际情况与经验适当增加连接池中连接的数量
  2. 使用资源调度,建立合适的队列,设置优先级,让优先级高的先执行,优先级低的排队执行
  3. 使用二八分配原则,使用缓存技术,实际上百分之八十的访问只集中在百分之二十的数据上,所以把经常访问的数据存放到缓存中,这个可以减轻IO,减少数据库访问,直接从缓存中读取数据,没有命中,再去访问数据库,缓存里面的数据要定时更新
  4. 增加数据库服务器的数量,使用读写分离技术,让主数据库服务器负责增、删、改操作,多个从服务器负责读操作,读的请求通过负载均衡,根据从服务器的访问压力来进行分配服务请求。

redis和数据库双写一致性问题

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。

三种策略可供参考

  • 先更新数据库,再更新缓存
  • 先删除缓存,再更新数据库
  • 先更新数据库,再删除缓存

1.先更新数据库,再更新缓存(有脏数据,不采用)

  • 线程A更新了数据库
  • 线程B更新了数据库
  • 线程B更新了缓存
  • 线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

2.先删缓存,再更新数据库(有脏数据,不采用,可用延时双删策略解决)

该方案会导致不一致的原因是

同时有一个请求A进行更新操作,另一个请求B进行查询操作

那么会出现如下情形:

  • 请求A进行写操作,删除缓存
  • 请求B查询发现缓存不存在
  • 请求B去数据库查询得到旧值
  • 请求B将旧值写入缓存
  • 请求A将新值写入数据库

上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

那么,如何解决上面这种情况呢?采用延时双删策略

3.延时双删策略(可以采用)

  • 先淘汰删除缓存
  • 再写数据库(这两步和原来一样)
  • 休眠1秒,再次淘汰删除缓存

这么做,可以将1秒内所造成的缓存脏数据,再次删除。

那么,这个1秒怎么确定的,具体该休眠多久呢?

针对上面的情形,应该自行评估自己的项目的读数据业务逻辑的耗时。

然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

4.先更新数据库,再删缓存(可以采用)

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

杠精:这种策略也有极端情况

并发情况,假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

  • 缓存刚好失效
  • 请求A查询数据库,得一个旧值
  • 请求B将新值写入数据库
  • 请求B删除缓存
  • 请求A将查到的旧值写入缓存

但是这种情况概率很低,因为先天条件是数据库 写 操作 比 读 操作 慢,这一点很难发生


事务

ACID

1、原子性:

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

2、一致性:

在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

3、隔离性:

数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执 行时由 于 交 叉 执 行而导致数据 的不一致 。 事务隔离分为不同级别,包括读未 提 交 ( Readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

4、持久性:

事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

Spring 和 数据库 事务的隔离级别

脏读是指一个事务在处理数据的过程中,读取到另一个为提交事务的数据。

不可重复读是指对于数据库中的某个数据,一个事务范围内的多次查询却返回了不同的结果,这是由于在查询过程中,数据被另外一个事务修改并提交了

幻读是事务非独立执行时发生的一种现象。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)

  1. 未提交读: 会导致 读脏
  2. 提交读: 解决脏读
  3. 可重复读: 解决脏读,不可重复读
  4. 序列化(串行化): 解决脏读,不可重复读,和幻读;

    这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
  5. Spring还有一个默认, 按照数据库事务级别来

传播行为

有七大传播行为,也是在TransactionDefinition接口中定义。

  • PROPAGATION_REQUIRED:支持当前事务,如当前没有事务,则新建一个。
  • PROPAGATION_SUPPORTS:支持当前事务,如当前没有事务,则已非事务性执行(源码中提示有个注意点,看不太明白,留待后面考究)。
  • PROPAGATION_MANDATORY:支持当前事务,如当前没有事务,则抛出异常(强制一定要在一个已经存在的事务中执行,业务方法不可独自发起自己的事务)。
  • PROPAGATION_REQUIRES_NEW:始终新建一个事务,如当前原来有事务,则把原事务挂起。
  • PROPAGATION_NOT_SUPPORTED:不支持当前事务,始终已非事务性方式执行,如当前事务存在,挂起该事务。
  • PROPAGATION_NEVER:不支持当前事务;如果当前事务存在,则引发异常。
  • PROPAGATION_NESTED:如果当前事务存在,则在嵌套事务中执行,如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作(注意:当应用到JDBC时,只适用JDBC 3.0以上驱动)。

Spring实现编程式事务,依赖于2大类,PlatformTransactionManager,与模版类TransactionTemplate(推荐使用)
声明式事务实现方式, Spring的tx:advice定义事务通知与AOP相关配置实现,另为一种通过@Transactional

Spring事务什么时候会失效?

  1. 如果数据库不支持事务,则失效

因为事务是作用于数据库。例如使用MySQL且引擎是MyISAM,则事务会不起作用,因为MyISAM引擎本身不支持事务;如果改成InnoDB,则可以。

  1. Service类没有被Spring管理

因为Spring的事务是基于AOP,所以如果Service类没有被Spring管理,变成一个Spring Bean,即使添加了@Transactional注解,事务也是无效的。

  1. 内部调用

不带事务的方法调用该类中带事务的方法,不会回滚。因为Spring的回滚是用过代理模式生成的,如果是一个不带事务的方法调用该类的带事务的方法,直接通过this.xxx()调用,而不生成代理事务,所以事务不起作用。常见解决方法“拆类”

  1. 发生的异常不是RuntimeException, 比如IOException

spring的事务默认是对RuntimeException进行回滚,而不继承RuntimeException的不回滚

因为在java的设计中,它认为不继承RuntimeException的异常是CheckException或普通异常,如IOException,这些异常在java语法中是要求强制处理的。

对于这些普通异常,Spring默认它们都已经处理,所以默认不回滚。可以添加rollbackfor=Exception.class来表示所有的Exception都回滚

  1. 异常被捕捉处理没有抛出
  2. 事务只能应用于 public 方法

@Transactional注解只能应用于public方法,如果你在protected、private或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

分布式事务控制

TPC

一般的实现形式:所有的事务一阶段执行sql不提交 ,都成功之后TC通知所有事务进行二阶段主动提交,如果有一个失败TC通知所有事务进行二阶段回滚

一阶段:协调器问“你们几个子事务参与者对应的活能不能干成?" 子事务参与者们一一回复“能干/干不成”

二阶段:协调器问根据子事务参与者们的反馈如果都能干则告诉所有人都去干吧,如果有人说干不了,特通知大家别干了

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2025_%E4%BA%8B%E5%8A%A1.png

TCC

try阶段所有参与者进行尝试提交业务(eg:创建订单的订单状态是CREATING,减库存虽然进行了100-2=98,但是会记录本次有2个冻结中的库存,等类似try操作);

Confirm阶段 如果try阶段的执行都成功了则TM通知所有参与者执行真正的提交(eg:创建订单的订单状态改为CREATED,减库存 被冻结的2个库存直接删掉,等类似Confirm操作【因为用网络超时等原因可能会有重复的调用所有要求支持幂等性】);

cancel阶段 如果try中有一个执行失败则TM通知所有参与者进行补偿操作(eg:创建订单的订单状态改为CANCELED,减库存中被冻结的2重新加回到数据库中,等类似cancel操作

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2026_%E4%BA%8B%E5%8A%A1.png

幂等性

幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

  • 1.token机制解决幂等性

使用uuid生成一个防重令牌token,并把token放到redis里,然后把这个token,封装到出参,给到前端的订单确定页面

  • 2.防重表解决幂等性
  • 3.业务层分布式锁

工具

Nginx

反向代理

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2013_Nginx.png

正向代理

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2014_Nginx.png

server{
listen 80;
server_name manage.jt.com;
location / {
proxy_pass http://jtWindows;
}
}
#定义windows集群
upstream jtWindows {
server localhost:8081;
server localhost:8082;
server localhost:8083;
}

rabbitMQ

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2017_RabbitMQ.png

rabbitmq生产

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2018_RabbitMQ.png

rabbitmq消费

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2019_RabbitMQ.png

kafka

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2020_RabbitMQ.png

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2021_kafka.png

kafka生产

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2022_kafka.png

kafka消费

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2023_kafka.png

kafka对消息保存时根据Topic进行归类,发送消息者就是Producer,消息接受者就是Consumer,每个kafka实例称为broker。然后三者都通过Zookeeper进行协调

kafka中的broker 是消息的代理,Producers往Brokers里面的指定Topic中写消息,Consumers从Brokers里面pull拉取指定Topic的消息,然后进行业务处理,broker在中间起到一个代理保存消息的中转站。

每个Topic被分成多个partition(区)。每条消息在partition中的位置称为offset(偏移量),类型为long型数字。消息即使被消费了,也不会被立即删除,而是根据broker里的设置,保存一定时间后再清除,比如log文件设置存储两天,则两天后,不管消息是否被消费,都清除。

每个consumer属于一个consumer group。在kafka中,一个partition的消息只会被group中的一个consumer消费

kafka如何确保数据不丢失

  • 生产时

kafka的ack机制:在kafka发送数据的时候,每次发送消息都会有一个确认反馈机制,确保消息正常的能够被收到。

  • 消费时

通过offset commit 来保证数据的不丢失,kafka自己记录了每次消费的offset数值,下次继续消费的时候,接着上次的offset进行消费即可

kafka怎么保证消息的消费顺序?

kafka只保证单partition有序,如果Kafka要保证多个partition有序,不仅broker保存的数据要保持顺序,消费时也要按序消费。

kafka保证消息顺序有2种方法。

  • 第1种:全局消费顺序

实现方式:1个Topic(主题)只创建1个Partition(分区),这样生产者的所有数据都发送到了一个Partition(分区),保证了消息的消费顺序。

  • 第2种:局部消费顺序

实现方式:生产者在发送消息的时候指定要发送到哪个Partition(分区)(1个)。

消费者以组的名义订阅topic,topic下有多个partition,消费者组中有多个消费者实例。
同一时刻,一条消息只能被组中的一个消费者实例消费。
如果按照从属关系来说的话就是,主题下的每个分区只从属于组中的一个消费者,不可能出现组中的两个消费者负责同一个分区。消息就是存储在partition中。

RabbitMQ和Kafka的区别

  • rabbitmq:
  1. producer,broker遵循AMQP(exchange,bind,queue),consumer;
  2. broker为中心,exchange分topic,direct,fanout和header,路由模式适合多种场景;
  3. consumer消费位置由broker通过确认机制保存;
  • kafka:
  1. producer,broker,consumer,未遵循AMQP;
  2. consumer为中心,获取消息模式由consumer自己决定;
  3. offset保存在消费者这边,broker无状态;
  4. 消息是名义上的永久存储,每个parttition按segment保存自己的消息为文件(可配置清理周期);
  5. consumer可以通过重置offset消费历史消息;
  6. 需要绑定zk;
  • AMQP是什么

AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个进程间传递异步消息的网络协议。

https://alexleon.oss-cn-shanghai.aliyuncs.com/markdown-pic/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BA%B2024_AMQP.png

发布者(Publisher)发布消息(Message),经由交换机(Exchange)。

交换机根据路由规则将收到的消息分发给与该交换机绑定的队列(Queue)。

最后 AMQP 代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。


设计模式

http://c.biancheng.net/design_pattern/


网络

HTTP和HTTPS的区别

HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。

HTTPS比HTTP更加安全,对搜索引擎更友好,利于SEO,谷歌、百度优先索引HTTPS网页;

使用 HTTPS 协议需要到 CA(数字证书认证机构) 申请SSL证书。

HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上SSL 握手需要的 9 个包,所以一共是 12 个包。

HTTP 和 HTTPS 用的端口也不一样,前者是 80,后者是 443。

在 OSI 网络模型中,HTTP 工作于应用层,而 HTTPS 工作在传输层。

举报

相关推荐

0 条评论