赞
踩
依据个人认知总结,如有技术性问题烦请指正!
基本数据类型的存储原理: 所有的简单数据类型不存在“引用”的概念,基本数据类型都是直接存储在内存中的内存栈上的,数据本身的值就是存储在栈空间里面,而Java语言里面八种基本数据类型就是这种存储模型
基本类型是按值传递
一个字节等于8位
练习题
short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?
- 第一个有错,1是int类型,不能转为short,第二个对,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
- 表达式的数据类型自动提升, 关于类型的自动提升,注意下面的规则。
①所有的byte,short,char型的值将被提升为int型;
②如果有一个操作数是long型,计算结果是long型;
③如果有一个操作数是float型,计算结果是float型;
④如果有一个操作数是double型,计算结果是double型;
而声明为final的变量会被JVM优化
首先我们要知道,引用类型的出现是为了节省内存,当我们使用引用类型时,一定要给定一个空间,
即需要new一个对象。(1) 引用是一种数据类型(保存在栈中),保存了对象在内存(堆)中的地址,这种类型即不是我们平时所说的基本数据类型也不是类实例(对象);
(2) 不同的引用可能指向同一个对象,换句话说,一个对象可以有多个引用,即该类类型的变量。
integer的128陷阱
算法 IP地址和int的双向转换
static关键字并不会改变变量和方法的访问权限
static是不允许用来修饰局部变量
所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)
静态变量
静态变量(带有static关键字的字段)是属于类的,所有该类的对象共用该字段;
非静态变量(普通字段)是属于类的对象的,每一个该类的对象都有自己的非静态字段,他们互不影响。
静态方法
静态方法与普通方法的区别,与静态字段与普通字段的区别类似
静态方法是不在对象上执行的方法,在调用静态方法时,不需要实例化该类而调用普通方法必须实例化该类。
抽象类
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用
抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口
抽象方法
抽象方法只包含一个方法名,而没有方法体
声明抽象方法会造成以下两个结果:
- 如果一个类包含抽象方法,那么该类必须是抽象类。
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
抽象类总结规定
为什么需要抽象类?
抽象方法和抽象类看上去是多余的,对于抽象方法,不知道如何实现,定义一个空方法体不就行了吗,而抽象类不让创建对象,看上去只是增加了一个不必要的限制。
引入抽象方法和抽象类,是Java提供的一种语法工具,对于一些类和方法,引导使用者正确使用它们,减少被误用。
使用抽象方法,而非空方法体,子类就知道他必须要实现该方法,而不可能忽略。
使用抽象类,类的使用者创建对象的时候,就知道他必须要使用某个具体子类,而不可能误用不完整的父类。
无论是写程序,还是平时做任何别的事情的时候,每个人都可能会犯错,减少错误不能只依赖人的优秀素质,还需要一些机制,使得一个普通人都容易把事情做对,而难以把事情做错。抽象类就是Java提供的这样一种机制。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为
继承的特性
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作
多态的优点
多态存在的三个必要条件
多态的实现方式
方式一:重写:
重写(Override):子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常
注意区分重载(重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。)
方式二:接口
方式三:抽象类和抽象方法
接口,在JAVA中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
Java的反射是指程序在运行期可以拿到一个对象的所有信息,是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P3In4YVW-1657867343290)(D:\资料留存\面试\图片知识点\反射API.jpg)]
final 用于修饰变量、方法和类。
final 变量:被修饰的变量不可变,不可变分为引用不可变和对象不可变,final 指的是引用不可变,final 修饰的变量必须初始化,通常称被修饰的变量为常量。
final 方法:被修饰的方法不允许任何子类重写,子类可以使用该方法。
final 类:被修饰的类不能被继承,所有方法不能被重写。
finally 作为异常处理的一部分,它只能在
try/catch
语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit (0)
可以阻断 finally 执行。
是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。
一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。
类使用implements关键字实现接口
super
关键字是一个引用变量,用于引用直接父类对象。每当创建子类的实例时,父类的实例被隐式创建,由
super
关键字引用变量引用。调用的位置只能在构造器的第一行
super
关键字的用法如下:
super
可以用来引用直接父类的实例变量。super
可以用来调用直接父类方法。super()
可以用于调用直接父类构造函数。
使用super和this应注意:
1)调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
2)super()和this()类似,区别是,super从子类中调用父类的构造方法,this()在同一类内调用其它方法。
3)super()和this()均需放在构造方法内第一行。
4)尽管可以用this调用一个构造器,但却不能调用两个。
5)this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
6)this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
7)从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
private | 私有的 | 被其修饰的属性以及方法只能被该类的对象 访问,其子类不能访问,更不能允许跨包访问 |
---|---|---|
protected | 受保护访问 | 被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问 |
public | 公开的 | 被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问 |
default(不加任何访问修饰符) | 默认访问 | 只允许在同一个包中进行访问 |
方法调用
final语义:
在接口里面的变量默认都是public static final 的,它们是公共的,静态的,最终的常量.相当于全局常量,可以直接省略修饰符。
实现类可以直接访问接口中的变量
变量被static修饰则该变量为类变量,类变量存储在方法区,不属于每个实例的私有,该类的所有对象操作的都是同一个变量
执行效率(相对情况):StringBuilder > StringBuffer > String
String:适用于少量的字符串操作的情况,对象不可变(内部结构用的是private final char[],字符串拼接是生成新的字符串)
StringBuilder:线程不安全,性能较好,适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:线程安全(StringBuffer中的方法基本被synchronized修饰),性能较差,适用多线程下在字符缓冲区进行大量操作的情况,对象可变(字符串拼接是在原字符串后链接)
问题1、String, StringBuilder, StringBuffer的区别、使用场景
StringBuilder, StringBuffer的实现原理:
• 用char[]存储数据,当char[]盛不下时,进行扩容,2倍扩容,避免总是扩容,默认数组的长度是16,如果能够预测char[]的长度的话,如果长度小于16,那么可以不设置,如果比16长的话,应该尽量设置长度,不然的话,会进行扩容,导致性能降低。
异常处理一般格式:
捕获异常:
try{
//代码块
}catch(异常类型,例如:Exception e){
//需要抛出的异常,例如:e.printStackTrace();
}catch(异常类型){
//需要抛出的异常
}finally{
//必定执行的代码块
}
所以说在一个异常处理中catch语句块是可以多个的,也就是可以抛出多个异常!
类加载机制:jvm把数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
类的生命周期:
类加载的三种方式:
jvm类加载机制:
全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
父类委托:先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。
双亲委派机制:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
判断2个类是否相同:
首先看他们的类加载器是不是一样的,如果不一样,那么肯定不是同一个类
局部变量表是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量
虚拟机栈由栈帧组成,栈帧由局部变量表、操作数栈、动态链接和方法返回四部分组成,有的虚拟机还有一些附加信息
元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存
废弃永久代原因:官方解释是为了融合其他vm,实际使用中永久代内存经常不够用或发生内存泄露
方法区:专门用来存放已经加载的类信息,常量,静态变量以及方法代码的内存区域
常量池:是方法区的一部分,主要用来存放常量和类中的符号引用等信息;
堆区:存放类的对象实例
栈区:也叫Java虚拟机栈,由一个个的栈帧组成的后进先出的栈式结构,存放方法运行时产生的局部变量,方法出口等信息。当调用一个方法时,虚拟机栈就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法调用了其他方法,则继续在栈顶创建新的栈帧。
堆、栈、方法区会出现的异常:
方法区 PermGen space:Perm被占满,无法为新的class分配存储空间而引发的异常
栈溢出(只有栈):递归没返回,或者循环调用造成
内存泄漏:解决办法一般就是根据垃圾回收前后情况对比,同时根据对象引用情况(常见的集合对象引用)分析,基本都可以找到泄漏点。
怎么让方法区溢出 方法区存的是 类加载信息、静态变量、常量、常量池
Full GC (新生代+老年代垃圾回收)
Full GC可以理解为Major GC+Minor GC组合后进行的一整个过程,是清理JVM整个堆空间(年轻代和老年代空间)。
Full GC触发条件
Serial收集器:(适用新生代+单线程“串行”)它在进行垃圾回收的工作时,必须暂停JVM中的其他工作线程
Serial Old收集器:(适用老年代+单线程“串行”)是Serial收集器在老年代上的版本,同样是采用复制算法的单线程收集器
Parnew收集器:(适用新生代+多线程“并行”)其实就是Serial收集器的多线程版本
Parellel Scavenge收集器:(适用新生代+多线程“并行”)采用复制算法的收集器,主要关注点在于达到一个可控制的吞吐量
Parellel Old收集器:(适用老年代+多线程“并行”)是Parellel Scavenge的老年代版本,采用多线程和标记-压缩算法。
CMS收集器:(标记-清除并发收集器+多线程)
收集过程分为4步:
初始标记:标记GC Roots能直接关联到的对象,速度很快
并发标记:进行GC Roots追踪的过程
重新标记:修正并发标记期间由于用户程序继续执行可能产生变动的那部分对象的标记记录,此阶段会比初始标记长一些,但远小于并发标记的时间。
并发清除
优点:
缺点:
G1收集器
并行与并发:G1能充分利用多CPU下的优势来缩短Stop The World的时间,同时在其他部分收集器需要停止Java线程来执行GC动作时,G1收集器仍然可以通过并发来让Java线程同步执行。
分代收集:与其他收集器一样,分代的概念在G1中任然被保留。可以不需要配合其他的垃圾收集器,就独立管理整个Java堆内存的所有分代区域,且采用不同的方式来获得更好的垃圾收集效果。
空间整合:G1从整体来看,使用的是标记-压缩算法实现的,从局部两个Region来看,采用的是复制算法实现的,对内存空间的利用非常高效,不会像CMS一样产生内存碎片。
可以预测的停顿:除了追求低停顿以外,G1的停顿时间可以被指定在一个时间范围内。
如果不计算维护Remenbered Set的操作,G1收集器的工作阶段大致区分如下:
垃圾回收器分类: JDK1.7和1.8(默认parallel scavenge,parallel old),1.9(默认G1)
1、串行处理器(单线程):serial,serial old
2、并行处理器(多线程):ParNew,parallel scavenge,parallel old 适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。 缺点:垃圾收集过程中应用响应时间可能加长
3、并发处理器(多线程):cms,G1;特点:对响应时间有高要求(低停顿)
★★★★★ 重点说下G1的垃圾回收过程:初始标记;并发标记;最终标记;选择回收(CMS是并发回收,G1相对于CMS的优点)
问题4:JVM调优?或者做过关于性能提高的工作吗
曾经遇到过性能调优的一个情况,起因是一个POD容器频繁宕机,通过检查发现相比其他容器,GC比较频繁,便考虑到可能是内存分配较小,调大了内存后,GC不频繁,宕机情况也解决
判断对象可以被回收:基于GC Roots的可达性分析算法,如果对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为垃圾对象,会被 GC 回收。
参数 | 含义 |
---|---|
-XX:+UseConcMarkSweepGC | 使用CMS垃圾回收器 |
-XX:+UseCMSCompactAtFullCollection | 在使用ConcurrentGC(并发GC)的情况下,防止内存碎片,对存活对象进行整理,使碎片减少 |
-XX:+CMSClassUnloadingEnabled | 老年代启用CMS,但默认是不会回收永久代的。此处对永久代启用类回收,防止内存满 |
-XX:+UseCMSInitiatingOccupancyOnly | 只有在老年代使用了初始化的比例后ConcurrentCollector(并发收集器)启动收集 |
-XX:+PrintGCDetails | 打印GC日志 |
-XX:+PrintGCDateStamps | 打印GC日志对应的时间戳 |
-XX:-OmitStackTraceInFastThrow | 省略异常栈信息从而快速抛出 |
-XX:+HeapDumpOnOutOfMemoryError | 可以让JVM在出现内存溢出时候Dump出当前的内存快照dump文件 |
-XX:MetaspaceSize=2g | 初始化的Metaspace大小,该值越大触发Metaspace GC的时机就越晚。 |
-XX:MaxMetaspaceSize=512m | 限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。默认无上限 |
-XX:MaxDirectMemorySize=1g | 指定本地直接内存大小,如果不指定,则默认与Java堆的最大值-Xmx指定一样 |
-Xmx4g -Xms4g | jvm内存最大最小值 |
-Xmn2g | 设置年轻代大小为2G |
-XXSurvivorRatio=3 | 代表Eden:Survivor:Survivor = 3:1:1 |
HeapDump文件是指定时刻的Java堆栈的快照,是一种镜像文件。
jvm的几个运行时区域都有发生 OutOfMemoryError 异常的可能
锁是多线程环境的一种同步机制,对线程访问资源权限进行控制,实现并发策略
是轻量级的synchronized,保证共享变量的“可见性”,只能对变量作用,如果使用恰当,比synchronized的使用和执行成本更低(不会引起线程上下文切换和调度)
只保证多线程操作的可见性(将当前处理器缓存行的数据写回到系统内存,同时这个写回内存操作会使其他CPU里缓存了该内存地址的数据无效),不保证原子性
只能作用在方法上,互斥锁
Syncronized是重量级锁,加了 syncronized 关键字的方法、代码块中,一次只允许一个线程进入特定代码段,从而避免多线程同时修改同一数据
应用Sychronized注意:
- 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
- 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁
- synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
有两种形式上锁,一个是对方法上锁,一个是构造同步代码块。他们的底层实现其实都一样,在进入同步代码之前先获取锁,获取到锁之后锁的计数器+1,同步代码执行完锁的计数器-1,如果获取失败就阻塞式等待锁的释放。只是他们在同步块识别方式上有所不一样,从class字节码文件可以表现出来,一个是通过方法flags标志,一个是monitorenter和monitorexit指令操作。
理解锁实现原理之前要先了解Java的对象头和Monitor,在JVM中,对象是分成三部分存在的:对象头、实例数据(存放类的属性数据信息)、对其填充(仅是字节对齐作用)。
对象头是synchronized实现锁的基础,因为synchronized申请锁、上锁、释放锁都与对象头有关。对象头主要结构是由Mark Word
和 Class Metadata Address
组成,其中Mark Word
存储对象的hashCode、锁信息或分代年龄或GC标志等信息,Class Metadata Address
是类型指针指向对象的类元数据,JVM通过该指针确定该对象是哪个类的实例。
锁的类型和状态在对象头Mark Word
中都有记录,在申请锁、锁升级等过程中JVM都需要读取对象的Mark Word
数据。
每一个锁都对应一个monitor对象,当线程试图获取对象锁时自动生成,但当一个monitor被某个线程持有后,它便处于锁定状态。
锁的状态总共有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁,在 JDK 1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁,但是在JDK 1.6后,Jvm为了提高锁的获取与释放效率对(synchronized )进行了优化,引入了 偏向锁 和 轻量级锁,并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。
升级过程:
无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
偏向锁:初次执行到synchronized代码块的时候,锁对象变成偏向锁,执行完同步代码块后,线程并不会主动释放偏向锁。
轻量级锁:当锁是偏向锁的时候,却被另外的线程访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
重量级锁:如果锁竞争情况严重,某个达到最大自旋次数(默认10次)的线程,会将轻量级锁升级为重量级锁。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待被唤醒。
公平锁:先申请的先得到锁
非公平锁:先申请的不一定先得到锁。一般来说,使用非公平锁可以获得较大的吞吐量,所以推荐优先使用非公平锁。
悲观锁:在读数据的时候总认为其他线程会对数据进行修改,所以采取加锁的形式,一旦本线程要读取数据时,就加锁,其他线程被阻塞,等待锁的释放。悲观锁总结为悲观加锁阻塞线程
乐观锁:在读数据时总认为其他线程不会对数据做修改,在更新数据时会判断其他线程有没有更新数据,如果有更新,则重新读取,再次尝试更新,循环上述步骤直到更新成功。这样来看乐观锁实际上是没有锁的,只是通过一种比较交换的方法来保证数据同步,总结为乐观无锁回滚重试。
CAS(比较和交换)
可重入锁:允许多个线程多次获取同一把锁,那从锁本身的角度来看,就是可以重新进入该锁。比如有一个递归函数里面有加锁操作,如果这个锁不阻塞自己,就是可重入锁,故也称递归锁 。
Lock相较于Synchronized优势如下:
CopyOnWriteArrayList适用于写少读多的并发场景
ReadWriteLock即为读写锁,他要求写与写之间互斥,读与写之间互斥,读与读之间可以并发执行。在读多写少的情况下可以提高效率
链接:https://pdai.tech/md/java/thread/java-thread-x-lock-AbstractQueuedSynchronizer.html
AbstractQueuedSynchronizer的分析,最核心的就是sync queue的分析。
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
Java 提供了三种创建线程的方法:
采用实现Runnable、Callable接口的方式创见多线程时,
使用继承Thread类的方式创建多线程时
Object类的线程方法。
**notify() **:通知一个在对象上等待的线程,使其从wait()返回,而返回的前提是该线程获取到了对象的锁。
notifyAll(): 通知所有等待在该对象上的线程。
wait():调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁。
wait(long) :超时等待一段时间(参数是毫秒),如果没有通知就超时返回。
wait(long, int) : 对于超时时间更细粒度的控制,可以达到毫秒。
https://blog.csdn.net/fanrenxiang/article/details/79855992
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
corePoolSize 线程池核心线程大小:这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut
maximumPoolSize 线程池最大线程数量:当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到队列中。如果队列也满,则会创建新线程处理新提交的任务。
keepAliveTime 空闲线程存活时间:一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,空闲线程会被销毁
unit 空闲线程存活时间单位
workQueue 工作队列:新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列
ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
threadFactory 线程工厂:创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon(守护)线程等等
handler 拒绝策略:当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,就需要拒绝策略,jdk中提供了4种拒绝策略
• 定时线程池同步数据
• 多数据库源问题的解决
set、list、map区别:https://blog.csdn.net/qq_39241239/article/details/82116734、 https://www.jb51.net/article/112985.htm
java集合:主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。
https://blog.csdn.net/dengpeng0419/article/details/47983033
叫车服务,有500量符合要求的车,怎么快速选出前10量?
根据车的远近、司机的评分等因素,进行堆排序,时间复杂度500log10
堆排序是稳定的吗?不是,稳定的有哪些?冒泡、插入、归并
介绍:HashMap是最常用的存储键值对的集合,继承了AbstractMap类,实现了Map等接口,内部原理是基于散列函数计算出元素存储的位置,查询的时候也是根据散列函数继续计算出存储的位置去获取该位置上存储的元素,非并发安全。
底层原理:1.7版本中,底层数据结构是数组+链表,也就是一个数组用来存储value
,因为散列函数是很有可能出现哈希碰撞的,也就是两个不同的key
计算得出同一个哈希值,结果就存到同一个数组索引上了,那不能覆盖掉前面的值呀,所以数组中存的是链表,如果有冲突了,同一个索引上就使用链表来存储多个值。
1.8版本中,因为链表的查询时间复杂度是O(n),所以冲突很严重,一个索引上的链表非常长,效率就很低了,所以在1.8版本的时候做了优化,当一个链表的长度超过8的时候就转换数据结构,不再使用链表存储,而是使用红黑树,红黑树是一个保证大致平衡的平衡树,所以性能相较AVL树这样的高度平衡树来将性能会更好。
1.7版本ConcurconrentHashMap底层采用的是分段锁,具体来说是一个 Segment 数组(默认长度为16),每个 Segment 又包含了一个 HashEntry 数组,所以可以看做一个 HashMap, Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 Segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
1.8版本取而代之的是 Node 数组 + CAS + synchronized + volatile 的新设计方式,只调用synchronized锁住首节点的方式,使得锁更加灵活,同时锁粒度也更加小
不仅数据结构变得更简单了(与JDK 1.8 的HashMap类似),锁的粒度也更小了,锁的单位从 Segment 变成了 Node 数组中的桶(科普:桶就是指数组中某个下标位置上的数据集合,这里可能是链表,也可能是红黑树)。说到红黑树,必须提一下,在JDK 1.8 的 HashMap 和ConcurrentHashMap 中,如果某个数组位置上的链表长度过长(大于等于8),就会转化为红黑树以提高查询效率
可以发现源码中完全没有加锁的操作,因为使用 volatile 关键字已经足以保证线程在读取数据时不会读取到脏数据,所以没有加锁的必要。
CAS 操作是新版本 ConcurrentHashMap 线程安全实现原理的精华所在,如果说其共享变量的读取全靠 volatile 实现线程安全的话,那么存储和修改过程除了使用少量的 synchronized 关键字外,主要是靠 CAS 操作实现线程安全的。
ConcurrentHashMap的底层结构和HashMap一样,都是数组+链表+红黑树
ConcurrentHashMap是同步的HashMap,读写都加锁
Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。 键的类型只能为字符串,值支持的五种类型数据类型为:字符串(String)、列表(List)、集合(Set)、有序集合(Zset)、散列表(Hash)。 Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。
数据结构:字典和跳跃表
计数器 :可以对 String 进行自增自减运算,从而实现计数器功能。 Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。
缓存: 将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。
查找表: 例如 DNS 记录就很适合使用 Redis 进行存储。 查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因 为缓存不作为可靠的数据来源。
消息队列: List 是一个双向链表,可以通过 lpop 和 lpush 写入和读取消息。 不过最好使用 Kafka、RabbitMQ 等消息中间件。
会话缓存: 在分布式场景下具有多个应用服务器,可以使用 Redis 来统一存储这些应用服务器的会话信息。 当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器。
分布式锁实现: 在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。 可以使用 Reids 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。
其它: Set 可以实现交集、并集等操作,从而实现共同好友等功能。 ZSet 可以实现有序性操作,从而实现排行榜等功能。
两者都是非关系型内存键值数据库,主要有以下不同:
数据类型: Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。
数据持久化: Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
分布式: Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需 要先在客户端计算一次数据所在的节点。 Redis Cluster 实现了分布式的支持。
内存管理机制: 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。 Memcached 将内存分割成特定长度的块来存储数据,来解决内存碎片的问题,但是这种方式会使内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。
对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间
Redis可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。
Reids 具体有 6 种淘汰策略:
作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并非针对所有 key,而是抽样一小 部分并且从中选出被淘汰的 key。
使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,启用 最近最少使用的数据淘汰策略。
Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。
Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。
RDB 持久化: 将某个时间点的所有数据都存放到硬盘上。 可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。 如果系统发生故障,将会丢失最后一次创建快照之后的数据。 如果数据量很大,保存快照的时间会很长。
优点
缺点
AOF 持久化: 将写命令添加到 AOF 文件(Append Only File)的末尾。 使用 AOF 持久化需要设置同步选项,从而确保写命令什么时候会同步到磁盘文件上。这是因为对文件进行写入并 不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。同步选项:每个写命令都同步、每秒同步一次 、no 让操作系统来决定何时同步
优点
缺点
Redis事务:Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。
缓存击穿(不存在的数据,可以在redis中存null)
数据库水平切分,或者读写分离
分布式锁,读数据库,然后把数据加载到缓存
熔断、降级
布隆过滤器
缓存雪崩
过期时间加上一个随机数
备份缓存:缓存a有超时,缓存b没有超时
分布式锁:每次只能使用限个数据库连接
缓存并发
分布式的话:用分布式锁
单个服务器的话:用synchronize,lock等保证线程安全
redis集群有三种模式:主从模式、哨兵模式、Redis cluster(redis集群)
三种模式主从复制原理基本一致,主从模式不支持高可用(机器故障需要手动操作);哨兵模式解决高可用问题,但扩容需求无法满足;
集群介绍
1)主从模式里一个redis实例作为主机(master),其余多个实例作为备份机(slave);
2)master支持数据的写入和读取操作,而slave支持读取及master的数据同步;
3)在整个架构里,master和slave实例里的数据完全一致;
主从复制原理
主从复制中因网络等原因造成数据丢失场景,当从节点再次连上主节点。如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。
主节点故障处理方式
主从模式中,每个客户端连接redis实例时都指定了ip和端口号。如果所连接的redis实例因为故障下线了,则无法通知客户端连接其他客户端地址,因此只能进行手动操作。
不支持高可用
主从模式很好地解决了数据备份的问题,但是主节点因为故障下线后,需要手动更改客户端配置重新连接,这种模式并不能保证服务的高可用。
集群介绍
哨兵模式中增加了独立进程(即哨兵)来监控集群。客户端在连接集群时,首先连接哨兵,通过哨兵查询主节点的地址,然后再去连接主节点进行数据交互。
如果master异常,则会进行master-slave切换,将最优的一个slave切换为主节点。同时,哨兵持续监控挂掉的主节点,待其恢复后,作为新的从节点加入集群中。
主节点故障处理方式/哨兵工作方式
扩容问题
哨兵模式的出现虽然解决了主从模式中master节点宕机不能自主切换(即高可用)的问题。但是,随着业务的逐渐增长,不可避免需要对当前业务进行扩容。
常见的扩容方式有垂直和水平扩容两种方式:
虽然垂直扩容方式很便捷,不需要添加多余的节点,但是机器的容量是有限的,最终还是需要通过水平扩容方式来解决。而水平扩容涉及到数据的迁移,且迁移过程中又要保证服务的可用性。因此,数据能不迁移就尽量不要迁移。
集群介绍
主节点故障处理方式
redis cluster中主节点故障处理方式与哨兵模式比较相似,当约定时间内某节点无法与集群中的另一个节点顺利完成ping消息通信时,则将该节点标记为主观下线状态,同时将这个信息向整个集群广播。
如果一个节点收到某个节点失联的数量达到了集群的大多数时,那么将该节点标记为客观下线状态,并向集群广播下线节点的fail消息。然后立即对该故障节点进行主从切换。等到原来的主节点恢复后,会自动成为新主节点的从节点。如果主节点没有从节点,那么当它发生故障时,集群就将处于不可用状态。
扩容问题
复杂sql语句,遇到的复杂SQL语句,group by、行转列(小表涉及)等
1、innodb支持事务ACID,myisam不支持
2、innodb支持行级锁,myisam支持表级锁
3、innodb支持mvcc(多版本并发控制),myisam不支持
4、MyIASM支持全文类型索引,而InnoDB不支持全文索引
5、MyIASM相对简单,效率上要优于InnoDB,小型应用可以考虑使用MyIASM
6、MyIASM表保存成文件形式,跨平台使用更加方便
应用:1、MyIASM管理非事务表,提供高速存储和检索以及全文搜索能力,如果再应用中执行大量select操作,应该选择MyIASM
2、InnoDB用于事务处理,具有ACID(原子性、一致性、隔离性、持久性)事务支持等特性,如果在应用中执行大量insert和update操作,应该选择InnoDB
数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑的数据库操作遵循:要么全部执行成功,要么全部不执行,关系型数据库具有ACID特性
Atomicity
) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;Consistency
): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;Isolation
): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;Durabilily
): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题,讨论隔离级别的场景,主要是在多个事务并发 的情况下
读未提交-Read uncommitted ----最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
读提交-Repeatable read ---- 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。(大多数数据库的默认级别就是读提交,比如Sql Server , Oracle)
可重复读-Read committed ---- 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(Mysql的默认隔离级别是可重复读)
序列化- Serializable ----最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读、不可重复读以及幻读。
### 脏读(读取未提交数据)
### 不可重复读(前后多次读取,数据内容不一致)
### 幻读(前后多次读取,数据总量不一致)
不可重复读和幻读到底有什么区别呢?
(1)不可重复读是读取了其他事务更改的数据,针对insert与update操作
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。
(2)幻读是读取了其他事务新增的数据,针对insert与delete操作
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
MySQL 的隔离级别基于锁和 MVCC (多版本并发控制)机制共同实现的。
序列化隔离级别,是通过锁来实现的。除了序列化隔离级别,其他的隔离级别都是基于 MVCC 实现。
不过, 序列化之外的其他隔离级别可能也需要用到锁机制,就比如 读提交 在当前读情况下需要使用加锁读来保证不会出现幻读
MyISAM 仅仅支持表级锁,锁整张表,在并发写的情况下性能非常差。
InnoDB 不光支持表级锁,还支持行级锁,默认为行级锁。行级锁的粒度更小,仅对相关的记录上锁即可,所以对于并发写入操作来说, InnoDB 的性能更高。
行级锁的使用注意事项:InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。当我们执行 更新或删除语句时,如果 WHERE
条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有记录进行加锁。
不论是表级锁还是行级锁,都存在共享锁和排他锁这两类:
排他锁与任何的锁都不兼容,共享锁仅和共享锁兼容。
是MySQL 存储引擎的默认索引类型,便于查找两个值之间的多个元素
B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。
与B树的区别:
B+树与红黑树的比较:
红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,主要有以下两个原因:
(一)更少的查找次数(红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,检索的次数也就更多)
(二)利用计算机预读特性(预读过程中,磁盘进行顺序读取,数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,相邻的节点也能够被预先载入)
先进行适当分批次,然后在批次内进行堆排序
使用二叉搜索树(BST),只需 log(N) 次运算,查找特定值好用
定义:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树
一条sql语句执行到结果经历了什么?
是归并排序,把问题拆分为小问题,通过解决小问题来解决最初的问题
关系型数据库:采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,以便于用户理解,关系型数据库这一系列的行和列被称为表,一组表组成了数据库。用户通过查询来检索数据库中的数据,而查询是一个用于限定数据库中某些区域的执行代码。关系模型可以简单理解为二维表格模型,而一个关系型数据库就是由二维表及其之间的关系组成的一个数据组织。
常见的关系型数据库:mysql,oracle,SQL Server
存储方式:行存储,一个表里每一个对象的记录存储一行,一行里包括了该记录的所有特征
优点:
1)复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
2)事务支持使得对于安全性能很高的数据访问要求得以实现。
缺点:
1)不擅长大量数据的写入处理
2)不擅长为有数据更新的表做索引或表结构(schema)变更
3) 字段不固定时应用不方便
4)不擅长对简单查询需要快速返回结果的处理
使用场景:
1)需要做复杂处理的数据;
2)数据量不是特别大的数据;
3)对安全性要求高的数据;
4)数据格式单一的数据;
非关系型数据库
NoSQL,泛指非关系型的数据库。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用。
常见的非关系型数据库:
(1)键值对存储(key-value):Redis键值对存储,优势:快速查询,缺点:存储数据缺少结构化。
(2)列存储:Hbase,优势:快速查询,扩展性强。缺点:功能相对于局限。
(3)文档数据库存储:MongoDB,早起应用多。优势:要求不特别的严格。缺点:查询性不高,缺少统一查询语法。
(4)图形数据库存储:应用于社交网络,优势:利用图结构相关算法。缺点:需要整个图计算才得出结果,不容易做分布式集群方案。
存储方式:
以列为单位进行数据的存储,一列作为一个记录,每个对象的记录会存储多行,各行相对独立;
优点:
1)nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。
2)nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。
3)nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
4)不支持Join处理,各个数据都是独立设计的,很容易把数据分散在多个服务器上,故减少了每个服务器上的数据量,即使要处理大量数据的写入,也变得更加容易,数据的读入操作当然也同样容易。
缺点:
1)无法对表进行复杂的计算,不支持join等功能。
使用场景:
1)海量数据存储;
2)多格式的数据存储;
3)对查询速度要求快的数据存储;
https://blog.csdn.net/weixin_46043015/article/details/107896330
spring帮我们做了什么,spring mvc做了什么,分发,静态资源配置,aop,IOC,设计模式用了什么
轻量级框:Spring是轻量级框架,基本的版本大约2M
Spring Bean 的生命周期:实例化 -> 属性赋值 -> 初始化 -> 销毁
Spring支持面相切面的编程,并且把应用业务逻辑和系统分开,可以很方便的实现对程序进行权限拦截和运行监控等功能
是框架会扫描注解,通过反射方法读出注解,然后执行对应的方法
为什么用SpringMVC?
很多应用程序的问题在于处理业务数据的对象和显示业务数据的视图之间存在紧密耦合,通常,更新业务对象的命令都是从视图本身发起的,使视图对任何业务对象更改都有高度敏感性。而且,当多个视图依赖同一个业务对象时是没有灵活性的。
SpringMVC基于java,实现了web MVC设计模式,请求驱动类型的轻量级web框架,使用了MVC架构模式思想,将web层进行职责解耦。基于请求驱动指的是使用请求-响应模型。
依赖的jar需要一个个配置,非常繁琐
Spring Boot:基于spring,为了解决使用spring框架时配置繁多、部署流程复杂、开发效率低等问题,可以创建独立的应用程序,嵌入tomcat、jetty等,可以直接启动应用程序而不需要外部的容器。同时,spring boot可以自动配置spring应用
- tcp/ip协议
- http和https区别,优缺点,保证内容不被篡改,传输的都是密文,不担心被看到,80,https慢一些,有加密解密过程,端口号443,配置https
• TCP面向连接,可靠传输,相对于UDP效率低,http的get,post等方法使用的就是TCP连接传输数据
• UDP没有连接,不可靠,但是传输速度快,用于视频会议等场合
TCP拥塞控制有四种算法:慢开始,拥塞避免,快重传,快恢复。
长连接:连接->传输数据->保持连接 -> 传输数据-> …->直到一方关闭连接,客户端关闭连接。
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。
短连接:连接->传输数据->关闭连接。
比如HTTP是无状态的的短链接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。因为连接后接收了数据就断开了,所以每次数据接受处理不会有联系。这也是HTTP协议无状态的原因之一。
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP都需要三次握手,处理速度会降低很多,所以每个操作完后都不断开,处理时直接发送数据包就OK了,不用建立TCP连接。
短连接:像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,资源情况和系统压力难以想象。
所以并发量大,但每个用户无需频繁操作情况下需用短连好。
短链接:我们模拟一下TCP短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起close操作。
长链接:模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
粘包拆包场景
因为TCP是面向字节流的操作,没有边界,操作系统在发送TCP数据时,会通过缓冲区进行优化
粘包:如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送。
拆包:如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送
为什么UDP没有粘包?
粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。
常见的解决方案
Netty对解决粘包和拆包的方案做了抽象,提供了一些解码器(Decoder)来解决粘包和拆包的问题。如:
可以扩展HttpServletRequestWrapper并覆盖getHeaderNames();,其中您可以返回另一个枚举,只添加了必需的标题。
单例怎么避免反射构造对象:增加一个标识位,如通过增加一个布尔类型的 ideal 标识,保证只会执行一次,更安全的做法,可以进行加密处理,保证其安全性
linux基本命令,加分项是线上确定日志确定问题在哪里,项目日志文件里面的,vim的命令,查找关键信息
grep “” *.log
tail -200 *.log
tail -200f *.log
tail -200 *.log | grep -a3 -b3 “”
ps -ef | grep “java”
log4j2,配置日志
降级的目的是为了保证核心服务可用
降级可以有几个层面的分类:自动降级,人工降级;按照功能可以分为:读服务降级和写服务降级;
1.对一些非核心服务进行人工降级,在大促之前通过降级开关关闭那些推荐内容,评价等对主流程序没有影响的功能
2.故障降级,比如调用的远程服务挂了,网络故障,或者RPC服务返回异常。那么可以直接降级,降级的方案比如设置默认值,采用兜底数据(系统推荐的行为广告挂了,可以提前准备静态页面做返回)等等
3.限流降级,在秒杀这种流量比较集中并且流量特别大的情况下,因为突发访问量特别大可能导致系统支撑不了。这个时候可以采用限流来限制访问量。当达到阈值时,后续的请求被降级,比如进入排队页面,比如跳转到错误页面(活动火爆,请稍后重试)
秒杀系统与原有电商系统分开部署
商品页面 进行静态化缓存,只访问服务器中商品的动态数据
下订单时,查看是否重复下单,查看redis,key为userId,value为用户下单的商品id集合
依据个人认知总结,如有技术性问题烦请指正!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。