当前位置: 首页 > news >正文

自已做网站小程序制作开发定制

自已做网站,小程序制作开发定制,做软件多少钱,做网站被捉JAVA基础 面向对象和面向过程的区别 面向过程#xff1a;基于步骤的编程方式#xff0c;用函数把这些步骤一步一步地实现#xff0c;然后在使用的时候一一调用则可 面向对象#xff1a;基于对象的编程方式#xff0c;通过定义类来描述对象的属性和行为#xff0c;面向对… JAVA基础 面向对象和面向过程的区别 面向过程基于步骤的编程方式用函数把这些步骤一步一步地实现然后在使用的时候一一调用则可 面向对象基于对象的编程方式通过定义类来描述对象的属性和行为面向对象具有封装继承多态三种特性 封装继承多态 封装将代码封装起来只暴露对外的接口提高了安全性和代码复用 继承继承指子类对父类的继承子类可以拥有父类的方法和属性也可以自定义方法和属性 多态类和类之间父类引用指向子类对象同一种操作作用于不同的对象在不同情况下可以有不同的行为。 Integer的valueOf方法 public class Main {public static void main(String[] args) {Integer i1 100;Integer i2 100;Integer i3 200;Integer i4 200;// trueSystem.out.println(i1i2);// falseSystem.out.println(i3i4);} } Integer i 100 底层是调用了 valueOf()可以看出如果数值在[-128,127]之间便会返回已经存在的对象的应用否则新建一个对象 public static Integer valueOf(int i) {if(i -128 i IntegerCache.high)return IntegerCache.cache[i 128];elsereturn new Integer(i); } 重写和重载 重写 发生在父类与子类之间 方法名参数列表返回类型除过子类中方法的返回类型是父类中返回类型的子类必须相同 访问修饰符的限制一定要大于被重写方法的访问修饰符publicprotecteddefaultprivate) 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常 重载 重载Overload是一个类中多态性的一种表现 重载要求同名方法的参数列表不同(参数类型参数个数甚至是参数顺序) 重载的时候返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准 JDK8新特性 接口默认方法java8中接口可以添加 default 修饰的方法并且可以有方法实现。 lambda表达式用更简洁的方式编写匿名函数可以用于遍历、排序、过滤等操作 Stream流用于处理集合数据提高代码可读性和简洁性 Optional类用于处理可能为空的值避免空指针 接口和抽象类 抽象类可以有构造器接口不行 抽象类可以有抽象方法和具体方法接口只能有方法定义 抽象类中的成员可以是private, default, protected, public接口只能是public除了默认方法 抽象类可以定义成员变量接口中定义的成员变量实际上都是常量接口中的成员变量默认被 static final 修饰。 有抽象方法的类必须是抽象类抽象类未必要有抽象方法 单继承多实现 使用场景 抽象类有具体概念的建议用抽象类比如animal 接口共同事物之间具有的共同特征比如flyable 数组下标为甚从0开始 数组有个寻址公式数组首地址数组下标*数组中元素类型的大小。如果从1开始对于cpu来说多了一个减法指令。 ArrayList底层的实现原理 底层用数组实现 初始容量为0当第一次添加元素的时候会初始化容量为10 后续进行扩容的时候都是原来的1.5倍每次扩容都需要拷贝数组。 用Arrays.asList转List后如果修改了数组内容list受影响吗List用toArray转数组后如果修改了List内容数组受影响吗 Arrays.asList返回的ArrayList不是真正的ArrayList而是Arrays的一个内部类他们都是指向同一个内存地址所以会有影响 list.toArray是通过数组拷贝所以不会影响 CopyOnWriteArrayList的底层实现 内部也是通过数组实现新添加元素时会复制出新数组在新数组上写 写操作结束时会把原数组指向新数组并且写操作会通过ReentrantLock加锁 CopyOnWriteArrayList允许在写操作时进行读操作读的时候直接在旧数组上读提升了读的性能适合读多写少且有线程安全需求的场景 Hashcode的作用 java的集合有两类一类是List还有一类是Set。前者有序可重复后者无序不重复。当我们在set中插入的时候怎么判断是否已经存在该元素呢可以通过equals方法。但是如果元素太多用这样的方法就会比较慢。 hashCode方法可以这样理解它返回的就是根据对象的内存地址换算出的一个值。这样一来当集合要添加新的元素时先调用这个元素的hashCode方法就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素它就可以直接存储在这个位置上不用再进行任何比较了如果这个位置上已经有元素了就调用它的equals方法与新元素进行比较相同的话就不存了不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了几乎只需要一两次。 hashcode和equals的关系 alibaba开发手册中有提到重写了equals方法也要重写hashcode方法 如果两个对象的hashcode不同则他们一定不同 如果两个对象的hashcode相同他们也不一定是同一个对象 如果两个对象相等则他们的hashcode一定相同 hashmap 1.7和1.8的区别 1.7采用数组链表1.8采用数组链表红黑树加红黑树是为了提高添加和查询的速度 1.7中链表插入使用的是头插法1.8是尾插法采用尾插法是为了避免多线程情况下同时竞争往链表头部插入节点造成死循环。 1.7中的hash算法更复杂包含各种位运算和异或运算1.8则进行了简化 map接口 HashMap: 存储键值对是线程不安全的 在jkd1.7之前采用数组链表当存入一个新元素时会先通过对key进行hash运算得到一个hash值也就是即将插入的位置即桶号随后判断这个位置hash值是否有元素若没有则直接添加若有则产生了hash冲突采用拉链法解决即生成一个链表并插入链表在插入链表前会先遍历链表中已经存在的节点并对它们的key进行 equals()比较判断新加入的节点是否和已经存在的节点的 key 相等若相等则覆盖不等则插入 在jdk1.8开始采用数组链表红黑树加红黑树是为了提高添加和查询的速度当链表长度8时会转为红黑树将链表转换成红⿊树前会判断如果当前数组的⻓度⼩于64那么会选择先进⾏数组扩容⽽不是转换为红⿊树只有当数组容量达到 64 且链表长度大于 8 时才会将链表转换为红黑树。 负载因子是指元素的数量与数组的数量之比也就是说当元素的数量负载因子*数组的数量时就会对数组进行扩容负载因子默认是0.75 TreeMap:有序 Map 底层实现为红黑树根据键的顺序进行排序也可以自定义排序 LinkedHashMap基于哈希表和双向表实现的有序 Map。 它维护了一个按照插入顺序排序的双向链表因此能够保证遍历 Map 时的顺序与插入时的顺序相同。 ConcurrentHashMap:线程安全的hashmap 在1.7之前底层由多个segment数组组成每个segment数组包含一个hashentry数组用于存储键值对。在并发时每个 Segment维护着自己的锁这样不同的线程可以同时访问不同的 Segment从而提高并发访问的效率。Segment 的数量默认为 16 在1.8开始底层实现由数组链表红黑树并且不再使用分段锁而是cas和synchronized每次只锁住当前链表或红黑树的根节点保证并发效率。 具体如下 如果当前存储桶没有元素则使用CAS操作将元素插入桶中 如果当前存储桶已经存在元素则使用synchronized锁住对应的桶在锁的保护下只能有一个线程能操作链表/红黑树就不会有并发的问题效率也得以提升。 CASCompare And Swap是一种乐观锁技术它认为多个线程不会共享同一份数据所以在实行cas操作时首先会读取该数据的当前值并预期当前值不会被其他线程修改。如果后面读取到的数据与当前值相同则表示没有被其他线程修改则可以更新该数据的值。若不同则表示被其他线程修改则返回失败。cas只能保证单个共享变量的原子性若要保证多个共享变量多个键值对的原子性需要加锁 hashtable: 线程安全效率低不推荐使用 在每个方法都使用了synchronized来保证线程安全性能差 hashmap的扩容机制原理 1.7版本 先生成新数组容量是原来的两倍 遍历老数组中每个位置上的链表中的元素 用链表中的元素的key与新数组的长度进行计算得到元素存放在新数组中的下标位置 将元素添加到新数组中 1.8版本 先生成新数组容量是原来的两倍 遍历老数组中的每个位置上的链表或红黑树 如果是链表则遍历链表中的元素用元素的key与新数组的长度计算得出存放在新数组中的下标位置 如果是红黑树则遍历红黑树先计算出每个元素应该存放在新数组的下标位置 然后统计新数组中每个下标位置的元素个数 如果该位置下的元素数量超过了8则生成一个红黑树存储元素 如果没有超过8则生成链表存储元素 HashMap寻址算法 计算对象的hashcode() 再进行hash()进行二次哈希二次哈希就是将得到的hashcode右移16位再异或运算让哈希分布更均匀 最后通过(capacity-1)hashcode得到数组下标位置 为什么hashmap的数组长度是2的次幂 可以使用位与运算替代取模运算提高运算效率例如当长度为8时它的二进制表示为1000长度为16时二进制表示为10000二进制位数最多可让散列均匀地分布在每个数组下标中 HashMap1.7多线程死循环的问题 jdk1.7链表采用头插法假设两个线程链表是A-B。 线程1准备扩容时线程2进来了 线程2这时也准备扩容然后因为是头插法链表顺序会颠倒从A-B变成B-A 轮到线程1也会颠倒变成A-B但是由于线程2之前的操作导致B的next指向了A最终导致B-A-B产生循环 jdk8采用尾插法避免这种问题 ConcurrentHashMap的扩容原理 1.7 基于分段锁 每个segment存放一个entry数组存储键值对 每个entry数组都可以进行扩容和1.7的hashmap类似 先生成新的数组然后转移元素到新数组中 扩容的判断也是各个segment内部单独判断是否超过阈值 1.8 不基于分段segment 当某个线程进行put时如果发现ConcurrentHashMap正在进行扩容那么该线程调用hepltransfer()一起进行扩容 如果某个线程put时发现没有正在进行扩容则将key-value添加到ConcurrentHashMap中 然后判断是否超过闻值超过了则进行扩容 ConcurrentHashMap是支持多个线程同时扩容的 扩容之前也先生成一个新的数组 在转移元素时先将原数组分组将每组分给不同的线程来进行元素的转移每个线程负责一组或多组的元素转移工作 hashmap和hashtable区别 父类不同HashMap继承自AbstractMap类HashTable继承自Dictionary类。不过都实现了map, Cloneable, Serializable这三个接口 对null的支持不同HashMap只支持一个为null的key为null的value可以有多个。HashTable都不支持。 线程安全不同 hashtable和hashmap都使用iterator迭代器hashtable也支持Enumeration hashtable数组默认大小11增长方式是2倍1hashmap默认16增长原来的2倍 Java的四种引用强弱软虚 强引用内存不足也不会被回收 软引用内存不足就会被回收 弱引用被jvm垃圾回收期发现就会被回收 虚引用比较特殊的引用类型为了能够在对象被回收时收到系统的通知 cookie session cookie存放客户端session服务端 cookie保存字符串session保存对象 cookie可以直接从浏览器获取不安全session存放在服务器相对安全 cookie大小在4kbsession一般没有限制 cookie支持跨域session不支持 localStorage sessionStorage localStorage永久存储除非手动清除sessionStorage临时存储关闭浏览器就清除 localStorage和sessionStorage都只能存储字符串类型如果存储其他类型编译器会自动toString()转成字符串来存储 内部类 静态内部类定义在类内部的静态类 静态内部类可以访问外部类的所有静态变量和方法即时是private也可以 其他类使用静态内部类可以这样 Outer.Inner inner new Outer.Inner(); 成员内部类定义在类内部的非静态类不能定义静态方法和变量final修饰的除外因为类初始化时是先初始化静态成员如果成员内部类允许定义静态变量那么初始化时的顺序是有歧义的外部类初始化时会先初始化静态成员而此时内部类尚未初始化导致内部类中的静态变量无法得到有效的初始化。。 局部内部类在方法中的类如果一个类只在某个方法中使用则可以考虑 匿名内部类要继承一个父类或实现一个接口因为匿名内部类需要隐式地继承外部类或实现某个接口才能进行方法的覆盖或实现使用new来生成一个对象的引用来重写父类的方法适用于一次性特定需求的场景可以简化代码并提高可读性。 泛型 泛型意味着编写的代码可以被不同类型的对象所重用使我们不必因为添加元素类型的不同而定义不同类型的集合 泛型擦除 编译器在编译时擦除了所有类型相关的信息在运行时不存在任何类型相关的信息也就是说泛型只存在于编译阶段而不存在于运行阶段。使用类型擦除的主要目的是确保能和 JDK1.5 之前的代码进行兼容并且实现简单几乎不需要更改 JVM代码 泛型的extend和super 泛型中经常会有extend和super来修饰 ? extend T表示包括T在内的所有T的子类 ? super T表示包括T在内的所有T的父类 如果在声明自定义集合类的时候定义了泛型则以定义的泛型为主如果没有定义泛型则以自定义集合类上的泛型为主。 java中有哪些类加载器 AppClassLoader是自定义类加载器的父类负责加载classpath下的类文件 ExtClassLoader是AppClassLoader的父类加载器负责加载 %JAVA_HOME%/lib/ext文件夹下的jar包和class类 BootStrapClassLoader是ExtClassLoader的父类加载器负责加载 %JAVA_HOME%/lib下的jar宝和class文件 类加载器双亲委派模型 AppClassLoader的父加载器是ExtClassLoader的父加载器是BootstrapClassLoader JVM在加载一个类时会调用AppClassLoader的方法来加载类不过会先使ExtClassLoader的方法来加载类。同样ExtClassLoader方法中会先调用BootstrapClassLoader来加载类如里BootstrapClassLoader加载到就直接。如里BootstrapClassLoader没有加载到ExtClassLoader就会自己尝试加载该类如果没有加载到那么则会由AppClassLoader来加载这个类。 所以双亲委派指得是JVM在加载类时会委派给ExtClassLoader和BootstrapClassLoader进行加载如果没加载到才由自己进行加载. 为什么采用双亲委派 为了安全保证类库API不会被修改只有经过验证的类才能被加载 通过双亲委派机制可以避免某一个类被重复加载当父类已经加载后则无需重复加载保证唯一性。 浅拷贝和深拷贝 浅拷贝如果是基本类型则拷贝基本类型的值如果是应用类型拷贝的就是地址即两个对象指向同一个地址修改其中一个对象的值另一个对象也会受到影响 深拷贝深拷贝是将一个对象从内存中完整的拷贝一份在堆空间内开辟一个新的区域存放新对象两个对象是独立互不影响的 static的用法 静态变量、方法被static修饰的变量/方法只属于类不属于某个对象实例它在类加载时初始化且仅初始化一次可以直接通过类名.访问。静态方法只能访问类的静态方法和静态变量如果要访问非静态方法/变量只能通过对象实例来访问。 静态代码块被static修饰的代码块只在类加载时初始化一次且优先于构造函数。多用于初始化操作 静态内部类定义在类中的静态内部类只能访问外部类的静态方法和变量如果要访问非静态方法/变量只能通过对象实例来访问。 静态导包可以直接调用方法名而不需要类名 import static java.lang.Math.*; public class Test{public static void main(String[] args){//System.out.println(Math.sin(20));传统做法System.out.println(sin(20));} } final有哪些用法 修饰类被final修饰的类不能被被继承 修饰方法不能被重写因为不能被重写所以在编译时期就可以确定调用哪个方法因此jvm在运行时会将该方法的字节码嵌入到调用该方法的位置而不是函数调用的形式这个过程叫做内联内联可以避免函数调用的开销提高程序运行效率 修饰变量被final修饰的变量称作常量不能被修改如果修饰的是引用如数组则引用不可变引用指向的内容可变。同时常量会在编译阶段存入常量池中 final int x 1; x 2; // 这会报错因为x的值不能被改变final int[] arr {1, 2, 3}; arr new int[]{4, 5, 6}; // 这会报错因为arr引用不能指向其他对象 arr[0] 4; // 这是可以的因为arr指向的对象的内容可以改变 常量池 编译时常量池 编译阶段生成的常量池用于存储字面量和符号引用 运行时常量池运行时常量池在类加载完毕后将每个编译时常量池中的符号引用值转存过来这样每个class都有个运行时常量池。类被解析时将符号引用替换成直接直接应用。这样做的目的是为了在运行时能够快速定位到所需的对象从而加快程序运行速度。 tip: 字面量可以理解为实际值int i 10; char a a; 这里的10和a都是字面量。符号引用是编译时期生成的、以字符串形式表示的符号名称它并不直接指向内存中的地址 try catch finallytry里有returnfinally还执行么 执行finally在try里的return前执行。 不管有无异常finally都会执行 try和catch都有returnfinally也会执行 finally是在try中的return后面的表达式运算后才执行的就是说表达式运算后但并没有返回而是先保存要返回的值不管finally如何执行返回的值都不会改变仍然是之前保存的值 finally中最好不要包含return当try和finally都有return时会忽略try的return执行finally的return。 Exception与Error 他们都继承自顶级父类Throwable Throwable可分为三种结构被检查异常CheckedException运行时异常RuntimeException错误Error。 运行时异常: RuntimeException及其子类都被称为运行时异常。java编译器不会检查它就是说当程序出现运行时异常时就算没有抛出、捕获还是会编译通过常见的运行时异常 ClassCastException IndexOutOfBoundsException NullPointerException 被检查异常Exception类本身以及Exception类下的非运行时异常及其子类的都属于被检查异常这类异常会被java编译器检查若不捕获或抛出则无法通过编译常见的被检查异常 IOException FileNotFoundException SQLException 错误Error类及其子类和运行时异常一样编译器不会对Error进行检查。程序本身无法修复这种出现错误会导致程序终止运行常见如下 VirtualMachineError OutOfMemoryError 线程、进程、程序 进程 进程是程序的一次执行过程是系统运行程序的基本单位一个进程包含一个或多个线程一个线程同时只能被一个进程拥有不同的进程使用不同的内存空间 线程 与进程相似线程是比进程更小的执行单位与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源。因此系统在产生一个线程或多个线程切换时负担要比进程小得多。 程序 程序是含有指令和数据的文件简单说程序就是静态的代码。 并发和并行 并发看起来同时进行利用时间片轮转实现多个任务的交替执行 并行真正同时进行依靠多个执行单元实现多个任务同时进行 线程有哪些基本状态 NEW: 初始状态 Runnable: 运行状态就绪和运行统称为运行中 Blocked: 阻塞状态调用同步方法时没有获取到锁会进入该状态 Waiting: 等待状态通过Object.wait()等待Object.notify()唤醒 Time_Waiting: 超市等待通过Object.wait(long)或Thread.sleep(long)置于该状态到时见会自动回到Runnable状态 Terminated: 终止状态线程执行完毕 线程池有哪些状态 RUNNING会接受新任务且会处理队列中的任务 SHUTDOWN不会接收新任务但会执行队列中的任务任务处理完会中断所有线程 STOP不会接收新任务也不会执行队列中的任务会立即中断所有线程 TIDYING所有线程停止后状态会变为TIDYING一但达到此状态就会调用线程池的terminated() TERMINATEDterminated()执行后会变为此状态 Java 序列化中如果有些字段不想进行序列化怎么办 transient被transient修饰的变量序列化时不会被保存到序列化流中反序列化时会被设为默认值。 java中IO流 按照流的流向分输入流和输出流 按照操作单元分字节流和字符流 按照流的角色划分节点流和处理流 java中io流有很多个类但基本都是从这四个抽象类派生出来的 InputStream OutputStream Reader Writer 反射原理 在运行时对于任意一个类都可以知道他的方法和属性对于任意一个对象都可以调用他的任意一个方法这种动态获取类信息以及动态调用对象方法的功能就是反射机制 反射的优缺点 优点 能够动态获取类的信息动态调用对象方法 与动态编译结合 缺点 性能低需要解析字节码 解决方法 若是多次创建一个实例时有缓存会快很多 通过setAccessible(true)来关闭Jdk的安全检查 相对不安全破坏了封装线因为通过反射能够获取私有方法和属性 IO和NIO IO是面向流的NIO是面向通道缓冲区的。IO面向流意味着每次从流中读取一个或多个字节直至读取完而NIO意味着数据可以从通道读取到缓冲区然后从缓冲区中处理。同样数据也可以写入缓冲区然后再通过通道写出 IO是阻塞的NIO既支持非阻塞也支持多路复用多路复用允许单个线程同时监听多个socket IO只能单向NIO可以双向 多路复用 假设开发一个聊天系统需要同时管理多个客户端操作传统io的话需要为每个客户端维护一个线程进行读写操作客户端数量多时会造成资源浪费性能下降。使用 多路复用技术可以使用一个选择器来管理所有客户端连接每个客户端连接对应一个通道这些通道都注册在选择器上。选择器会不断轮询这些通道检查是否有数据需要读写这样就只需要用一个线程来管理多个客户端连接。 IO事件的模型 这三种都是NIO的实现方式都属于多路复用技术 select模型使用数组来存储Socket连接文件描述符容量固定需要通过轮询来判断是否发生了IO事件 poll模型使用链表来存储Socket连接文件描述符容量不固定需要通过轮询来判断是否发生了IO事件 epoll模型epoll跟poll是完全不同的epoll是一种事件通知模型当发生了IO事件时才进行IO操作不需要像poll那样去主动轮询 java程序是如何执行的 先把java代码编译成字节码文件.java-.class) 再将class文件放置到jvmjvm使用类加载器装载class文件 装载完毕后进行字节码校验 校验通过后jvm会把字节码文件翻译成机器码由操作系统执行。 设计模式 工厂模式 简单工厂 所有的产品共有一个工厂如果新增产品则需要修改代码违反开闭原则 BeanFactory就是采用这种 工厂方法模式 给每个产品都提供一个工厂不同工厂负责对应的产品生产遵循开闭原则 项目中用的多 抽象工厂方法模式 工厂方法针对的是一个产品等级结构而抽象工厂方法模式则面向多个产品等级结构。 如果有多个种类的产品需要生产时优先建议这种 当某个品牌要增加新的产品时所有工厂类都需要修改 策略模式 主要角色分为 抽象策略类这是一个抽象角色通常是个接口或抽象类给出所有的具体策略类所需的方法 具体策略类实现了抽象策略类中的方法提供具体的实现 环境类持有策略的引用给客户端调用 比如旅游出行 抽象策略类 Strategy接口中定义了traval方法 具体策略类汽车飞机高铁等实现了Strategy接口并重写了traval方法 环境类TravalContext则提供了选择哪一项出行方式的方法。 JVM JDK和JRE和JVM区别 jdkjava标准开发包包含jre如果要开发java程序就需要安装jdk jrejava运行环境包含jvm如果只是要运行java字节码文件只安装jre就可以了 jvmjava虚拟机 编写java代码也可以在txt文件写如果要编译需要通过jdk中的javac编译器编译后的字节码文件需要通过jvm来执行并翻译成各个平台的机器码。 JVM内存模型 线程独占栈、本地方法栈、程序计数器 线程共享堆、方法区 栈又称方法栈是线程私有的线程执行方法时会创建一个栈帧用来存储执行方法的信息。调用方法时执行入栈方法返回时执行出栈。 本地方法栈与栈类似也是用来存储执行方法的信息。执行java方法时使用栈执行Native方法其他语言编写的方法时使用本地方法栈。 程序计数器记录当前线程正在执行的字节码指令的地址每个线程都有独立的计数器并且只为java方法服务如果是native方法程序计数器的值为undifined。 堆堆被线程共享存放对象的实例当对没有可用空间时会报OOM异常。堆是垃圾回收的主要区域根据对象的存活周期不同JVM把对象进行分代管理由垃圾回收期进行垃圾回收 方法区被线程共享主要存储类的信息静态变量常量即时编译器优化后的代码等数据jvm启动时创建关闭时释放 方法内的局部变量是否线程安全 如果方法内局部变量没有逃离方法的作用范围也就是没有形参也没有返回则线程安全 如果局部变量引用了对象并逃离了则线程不安全 分代回收 新生代堆内存中的一块区域用于存储新创建的对象新生代通常只占用堆内存的一小部分。当新生代中的对象经过数次垃圾回收后仍然存活就会移动到老年代中。 老年代也是堆内存中的一块区域用于存储长时间存活的对象。占用堆内存的大部分空间。 持久代jdk8被元数据区替代也称为方法区用于存储已被虚拟机加载的类信息、常量、静态变量以及即时编译器优化后的代码等数据。 堆和栈的区别 功能不同栈用于存储局部变量、对象的引用、方法调用堆存储java的对象。 共享性不同栈线程私有堆线程共享 异常错误不同java.lang.StackOverFlowError和java.lang.OutOfMemoryError 空间大小不同栈的空间远小于堆 垃圾回收的过程 Minor GC也称为Young GC只对新生代进行垃圾回收由于java对象大多存活时间不长所以Minor GC发生频繁回收速度也快。 Full GC: 也称为 Major GC 全局性的垃圾回收方式,通常会在老年代空间不足时触发在执行Full GC时会暂停所有应用线程所以会导致较长时间的停顿。 垃圾回收器的不同类型 针对新生代 PS GCParallel Scavenge GC基于分代收集算法的垃圾回收器。在新生代使用复制算法老年代采用标记-清除或标记-压缩算法是Server模式下默认的垃圾回收器。 Serial GC单线程收集器垃圾回收时必须暂停其他线程 NewPar GC是基于Serial收集器的多线程版本是Server模式下首选的垃圾回收器。 针对老年代 CMS GCConcurrent Mark Sweep Garbage Collector分布标记清除垃圾回收期能够在线程运行的同时进行垃圾回收减少停顿时间。 针对整个堆空间 G1 GC在jdk9成为默认垃圾回收器G1 GC在采用了多线程并行的同时将堆内存分成了多个区域每个区域都可以充当edensurvivoroldhumongous其中humongous专为大对象准备通过智能地选择性回收来保证效率 ZGC一种面向大堆低延迟的垃圾回收期它将堆空间分成了多个碎片来管理能够在非常段的停顿时间处理大量垃圾避免传统GC的长时间停顿。 tips: G1 GC和ZGC不需要和其他垃圾收集器搭配使用 垃圾回收算法 标记清除算法首先标记出所有需要回收的对象标记完成后统一回收掉被标记的对象。 标记压缩算法首先标记出所有需要回收的对象然后让所有存活的对象往一端移动然后清理端边界以外的对象。 复制算法将可用内存按容量分成大小相等的两块每次只是用其中的一块。当这块空间用完后就将还存活的对象复制到另一块然后再把使用过的内存一次清理掉 分代收集算法把堆分成新生代和老年代。新生代中每次垃圾回收都有大量对象死亡只有少量存活所以新生代一般采用 复制算法 老年代中对象存活率高又没有额外的空间担保所以采用 清除-标记 或 清除-压缩 。 什么时候会触发FullGC 调用System.gc() 老年代空间不足 老年代只有当新生代对象转入或创建过大的对象、数组时才会出现不足的现象当执行FullGC后空间仍然不足便会抛出OOM异常。 解决 尽量使对象在Minor GC阶段针对新生代的垃圾回收动作被回收尽量减少老年代对象的数量以及不要创建过大的对象和数组。 持久代空间满 当系统中要加载的类、调用的方法过多时持久带可能会满执行 FULL GC 时仍然回收不了则会抛出java.lang.OutOfMemoryError: PermGen space 解决 增大持久带空间或配置 CMS GC 与Full GC相比CMS GC具有更高的回收效率和更短的停顿时间因此可以更好地解决PermGen空间不足的问题。。 什么是JVM为什么称为平台无关性 JVM是一个可以执行java字节码的虚拟机系统。 java源文件被编译成字节码文件JVM负责将字节码转换成特定平台的机器语言从而实现了平台无关性。 什么是直接内存 直接内存不属于jvm的内存结构不由jvm管理由操作系统管理的内存区域 一般用于NIO操作用于数据缓冲区读写性能高不受jvm垃圾回收控制 对象分配规则 对象优先分配在Eden区当Eden区空间不足时会执行 Minor GC 幸存的对象会被分配到survivor。 大对象需要大量连续内存空间的对象直接放入老年代。 在survivor长期幸存的对象放入老年代。虚拟机为每个对象定义了一个年龄计数器如果Eden区的对象经过了一次Minor GC对象就会进入survivor区往后每次GC时对象的计数器都会1达到一定阈值则会进入老年代。 空间分配担保新生代在发生 Minor GC 前会先检查老年代最大可用的连续空间是否大于新生代中所有对象之和或历次晋升老年代的对象的平均大小若不成立则进行Full GC若成立则会进行Minor GC不过这次Minor GC是有风险的可能出现大量对象在 Minor GC 后仍然存活 解决方法 调整堆内存的大小和新老生代 选择更合适的垃圾回收器 优化应用程序代码 java对象创建过程 jvm在执行new指令时会先去检查常量池中是否已经定义了这个对象的符号引用如果没有则进行类加载过程解析如果有则直接通过符号引用定位到类的加载信息。 类加载完成后需要为新对象分配空间有多种方法一种是“指针碰撞”一种是“空闲列表”不过最常用的是 TLAB本地线程缓冲分配。 分配空间后jvm会将对象的内存空间初始化为0 设置对象头包括对象的类型指针、GC 相关的标志位等 jvm对象分配空间的方法: 指针碰撞用于java堆中内存是绝对规整的情况 空闲列表用于并不是规整的情况 TLABTLAB技术会在堆中预先分配一小块内存称为本地线程分配缓冲区。当线程需要分配内存时会先在自己的缓冲区中分配这样每个线程都可以在自己的缓冲区快速分配内存而不需要与其他线程竞争。 java对象的结构 包括三部分 对象头由两部分组成第一部分存储对象自身的运行时数据第二部分是指针类型指向对象代表的是哪个类。 实例数据存储对象真正的有效信息。 对齐填充jvm要求对象起始地址必须是8字节的整数倍 如何判断对象可以被回收 引用计数每个对象有一个引用计数属性新增一个引用时计数1释放时计数-1为0时表示可以回收此方法简单但无法解决对象相互循环引用的问题。 可达性分析以GC Roots为起点向下搜索搜索所走过的路径称为引用链当一个对象到GC Roots没有任何引用链相连时则该对象可以被回收。 哪些对象可以作为GC Root 栈中引用的对象比如new的一个对象 被静态属性引用的对象 常量引用的对象 jvm监控分析命令 jpsJVM Process Status Tool用于查看当前运行的java进程及其信息可以用来获取进程id。 jinfo用于查看或修改当前运行的java进程的参数和属性可以用来动态调整部分参数 jstack用于生成当前java进程的所有线程快照可以用来分析线程状态和死锁问题 jstat用于监视jvm运行时状态信息它可以显示jvm进程中的内存垃圾回收等信息。 jstat -gcutil pid 毫秒值 jmap查看各个区域的的使用情况 jvm调优在哪设置参数 war包部署的话在tomcat的配置文件 catalina.sh 中配置 jar包部署的话在启动时设置参数比如 java -Xms512m -Xmx1024m -jar xxx.jar 如何jvm调优 对于还在正常运行的系统 可以使用jmap查看各个区域的使用情况 通过jstack查看线程的运行情况 通过jstat查看垃圾回收的情况特别是FullGC如果FullGC频繁就需要jvm调优 通过各个命令的结果或者jconsole或jvisualvm工具来分析 首先如果频繁发生fullGC又没有出现内存溢出说明FullGC回收了很多对象出现这种情况可能是因为对象太大所以直接放入了老年代所以这些对象最好能在minorGC就回收掉避免这些对象进入老年代可以修改年轻代大小或者调整年轻代和老年代的比例 还可以通过jstack找到占用cpu最多的线程定位到具体的方法优化这个方法 对于已经发生OOM 通过设置参数 -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/tmp/heapdump.hprof 使系统发生OOM时生成dump文件 或者通过命令 jmap -dump:formatb,fileheap.hprof pid 获取dump文件 可以利用jvisualvm来分析dump文件 根据dump文件找到异常的实例对象和异常的线程定位到具体代码 第一种方法需要OOM才会生成dump文件第二种则是可以在运行时手动获取不需要等到异常 如何排查cpu飚高 使用top命令查看进程占用cpu的情况 通过ps命令查看占用cpu最高的进程 使用jstack查看进程中是哪些线程有问题 调优工具 jdk自带的 jconsole是jdk自带的图形化性能监控工具能够用于Jvm中内存、线程和类等的监控 jvisualvmjconsole的升级版整合了jstackjmapjinfo等调试工具的功能 jvm性能调优 -Xmx堆内存最大限制通常可以设置为系统可用内存的70% -Xms 堆内存初始大小通常这个参数的值与 -Xmx 一致 -Xmn 新生代大小可以设置为整个堆内存的1/4到1/3 -XX:NewSize 新生代初始大小 -XX:NewRatio 新生代老生代的占比 -XX:SurvivorRatio Eden和Survivor的占比 -Xss 栈的大小一般设置在256k到512k -XX:PermSize 永久代初始大小 -XX:MaxPermSize 永久代最大限制 设定垃圾回收器 年轻代用 -XX:UseParNewGC 老年代用 -XX:UseConcMarkSweepGC 简述垃圾回收机制 程序员不需要显式的去释放对象而是由jvm自行执行在jvm中有一个垃圾回收线程是低优先级的在正常情况下不会被执行只有当虚拟机空闲或堆内存不足时才会触发执行。 你有没有遇到过OutOfMemory问题你是怎么来处理这个问 题的 创建了过大的数组导致OOM异常 根据实际需要创建适当长度的数组或者通过 -Xmx 调整堆空间的大小。 OOM的常见原因 一次性从数据库取太多数据一般一次取10w条就可能导致OOM 递归循环调用方法 启动参数设置的堆内存太小 创建过多的对象过大的数组 JDK 1.8之后Perm Space有哪些变动? jdk8开始元数据区取代了持久代同时字符串常量池移到了堆内存中(8之前放在方法区中。 new String(abc)到底创建了几个对象 首先因为new这个关键字会在堆内存中实例化一个String对象然后在String的构造函数传了一个abc字符串因为String是final修饰的所以abc是一个字符串常量因此jvm会去字符串常量池中查找是否存在“abc”的对象引用如果没有就会在堆内存中创建一个abc的字符串常量并且将引用保存在字符串常量池中如果后续再有字面量abc的定义的话就直接从常量池中取对应的引用。 所以本题的答案应该是 如果常量池中没有abc的引用则2个 如果有则1个 MetaSpace⼤⼩默认是无限的么? 大小默认没有限制一般根据系统的大小jvm会动态改变此值。 可以指定元数据区的大小 -XX:MetaspaceSize分配给元数据区的初始大小 -XX:MaxMetaspaceSize分配给元数据区的最大值超过此值会触发Full GC。 线程 JMMjava内存模型 JMM定义了共享内存中多线程程序读写操作的行为规范 JMM把内存分为2部分一块是私有线程的工作区域工作内存一块是所有线程的共享区域主内存 线程跟线程之间是相互隔离线程跟线程交互需要通过主内存 实现多线程的方法 继承 Thread 类创建一个类让它继承 Thread 类然后重写 run() 方法。创建该类的实例对象然后调用它的 start() 方法来启动线程。 实现 Runnable 接口创建一个类让它实现 Runnable 接口然后重写 run() 方法。创建该类的实例对象并将其作为参数传递给 Thread 类的构造函数来创建一个线程对象。然后调用线程对象的 start() 方法来启动线程 实现 Callable 接口创建一个类让它实现 Callable 接口然后重写 call() 方法。创建该类的实例对象并将其作为参数传递给 FutureTask 类的构造函数来创建一个 FutureTask 对象。然后将 FutureTask 对象作为参数传递给 Thread 类的构造函数来创建一个线程对象。最后调用线程对象的 start() 方法来启动线程。 如何退出线程 使用标志位 采用interrupt方法通过while(thread.isInterrupted)方法来退出 采用stop方法不推荐会强制终止线程可能导致线程问题比如不知道任务到哪一步了锁释放了没 notify和notifyAll notify只能唤醒等待池中的一个线程notifyAll可以唤醒等待池中所有线程 notify可能造成死锁因为无法确定哪个线程被唤醒所以被唤醒的线程可能不是所期望的那么它会再次进入等待状态而其他线程仍然是等待状态就会造成死锁。比如两个线程一个生产者线程一个消费者线程产品充足时生产者线程会进入等待产品不足时消费者线程会进入等待假设当前生产者和消费者都处于等待状态此时需要唤醒消费者线程进行消费调用notify()会随机唤醒一个线程可能唤醒的是生产者生产者被唤醒后发现产品充足不需要生产则进入等待而此时消费者依旧是等待状态没有被唤醒就产生了死锁notifyAll不会 为什么 wait() 和 notify() 必须在同步块中使用呢 因为它们涉及到线程间的协作和状态的改变需要依赖于同步机制来实现线程之间的互斥和同步。 如果不在同步块会抛出异常是因为lost wake-up problem假设两个线程生产者消费者生产者count1消费者count-1当count0时消费者会唤醒生产者当count0时生产者会唤醒消费者假设此时消费者检查count0唤醒生产者后发生了上下文切换没有立即进入等待状态生产者唤醒后执行count1并准备唤醒消费者但此时生产者还未进入等待状态所以生产者的notify没有效果执行唤醒操作后消费者才进入等待状态也就一直陷入等待状态 sleep和wait的区别 sleep是Thread类的方法wait是Object类的方法 sleep不会释放锁wait会释放锁 wait需要依赖synchronized关键字 Thread类的start()和run()区别 start()用来启动新创建的线程并且start()内部是调用的run方法但这跟直接调用run方法不同run方法是在原本的线程中运行而start是启动新线程 start只能被调用一次run可以多次 java中interrupted 和 isInterrupted方法的区别 调用interrupt方法中断一个线程时会设置终端标识为true。 isInterrupted是一个实例方法用于检查线程对象的中断状态。 守护线程 线程分为用户线程和守护线程用户线程就是普通线程守护线程就是jvm的后台线程比如垃圾回收线程就是一个守护线程守护线程会在其他普通线程停止运行后自动关闭。可以设置 thread.setDaemon(true) 来把一个线程设置为守护线程 volatile是什么 volatile是一种轻量级的同步机制相比于synchronizedvolatile不会造成阻塞不会引起线程上下文切换和调度volatile有三大特性 有序性保证执行顺序跟代码顺序一致避免了jvm的指令重排序问题被volatile修饰的变量会在读写时加入不同的屏障阻止其他读写操作越过屏障。 原子性只能保证单个共享变量的原子性若是多个共享变量需要与synchronized一起使用。 可见性可以防止即时编译器的优化比如while(!flag)会被优化成while(true)使得修改了被volatile修饰的变量其他线程可以立即看到修改后的值 synchronized关键字 synchronized是java语言的关键字是原生语法层面的互斥锁。属于阻塞式同步 synchronized可以作用于方法代码块类 修饰非静态方法的话是对调用该方法的对象加锁 修饰静态方法其实和修饰类的作用是一样的锁的是该类所有对象的方法 修饰代码块可以指定加锁对象进入同步代码块前要先获取指定对象的锁 synchronized修饰代码块时底层是由monitor实现的会在同步块的前后生成 monitorenter 和 monitorexit 字节码指令。在执行 monitorenter 时会尝试获取对象锁如果该对象没被锁或当前线程已经拥有了这个锁则锁的计数器1当执行 monitorexit 时则计数器-1当计数器为0时锁就被释放。若获取锁失败则进入阻塞。monitor内部有三个属性 owner关联获得锁的线程 entrylist关联处于阻塞状态的线程 waitset关联处于waiting状态的线程 修饰方法或类时并没有 monitorenter 和 monitorexit 而是 ACC_SYNCHRONIZED 标识jvm通过这个标识来判断一个方法或类是否是同步方法或同步类。 早期版本的synchronized属于重量级锁效率低下是因为 monitor 是依赖于底层的操作系统实现的而在java6之后java官方从jvm层面进行了优化实现了锁升级机制所以现在的效率还可以。 synchronized锁升级机制 锁升级过程无锁-偏向锁-轻量级锁-重量级锁 无锁初始状态还没有线程竞争锁 偏向锁当只有一条线程执行到同步代码块时jvm会使用cas操作获取锁升级为偏向锁进入偏向锁状态后如果没有其他线程竞争该线程后续再次访问同步代码块时jvm不会在进行cas操作直接运行代码块犹如没有锁一样 轻量级锁当有多条线程竞争同一把锁时偏向锁会升级为轻量级锁本质是通过cas自旋实现的不会阻塞线程但是会持续消耗cpu因为其他线程会不断自旋尝试获取锁 重量级锁如果自旋次数过多仍未获取到锁则会升级为重量级锁会阻塞线程 Lock和synchronized Lock是一个接口syncronized是原生语法层面的关键字 Lock需要手动的加锁释放锁syncronized 的加锁释放锁是通过jvm自动控制的在进行代码块时自动获取锁退出时自动释放 Lock和syncronized都支持可重入锁 Lock支持公平锁和非公平锁支持等待可中断这些新特性 Lock的加锁粒度更细可以作用到代码块中的某部分syncronized通常作用域整个方法或某个代码块 JAVA锁 乐观锁乐观锁是一种乐观思想认为读多写少认为并发情况下一定不会被其他线程修改java中通常通过cas来实现乐观锁CASCompare And Swap是一种乐观锁技术它认为多个线程不对共享同一份数据所以在实行cas操作时首先会读取该数据的当前值并预期该数据的值不会被其他线程修改。如果后面读取到的数据与预期值相同则表示没有被其他线程修改则可以更新该数据的值。若不同则表示被其他线程修改则返回失败。cas只能保证单个共享变量的原子性若要保证多个共享变量多个键值对的原子性需要加锁 悲观锁悲观锁就是悲观思想认为并发情况下一定会被其他线程修改所以每次读写数据时都会加锁java中使用synchronized实现悲观锁 自旋锁如果持有锁的线程能在很短时间内释放锁则等待线程不会进入阻塞状态而是等一等也就是自旋等持有锁的线程释放后立刻就能获取到锁。为了不让线程一直占用cpu自旋需要设置一个自旋的最大时间。通过AtomicReference类实现基于cas的自旋锁 可重入锁可重入锁支持线程重复获取锁在同一个线程可以多次获取同一个锁而不会发生死锁在java中用 ReentrantLock 实现 Synchronized 同步锁属于独占式的悲观锁也属于可重入锁 公平锁与非公平锁公平锁遵循先到先得的原则按照线程请求锁的顺序分配锁资源非公平锁则无序 java中synchronized 和 ReentrantLock 有什么不同 ReentrantLock是一个接口syncronized是原生语法层面的关键字 ReentrantLock需要手动的加锁释放锁syncronized 的加锁释放锁是通过jvm自动控制的在进行代码块时自动获取锁退出时自动释放 ReentrantLock和syncronized都支持可重入锁 ReentrantLock支持公平锁和非公平锁支持等待可打断( 通过lock.lockInterrupitibly获取锁如果一直获取不到锁可通过 interrupt 来提前终止线程 )可超时( lock.tryLock(2,second) )多条件变量( lock.newCondition )这些新特性 ReentrantLock的加锁粒度更细可以作用到代码块中的某部分syncronized通常作用域整个方法或某个代码块 synchronized有一个锁升级机制 AQS AQS是多线程中的队列同步器是一种锁机制像ReentrantLock就是基于AQS实现的。 AQS内部维护了一个先进先出的双向队列存储排队的线程 在AQS内部还有一个属性state这个state默认是0表示无锁状态如果有线程将state修改成了1就表示该线程获取了锁。同时在修改state的时候采用的是cas操作保证多个线程修改下的原子性 ReentrantLock 分为公平锁和非公平锁底层如何实现 他们底层实现都是由AQS来进行排队也就是 AbstractQueuedSynchronizer ReentrantLock的Sync继承了AbstractQueuedSynchronizer也是加锁释放锁的核心NonfairSync继承了SyncFairSync也继承了Sync分别对应公平锁和非公平锁两把锁区别在于线程在使用lock()加锁时 如果是公平锁会先检查AQS队列中是否存在线程在排队如果有线程在排队则当前线程也进行排队 如果是非公平锁则不会检查是否有线程在排队而是直接竞争锁。 当锁释放时都是唤醒排在最前面的线程所以非公平锁只是体现在了线程加锁阶段而没有体现在线程被唤醒阶段。 java如何避免死锁 避免死锁最主要就是避免若干线程循环等待资源 注意加锁顺序 注意加锁时限设置过期时间 注意检查死锁可通过编写脚本实现周期执行jstack来检查一但发生死锁则发送通知 有三个线程T1,T2,T3,如何保证顺序执行 使用join()方法join方法会阻塞当前线程直到目标线程执行完毕才会执行当前线程。 比如三个线程T1,T2,T3在T3的run方法中写 T2.join() 在T2的run方法中写 T1.join() 这样当启动三个线程时启动顺序可以随意但是执行结果会是T1-T2-T3。 Thread类中的yield方法有什么作用 yield方法可以暂停当前正在执行的线程但它只是让当前线程让出cpu时间片以便其他线程有机会执行若没有其他线程处于就绪状态则当前线程仍然可以继续执行。 线程池中submit() 和 execute()方法 两个方法都可以向线程池提交任务execute方法的返回值类型是void而submit方法的返回值是Future类型通过Future的get方法可以获取线程执行的返回值。 常用的线程池有哪些 newSingleThreadExecutor创建一个单线程的线程池保证所有任务的执行顺序按照任务的提交顺序执行 newFixedThreadPool创建一个固定大小的线程池可控制线程最大并发数超出的线程会在队列中等待 newCachedThreadPool创建一个可缓存的线程池缓存线程池会根据需要自动创建新的线程并且在线程空闲一定时间后自动回收线程 newScheduledThreadPool创建一个定长线程池支持定时及周期性任务执行 简述一下你对线程池的理解 线程池其实就是一个可以容纳多个线程的容器里面的线程可以反复使用省去了频繁创建销毁线程对象的操作。 创建线程池的方法 使用Executors创建不推荐 Java中为什么不建议使用Executors的四种线程池呢_Mc0的博客-CSDN博客 newSingleThreadExecutor和newFixedThreadPool 允许的请求队列长度为lntegerMAX VALUE可能会堆积大量的请求从而导致OOM。 newCachedThreadPool 允许的创建线程数量为integer.MAX VALUE可能会创建大量的线程从而导致OOM。 使用ThreadPoolExecutor的构造方法自定义线程池推荐 ThreadPoolExecutor threadPool new ThreadPoolExecutor(5, // 核心线程数10, // 最大线程数60, // 线程空闲时间超过这个时间没有任务执行就会被回收TimeUnit.SECONDS, // 空闲时间单位new ArrayBlockingQueue(20), // 用于存储待执行的任务的阻塞队列容量为20Executors.defaultThreadFactory(), // 线程工厂用于创建线程new ThreadPoolExecutor.AbortPolicy() ); 合理利用线程池的好处 避免线程频繁创建和销毁的损耗 提高响应速度当任务到来时任务可以不需要等到线程创建就能立即执行 统一分配、管理线程 线程池常见的阻塞队列 LinkedBlockingQueueArrayBlockingQueue默认无界支持有界最好设置参数让他有界强制有界底层链表底层数组懒加载在新增节点时才添加数据提前初始化数组两把锁头尾一把锁 线程池常见的拒绝策略 AbortPolicy拒绝策略当线程池满了且任务队列也满了时抛出RejectedExecutionException异常 CallerRunsPolicy:用调用者所在的线程来执行任务 DiscardOldestPolicy丢弃阻塞队列中靠最前的任务并执行当前任务 DiscardPolicy直接丢弃任务 线程池提交任务的流程 当执行execute()或submit()方法提交一个Runable对象时 会先判断当前线程池中的线程数是否小于核心线程数 如果小于则创建新线程并执行 如果大于等于则放入待执行队列 如果队列没满则正常入队 如果队列满了则入队失败会判断当前线程池中的线程数是否小于最大线程数 如果小于则创建新线程并执行任务 如果大于等于则执行拒绝策略 如何控制某个方法允许并发访问线程的数量 使用 semaphore 在并发的情况下可以控制方法的访问量 创建 semaphore 对象设置一个容量表示最多只能这么多容量的线程执行 acquire() 表示请求一个信号量这是信号量数量-1。 release() 表示释放一个信号量1。 为0就不能 ThreadLocal 1、基本原理 threadlocal会为每个线程开辟一个独立的线程副本可以利用threadlocal将数据缓存在某个线程内部该线程可以在任意时刻任意方法中获取缓存的数据 ThreadLocal通过操作每个线程内部的ThreadLocalMap来实现也就是说每个线程内部都有一个ThreadLocalMapThreadLocalMap的底层实现是entry数组每个entry的key存储的key为 ThreadLocalvalue对应线程的局部变量值 因为ThreadLocal操作的都是当前线程中的变量和其他线程无关也就没有线程安全的问题 2、为什么会发生内存泄漏 因为 ThreadLocalMap中的entry的key是弱引用对象弱引用是只要被垃圾回收器发现就会被回收被回收后entry中的key就是null了这样就无法找到对应的value而entry中的value是强引用只有线程结束才会被回收所以会造成内存泄露正确的使用方法是在使用完之后调用threadlocal的remove方法手动清除entry对象。 JavaWeb JDBC 原生jdbc操作数据库流程 class.forname() 加载驱动 DriverManager.getConnection() 获取数据连接对象 根据SQL获取sql会话对象Statement.PreparedStatement 执行SQL处理结果集执行SQL前有参数则通过 setXXX()进行设置 关闭连接 jdbc处理事务的过程 设置为手动提交 conn.setAutoComit(false); 提交事务conn.commit() 出现异常则回滚conn.rollback() 网络通讯部分 TCP与UDP区别 连接性TCP是面向连接的协议UDP是无连接的协议 可靠性TCp提供可靠的数据传输确保数据按顺序到达目标地址UDP因无连接是不可靠协议数据包可能丢失或乱序 速度UDP比TCP更快 传输大小TCP没有数据包大小限制可以传输任意大小的数据UDP每个数据包的大小限制在64k内。 适用场景TCP适用于对数据完整性和顺序性要求较高的场景如电子邮件、文件传输UDP适用于实时性要求高可接受部分数据丢失的场景如音视频童话实时游戏。 什么是HTTP协议 客户端和服务器端之间数据传输的格式规范格式简称为“超文本传输协议”是一个基于请求与响应模式的无状态的应用层的协议基于TCp的连接 get和post get重点在获取数据post发送数据 get通过url请求通过fieldvalue的形式拼接在url?后多个请求数据用连接用户是可以直接看到的不安全。post将数据封装在请求体中用户无法直接看到相对安全。 get传输的数据量小因为受到url长度限制但效率高。post可以传输大量数据 重定向和转发 重定向是客户端行为转发是服务器端行为 重定向两次请求第一次请求发送到原始地址服务器接收后会返回一个重定向响应响应中包含302状态码和“Location字段该字段指向了重定向的目标url浏览器再根据这个目标url发起第二次请求。浏览器地址会发生变化可以访问自己web之外的资源传输的数据会丢失。 转发一次请求浏览器地址不变访问自己本身的web资源传输的数据不会丢失。 Ajax Ajax是一种创建交互式网页应用的网页开发技术。 通过异步模式提升了用户体验。 可以实现局部刷新 优化了浏览器与服务器之间的传输减少不必要的数据往返减少了带宽占用。 OSI七层模型和TCP/IP模型 从上到下 OSI: 应用层 负责给应用程序提供接口HTTP协议在此层 表示层 负责编解码加解密压缩与解压缩 会话层 协调系统间的通信 传输层 负责端到端的连接TCP和UDP在此层 网络层 为网络设备提供逻辑地址 数据链路层 提供可靠的点对点数据传输这一层通常处理帧的封装、错误校验 物理层定义物理设备相关标准和模式 TCP/IP: 应用层 - 传输层 - 网络层 - 数据链路层 HTTP建立连接过程 HTTP协议基于TCP的连接所以建立http连接前要先建立Tcp连接 浏览器解析用户输入的URL生成一个HTTP格式的请求 先根据URL域名从本地hosts文件查找是否有映射IP如果没有就进行DNS解析得到IP地址 通过三步握手建立tcp连接 客户端发送http请求 服务端处理请求并响应 客户端接收响应并渲染 客户端或服务端断开TCP连接 在http1.0的时候默认短连接也就是每一次请求都会按上面的步骤重复在http1.1开始默认是长连接通过请求头connection字段申明keep-alive作用就是减少tcp握手次数在使用长连接的情况下一开始建立了一条连接后续机会继续沿用这条连接 TCP三次握手四次挥手 【通俗易懂】三次握手与四次挥手_三次握手和四次挥手_小玄的博客-CSDN博客 三次握手 第一次握手客户端向服务器端发送一个syn报文并置标志位syn1请求建立连接初始化序号seqx此时客户端状态为 SYN_SENT。 第二次握手服务器端收到syn报文后会发送 syn-ack 报文并置 syn1初始序号seqy确认号字段 ackx1表明我收到了初始序号seqx的报文此时服务器状态为 SYN_RCVD。 第三次握手客户端收到 syn-ack 报文后会发送一个 ack 报文初始序号seqx1确认号字段 acky1表明我收到了你的确认此时客户端状态为 ESTABLISHED。 服务器接收到ack报文后也处于ESTABLISHED状态双方就建立了连接 四次挥手 初始时客户端和服务器都是 ESTABLISHED 状态服务器和客户端都可以发起断开连接的请求假设客户端发起请求 第一次挥手客户端发送一个 FIN 报文序号 sequ此时客户端状态为 FIN_WAIT_1 第二次挥手服务器收到 FIN 报文后立即发送 ack报文确认号字段 acku1序号 seqv。表明已经收到了客户端的报文。此时服务器状态为 CLOSE_WAIT 状态 第三次挥手服务器发送 Fin 报文置序号 seqw确认号 acku1表明可以断开连接。这里第三次挥手和第二次挥手不能合并在一起是因为ack和fin的触发时机不同服务器收到客户端的 fin 报文可以立即发送 ack 报文但服务器想发送fin报文需要处理完缓冲区中的数据才可以。 第四次挥手客户端收到 Fin 报文发出 ack 报文序号 sequ1确认号ackw1此时客户端状态为 TIME_WAIT需要等待一段时间确保服务器收到自己的报文后才会进入CLOSED状态如果服务器没收到就会触发超时重传服务器会再次发送 FIN 报文也就是第三次握手的过程。 常见的HTTP相应状态码 返回的状态 1xx指示信息--表示请求已接收继续处理 2xx成功--表示请求已被成功接收、理解、接受 3xx重定向--要完成请求必须进行更进一步的操作 4xx客户端错误--请求有语法错误或请求无法实现 5xx服务器端错误--服务器未能实现合法的请求 HTTP 协议与 TCP/IP 协议的关系 HTTP 的长连接和短连接本质上是 TCP 长连接和短连接。HTTP 属于应用层协议在传输层使用 TCP 协议在网络层使用 IP 协议。IP 协议主要解决网络路由和寻址问题TCP 协议主要解决如何在 IP 层之上可靠的传递数据包使在网络上的另一端收到发端发出的所有包并且顺序与发出顺序一致。TCP 有可靠面向连接的特点。TCP/IP协议是底层的网络协议负责将数据传输到目标地址。而HTTP协议是应用层协议通过TCP/IP协议进行通信实现了客户端和服务器的交互。HTTP协议依赖于TCP/IP协议的可靠性和连接管理使得Web浏览器能够访问和获取Web服务器上的资源。 跨域是什么怎么解决 跨域是指浏览器在发起网络请求时会检查该请求所对应的协议域名端口和当前网页是否一致如不一致则浏览器会进行限制比如原本是在www.baidu.com如果使用ajax去访问www.jd.com是不可行的。 解决方法 设置响应头resp.setHeader(Access-Control-Allow-Origin,*)表示可以访问所有网站不受同源策略的影响 使用jsonpjsonp是基于script标签来实现的因为script标签是可以跨域的 后台控制先访问同域名下的接口然后在接口中通过HttpClient去调用目标接口 网关比如springcloud的gateway 数据库 Sql注入 举例 select admin from user where usernameadmin or aa and passwdpwd or aa 使用预编译的方式预防select admin from user where usernameAnd passwor? mybatis中使用 # 而不是 $ 来预防 select查询语句完整执行顺序 select - from - where - group by - having - order by group by 将查到的数据分组 having对分组的数据过滤 order by按照指定顺序 存储引擎 MyIASMMysql5.5之前默认的存储引擎不支持行级锁和外键使用的是表级锁所以写操作需要锁住整张表效率低读操作则很快。存储总行数不支持事务。适用于大部分是读操作的场景。 InnoDb支持事务和外键支持表级锁默认使用行级锁并发性能较高不存储总行数提供了崩溃恢复的能力通过 redo log 和 undo log 保证数据的持久性和原子性。适用于经常写操作的场景。 binlog MySQL的server层日志。 用于实现数据库的复制和恢复。记录了所有的DDL语句和DML语句 可以用于主从复制和基于时间点还原数据库 redo log InnoDB存储引擎核心日志。 确保事务的持久性。 记录的是事务提交时数据页的物理变化并在后台异步地将这些更改应用到磁盘上的数据文件从而确保数据的持久性,宕机时可用来回复数据 undo log InnoDB存储引擎日志。 确保事务的原子性。 undo log记录了事务执行过程中对数据的的反向操作以便在事务回滚或崩溃恢复时可以撤销这些修改将数据恢复到原始状态确保数据的原子性。 Memory使用存在内存中的内容来创建表每个Memory表对应一个磁盘文件因为是存放在内存的所以访问速度很快一但mysql服务关闭数据就会丢失 Innodb是如何实现事务的 事务特性分为ACID 其中redolog通过记录事务提交时的数据页的物理修改宕机时可用于回复数据来确保持久性 undolog记录事务执行过程中的反向操作用于事务回滚或发生故障时可以撤销这些操作恢复原始数据确保原子性 隔离性则通过锁(拍他锁)和MVCC来实现 一致性需要通过另外三个特性来保证 解释一下MVCC如何解决隔离性 多版本并发控制维护一个数据的多个版本使读写没有冲突底层实现分为3个部分第一个是隐藏字段第二个是undolog第三个是readView读视图 隐藏字段是指mysql给每个表都设置了隐藏字段有一个是trx_id(事务id)记录每一次操作的事务id是自增的另个是roll_pointer指向上一个事务版本 undolog的作用是记录回滚日志存储老版本数据内部会形成一个版本链通过roll_pointer指针连接各个事务版本 readView解决查询选择哪一个版本的事务在内部定义了一些匹配规则和当前的一些事务id来判断该访问哪个版本的数据不同隔离级别的快照读是不同的最终的访问结果也不同如果是RC则每次执行快照读都会生成readView保证读取的是事务开始之前已经提交的数据如果是RR则仅在事务中第一次执行快照读时生成readView后续复用确保了在事务执行期间看到的数据是一致的 当前读读取的是记录的最新版本通常会加锁共享锁和排它锁都是当前读 快照读简单的select就是快照读读取的可能是历史版本不加锁非阻塞 主从同步的原理 为了减轻数据库的压力以及避免数据丢失和主系统宕机 核心是binlogbinlog记录所有DDL和DML语句可以用于数据库的复制和恢复 具体流程是 master提交事务时会将数据变更记录在binlog中 slave读取master的binlog记录在自己的中继日志中 relay log slave再读取中继日志中的数据写入自己的数据库中 了解分库分表嘛 具体可以拆分成4种策略 水平分库将一个库的数据拆分到多个库中解决海量数据存储和高并发的问题 水平分表相对用的少解决单表存储和性能的问题 垂直分库根据业务拆分提高高并发下的磁盘IO一些业务可能更加频繁地进行写操作而另一些业务则更加偏向读取。将它们分别存储在不同的数据库中可以根据其特性进行针对性的磁盘IO优化对写多的数据库可以采用高速磁盘或内存缓存对于读多的可以用SSD来提高性能和数据隔离 垂直分表根据字段拆分可以实现冷热数据分离多个表之间互不影响 进行分库后可能会出现一些问题比如分布式事务问题跨节点关联查询跨节点分页排序查询主键避免重复问题所以需要引入中间件如mycat来解决分布式事务可以用本地消息表或seata解决 了解过索引嘛 索引是帮助mysql高效获取数据的数据结构是有序的 通过索引列对数据进行排序提高数据的检索效率 避免全表扫描 索引的底层数据结构了解过吗 mysql中innodb采用B树的数据结构 B树是个矮胖树层级更少性能更高 非叶子结点只存储指针叶子结点存储数据 叶子结点是一个双向链表更加利于范围查询 什么是聚集索引什么是非聚集索引 聚集索引指数据和索引放在一起叶子结点保存了整行数据聚集索引有且只有一个通常聚集索引选取规则 如果存在主键主键索引就是聚集索引 如果不存在主键则使用第一个唯一索引作为聚集索引 如果都没有innodb会自动生成一个rowid作为隐式的聚集索引 非聚集索引指数据和索引分开存储叶子结点保存对应的主键非聚集索引可以有多个一般我们自定义的索引都是非聚集索引 什么是回表查询 通过二级索引找到对应的主键再通过主键在聚集索引找到对应的行数据这个过程就是回表 是什么叫覆盖索引 指查询使用了索引返回的列必须在索引中能够全部找到 使用id查询走聚集索引能够一次查到整行数据只用一次索引扫描所以也属于覆盖索引性能高 如果查询返回的列中没有全部包含索引列会触发回表查询就不是覆盖查询所以要避免select * mysql超大分页处理 数据量较大时进行limit分页处理时在查询时越往后分页查询效率越低 比如 select * from table limit 9000000,10; 解决方法 通过覆盖索引和子查询来优化 select * from table t, (select id from table order by id limit 9000000,10) a where t.id a.id; 子查询中根据id排序并分页确定了id后只查询匹配子查询返回的id列表中的数据因为查询id时走的是覆盖索引所以性能相对更高 创建索引原则 针对数据量较大访问频繁的表创建索引单表超过10w 针对经常where, order by, group by 的字段创建索引 尽量创建唯一索引选择区分度较高的列区分度越高索引效率越高 尽量使用联合索引更大可能可以覆盖索引避免了回表查询 控制索引的数量写操作的时候需要维护索引 如果索引列不能存储NULL值则设置为NOT NULL 什么情况下索引会失效 违反最左前缀法则整个联合索引都失效如果符合最左前缀但是联合索引跳过中间某一列则只有最左列的索引生效 对于联合索引中间的列使用了范围查询也就是age18右边的列不能使用索引 在索引列上进行运算操纵和函数操作 字符串不加单引号会被自动类型转换造成索引失效 %放在开头的模糊查询 避免in 和 not in也可能会导致全表扫描对于连续的数值能用between就别用in 常用sql优化 表的设计优化参考《阿里开发手册》比如设置合适的数值int, tinyint, bigint设置合适的字符串类型(char是定长varchar是变长)根据实际情况使用 sql优化 避免使用select * 避免索引失效 尽量用union all代替 unionunion会多一次过滤效率低 索引并不是越多越好索引可以提高查询的效率但会降低insert和update的效率一个表中的索引最好不要超过6个 Join优化能用innerjoin就别用左右连接如果必须用左右连接也要以小表为驱动内连接会对两个表进行优化优先吧小表放在外边把大表放在里边左右连接则不会重新调整顺序 避免in 和 not in也可能会导致全表扫描对于连续的数值能用between就别用in %放在开头的模糊查询 索引 索引就是加快检索表中数据的方法。按分类可以分成 普通索引最基本的索引没有任何限制。 唯一索引与普通索引类似不同在于索引列的值必须唯一可以为null如果是组合索引则列值的组合必须唯一 主键索引特殊的唯一索引一个表只能有一个主键不允许有空值。 组合索引由多个列组成只有在查询条件中使用到了创建索引时的第一个字段索引才会被使用。 全文索引主要用来查找文中的关键字只有char, varchar, text列可以创建全文索引。 按结构可以分成 BTree索引这是最常见的索引类型。B-Tree索引适用于查找某个范围内的记录例如根据名字或者日期范围来查询记录。B-Tree索引可以优化等值查询和范围查询并且支持多列索引。常见的包括PRIMARY KEY、UNIQUE、INDEX等 Hash索引这种索引适用于等值查询例如根据ID来查询某个记录。Hash索引将索引列的值作为输入通过Hash算法计算出一个指针指向对应的记录。Hash索引无法优化范围查询也无法支持排序因此Hash索引在实际应用中使用较少 Full-text索引这种索引适用于全文搜索例如在文章中查找包含某个关键词的记录。Full-text索引可以提高全文搜索的效率并且支持模糊匹配和相关度排序。 R-Tree索引 这种索引适用于地理信息系统和空间数据存储。R-Tree索引可以高效地查询某个范围内的记录例如查询某个点是否在某个多边形内 优点 创建唯一性索引保证某些列的唯一性 大大加快检索速度这也是主要的原因 加速排序和分组的过程 缺点 增加索引维护成本当表中的数据发生变化如插入、更新、删除时索引需要进行维护 额外占用物理空间 B树和B树的区别为什么mysql采用B树 B树的特点是 节点排序 一个节点可以存储多个元素并且是排好序的 B树的特点是拥有B树的特点同时 B树是个矮胖树层级更少性能更高 非叶子结点只存储指针叶子结点存储数据 叶子结点是一个双向链表更加利于范围查询 三大范式 第一范式每个属性都是不可再分的最小数据单元。 第二范式满足第一范式并且每一行的非主属性必须完全依赖于主键每张表只能描述一件事情 第三范式满足第二范式并且表中的列不存在对非主键列的传递依赖比如订单表中除了主键订单编号外顾客姓名依赖于非主键顾客编号。 BC范式BC范式是指Boyce-Codd范式它是在第三范式的基础上定义的。它的定义是在关系模式中每一个决定因素都包含候选键也就是说只要属性或属性组A能够决定任何一个属性B则A的子集中必须有候选键 存储过程 为了完成特定功能使用sql语句编写的程序存储过程在第一次执行时进行编译然后将编译好的代码保存在数据库中供以后调用提高执行效率。 存储过程优缺点 优点 1存储过程是预编译过的执行效率高。 2存储过程的代码直接存放于数据库中通过存储过程名直接调用减少网络通讯。 3安全性高执行存储过程需要有一定权限的用户。 4存储过程可以重复使用减少数据库开发人员的工作量。 缺点 1调试相对麻烦 2移植问题数据库端代码当然是与数据库相关的。但是如果是做工程型项目基本不存在移植问题。 存储过程写法 创建 CREATE PROCEDURE p1() BEGIN SELECT count(*) FROM account; END; 调用 call p1(); 查看 SHOW CREATE PROCEDURE 存储过程名称 ; -- 查询某个存储过程的定义 删除 DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ; 触发器是什么 触发器是特殊的存储过程。触发器是指一段代码当触发某个事件时自动执行这些代码。 使用场景 • 可以通过数据库中的相关表实现级联更改。 • 实时监控某张表中的某个字段的更改而需要做出相应的处理。 注意不要滥用否则会造成数据库及应用程序的维护困难。 MySQL中都有哪些触发器 在MySQL数据库中有如下六种触发器 • Before Insert • After Insert • Before Update • After Update • Before Delete • After Delete 创建触发器 只有一个执行语句 create trigger 触发器名 before | after 触发事件 on 表名 for each row 执行语句 有多个执行语句 create trigger 触发器名 before | after 触发事件 on 表名 for each row begin 执行语句列表 end char varchar varchar 类型的长度是可变的而 char 类型的长度是固定的。char 类型是一个定长的字段以 char(10) 为例不管真实的存储内容多大或者是占了多少空间都会消耗掉 10 个字符的空间 char 长度最大为 255 个字符varchar 长度最大为 65535 个字符 varchar 类型的查找效率比较低而 char 类型的查找效率比较高 事务 将一组操作作为一个整体向系统提交要么都执行成功要么都执行失败回滚。是一个不可分割的工作逻辑单元。事务必须具备以下四个属性ACID 原子性事务是一个完整的操作事务中的操作要么都执行成功要么都执行失败。 一致性事务完成时必须使所有的数据都保持一致状态 隔离性事务之间是彼此隔离的相互独立的 持久性事务完成后对数据库的修改是永久存在的 举个例子 A向B转账转账成功A扣除500B增加500原子性体现在扣除和增加是一个整体要么都成功要么都失败 一致性就是数据要一致A扣除了500B相应也增加了500整体数据不变 隔离性就是A向B转账的过程中不被其他事务干扰 持久性就是事务提交后这个修改时永久性的 事务处理的两种方式 用BEGIN ROLLBACK COMMIT来显式实现事务 SET AUTOCOMMIT0/1 禁止自动提交/开启自动提交 四种隔离级别 mysql默认可重复读 读未提交一个事务可以读到另一个事务未提交的数据脏读 读已提交一个事务只能读取到已经提交的数据避免了脏读可能出现不可重复读 可重复读一个事务中对同一数据的多次读取得到的结果是一致的避免了脏读和不可重复读可能出现幻读 序列化强制事务串行执行会严重影响性能 脏读、不可重复读、幻读 脏读事务读取到了另一个事务未提交的数据 不可重复读在一个事务中多次读取同一份数据得到的结果不一致 幻读在一个事务中多次查询会出现新增或消失的行跟出现幻觉一样 数据库并发策略 乐观锁、悲观锁、时间戳 乐观锁认为用户在读数据的时候别人不会去写自己正在读的数据适合多读场景 乐观锁常见的2种实现方式 版本号机制在数据表中加入version字段当数据被修改version1在读取数据的时候也会将version读出来并保存当修改时判断表中的version和前面保存的version是否一致一致才可以更新不一致则说明被其他线程修改过需要放弃提交更新操作。 cas操作在实行cas操作时首先会读取该数据的当前值并预期该数据的值不会被其他线程修改。如果后面读取到的数据与预期值相同则表示没有被其他线程修改则可以更新该数据的值。若不同则表示被其他线程修改则返回失败。cas只能保证单个共享变量的原子性若要保证多个共享变量多个键值对的原子性需要加锁(syncronize) 缺点 ABA问题如果一个变量 V 初次读取的时候是 A 值并且在准备赋值的时候检查到它仍然是 A 值那我们就能说明它的值没有被其他线程修改过了吗 ? 很明显是不能的因为在这段时间它的值可能被改为其他值然后又改回 A那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的“ABA问题。在 Java 中可以使用 AtomicStampedReference 类来解决 ABA 问题该类可以记录变量值和版本号的信息确保 CAS 操作的正确性。 循环时间长开销大若是自旋cas也就是不成功就一直循环执行到成功如果长时间不成功会给cpu带来非常大的执行开销。 只能保证单个共享变量 悲观锁跟乐观锁相反认为在读数据时别人一定会写自己在读的数据所以在读数据时会对读取的数据加锁适合多写场景 时间戳不加锁通过时间戳来控制并发在表中添加一列时间戳TimeStamp每次读出来的时候把该字段也读出来要修改时更新为最新时间并在提交修改时与前面读取保存的字段判断如果大于数据库中的该字段则允许修改否则不行。 数据库锁 行级锁是一种排它锁防止其他事务修改此行。 表级锁对当前操作的整张表加锁他实现简单资源消耗少表级锁分为表共享读锁共享锁和表独占写锁排它锁 页级锁介于行级锁和表级锁的一种表级锁速度快但冲突多行级锁冲突少但速度慢。页级锁一次锁定相邻的一组记录。 也可以划分为 共享锁读锁共享锁允许多个事务同时读取同一份数据但不允许对数据进行修改。 排它锁写锁排它锁只允许一个事务独占地进行数据读取和修改其他事务无法同时获取共享锁或排它锁。 也可以划分为 乐观锁 悲观锁 如何定位慢查询 可以通过运维工具skywalking可以直观的看到那个接口比较慢并且可以分析接口哪部分比较慢如果是sql执行慢还能看到sql的执行时间。 mysql也提供了慢日志功能slow_query_log 1 需要开启设置指定时间当sql执行超过这个时间就会记录到这个日志中不过一般只用于调试阶段因为会有一定损耗 慢查询优化 通过explain语句 通过key和key_len检查是否走了索引 通过type字段查看sql有无优化空间是否存在全索引扫描或全表扫描 通过extra判断是否出现了回表的情况如果有可以尝试添加索引或修改返回字段来修复 possible_key可能用到的索引 key实际命中的索引 key_len索引占用的大小 exter如果是 Using where 或 using index 表示走了索引需要的数据在索引列内都能找到不需要回表查询。如果是 Using index condition 表示走了索引需要回表查询 type这条sql连接的类型性能由好到坏是 Null(查询没有用到表), System(查询mysql自带的表), const(根据主键查询), eq_ref(主键索引或唯一索引返回一条数据), ref(索引查询可能返回一或多条数据), range(范围查询), index(遍历整个索引查询), all(全表扫描) 分布式缓存 缓存击穿 一个热点数据失效导致大量请求涌入数据库 解决方案 加锁优点强一致性缺点并发性能 逻辑过期不加过期时间而是将过期时间放在缓存的value中通过代码来判断逻辑 假设线程1查缓存从value中判断出来当前的数据已经过期了然后会获取锁 接着创建一个新线程2此时的线程1可以直接返回结果不过返回的是脏数据 线程2查询数据库并将最新的数据写入缓存重置过期时间然后释放锁 在线程2释放锁前其他线程访问但无法获取锁也会直接返回脏数据。 只有等线程2完成重置逻辑后释放锁后其他线程才能返回正确的数据 优点高可用性能好 缺点不能保证数据一致 缓存穿透 指请求一个数据库中没有的数据数据库中没有缓存自然也没有如果频繁访问的话就会对数据库造成很大压力。 解决方案 缓存空结果并设置它的过期时间 设置布隆过滤器布隆过滤器由一个位数组和多个哈希函数组成。当要插入一个元素时首先通过多个哈希函数生成对应的多个哈希值然后将位数组中对应位置的位设置为1。当要查询一个元素是否存在时使用相同的多个哈希函数得到多个哈希值然后检查位数组中对应的位是否都为1。如果有任何一位为0就可以确定元素不在集合中如果所有位都为1元素可能在集合中数组越大误判率越小可以通过redisson或guava实现布隆过滤器可以操控误判率 缓存雪崩 缓存中大量数据同时失效导致大量请求直接访问数据库 解决方案设置不同的缓存失效时间搭建redis集群添加多级缓存如guava添加降级限流策略 缓存预热 系统上线前先将相关的数据加载到缓存中避免用户首次访问直接去数据库。 SpringMVC 什么是SpringMVC SpringMVC是一个基于java的实现了MVC设计模式的请求驱动类型的web框架通过把ModelViewComtroller层分离实现了多个组件将web层进行职责解耦把复杂的web应用分成逻辑清晰的几部分 SpringMVC的流程 用户发送请求至 DispatcherServlet 前端控制器 DispatcherServlet 收到请求后调用 HandlerMapping 处理器映射器 HandlerMapping根据请求的URL将请求映射到相应的controller并返回给DispatcherServlet DispatcherServlet 调用 HandlerAdapter 处理器适配器。 HandlerAdapter 经过适配调用具体的 controller 并最终返回 ModelAndView 给 DispatcherServlet DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器 ViewResolver 解析后返回具体的view DispatcherServlet 对view进行渲染并响应给用户 优点 支持各种视图技术、模板引擎不局限于jsp 与spring集成 清晰的模块组件 主要组件 DispatcherServlet 前端控制器接收请求响应结果相当于转发器减少了其他组件之间的耦合 HandlerMapping 处理器映射器根据请求URL来找controller HandlerAdapter 处理器适配器执行控制器并处理参数、返回值 ViewResolver 视图解析器进行视图解析 SpringMvc常用注解 RequestMapping处理请求url映射的注解可用于类和方法上用在类上表示该类中所有响应请求的方法都是以该地址作为父路径 RequestBody接收http请求的json数据将json转为java对象 ResponseBody将java对象转为json数据并返回 如何拦截get请求的方法 可以在 RequestMapping 注解中加 MethodRequestMethod.GET 注解 注解的工作原理主要是通过Java的反射机制来实现的。当编译器或运行时系统遇到注解时会通过反射获取注解的信息并根据注解的定义来进行相应的处理。 Spring Spring是什么 Spring是一个轻量级的IOC和AOP容器框架提供容器来装载具体的bean对象没有spring钱通常使用new关键字来创建对象有了spring后只需要告诉spring容器有哪些对象他会帮我们创建和维护整个对象的生命周期。 IOC表示控制反转也就是把原来new对象的控制权交给了容器Ioc的一个重点就是在系统运行中动态的向某个对象提供它所需要的资源。这一点是通过DI实现的DI就是依赖注入和IOC是同一个概念的不同角度的描述即依赖IOC容器来动态注入对象需要的资源。 AOP面向切面作为面向对象的一种补充用于将那些与业务无关却对多个对象产生影响的公共行为和逻辑抽取并封装为一个可重用的模块这个模块被命名为“切面”可以减少系统的重复代码降低模块间耦合度可用于权限日志事务处理。在需要进行增强的方法上采用AOP的五大通知前置环绕后置最终异常来添加与业务无关的逻辑以实现增强。 在后台记录请求接口使用AOP使用环绕通知切点表达式找到记录请求接口的方法通过环绕通知方法的参数来获取请求接口的信息 Spring主要由以下几个模块组成 Spring Core核心类库提供IOC服务 Spring Context提供框架式的Bean访问方式以及企业级功能如定时任务。 Spring AOP提供AOP服务 Spring DAO对jdbc的抽象 Spring ORM对现有ORM框架的支持 Spring Web提供了面向Web的特性如文件上传 Spring MVC提供面向web应用的MVC实现 Spring的优点 spring属于低侵入式设计代码污染度低 spring的DI机制将对象之间的依赖关系交由框架处理降低组件之间的耦合 提供了aop技术支持将一些通用任务如日志事务权限进行集中管理提供复用 对主流框架提供很好的集成 OOP和AOP OOP面向对象具有继承封装多态三大特性。允许开发者定义纵向的关系如通过继承来构建层次关系但并不适用于定义横向的关系会导致大量代码重复不利于各个模块的重用 AOP面向切面作为面向对象的一种补充用于将那些与业务无关却对多个对象产生影响的公共行为和逻辑抽取并封装为一个可重用的模块这个模块被命名为“切面”可以减少系统的重复代码降低模块间耦合度可用于权限日志事务处理。在需要进行增强的方法上才用哪个AOP的五大通知前置环绕后置最终异常来添加与业务无关的逻辑以实现增强。 AOP实现的关键是代理模式分为静态代理和动态代理。 静态代理(AspectJ)就是AOP框架会在编译阶段生成AOP代理类因此也称为编译时增强会在编译阶段将切面织入到java字节码中运行的时候就是增强后的AOP对象。 动态代理分为 JDK动态代理 和CGLib动态代理动态代理就是说AOP框架不会去修改字节码而是每次运行时在内存中临时为方法生成一个AOP对象这个 AOP 对象包含了目标对象的全部方法并且在特定的切点做了增强处理 Spring的IOC理解 IOC表示控制反转也就是把原来new对象的控制权交给了容器Ioc的一个重点就是在系统运行中动态的向某个对象提供它所需要的资源。这一点是通过DI实现的DI就是依赖注入和IOC是同一个概念的不同角度的描述即依赖IOC容器来动态注入对象需要的资源。 IOC的注入方式有3种setter注入构造器注入注解注入 IOC使得各个组件保持松散的耦合AOP使得可以将与业务无关的遍布应用各层的功能分离出来形成可重用的功能组件 BeanFactory 和 ApplicationContext 有什么区别 BeanFactory是Spring里面最底层的接口ApplicationContext是BeanFactory的派生类除了提供BeanFactory的功能还继承了诸如EnvironmentCapable、MessageSource、ApplicationEventPublisher接口提供获取系统环境变量国际化事件发布资源加载等功能。 BeanFactory是延迟加载注入Bean在调用getBean()才实例化ApplicationContext是在容器启动时一次性创建所有的Bean Spring Bean的生命周期 首先说一下servlet的生命周期实例化初始int接受请求service销毁destroy Spring Bean的生命周期 实例化bean对于BeanFactory容器当客户向容器请求一个尚未初始化的bean时容器会调用 getbean进行实例化。对于ApplicationContext容器容器启动时一次性创建所有的bean实例 设置对象属性依赖注入实例化后的对象被封装在BeanWrapper对象中SPring根据BeanDefinition中的信息以及BeanWrapper提供的接口完成依赖注入 处理Aware接口Spring会检测该对象是否实现了xxxAware接口 如果这个Bean实现了 BeanNameAware 接口就要重写 setBeanName(String beanId) 方法可以拿到beanname 如果实现了 BeanFactoryAware 接口要重写它实现的setBeanFactory()方法拿到spring工厂 如果实现了 ApplicationContextAware 接口重写setApplicationContext(ApplicationContext)方法可以拿到Spring上下文 postProcessBeforeInitialization接口如果Bean实现了 BeanPostProcessor接口将会调用 postProcessBeforeInitialization(Object obj, String s) 方法 init-method如果Bean在Spring配置文件中配置了 init-method 属性会自动调用其配置的初始化方法 postProcessAfterInitialization如果Bean实现了 BeanPostProcessor接口将会调用postProcessAfterInitialization(Object obj, String s)方法 DisposableBean如果Bean实现了 DisposableBean 接口则会调用其实现的destory()方法 destroy-method最后如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性会 自动调用其配置的销毁方法。如果既实现了DisposableBean接口又配置了自定义销毁方法(destroy-method)则会先执行自定义销毁方法再执行 DisposableBean 的 destory()方法 spring循环依赖问题 循环依赖就是两个或两个以上的bean互相持有对方比如A依赖BB依赖A。 spring允许循环依赖依据三级缓存解决大部分的循环依赖的问题 一级缓存缓存初始化完成的Bean对象 二级缓存缓存早期的Bean对象就是生命周期没走完的Bean 三级缓存缓存ObjectFactory表示对象工厂用来创建某个代理对象 一二级缓存能解决一般对象的循环依赖代理对象需要加上三级缓存。 如果是构造方法出现了循环依赖的问题由于构造函数在bean生命周期中是第一个执行的所以spring无法解决可以通过Lazy注解加在构造函数上来解决 Spring容器启动流程 首先会进行扫描扫描得到所有的BeanDefinition对象并存在一个Map中 然后筛选出非懒加载的单例Bean并创建对于多例Bean不需要在启动过程中去创建而是会在每次获取Bean时利用BeanDefinition去创建 创建Bean的过程也就是Bean的生命周期 Bean创建完了后Spring会发布一个容器启动事件 此刻spring启动结束 Spring Bean的作用域 singleton单例模式默认Spring IOC 容器中只会存在一个共享的Bean实例在多线程下是不安全的。 prototype多例为每一个bean提供一个实例 request为每一个请求创建一个实例请求完成后会失效并被垃圾回收 session与request类似确保每个session中有一个bean实例session过期后bean将被销毁 global session在一个全局的Http Session中容器会返回该 Bean 的同一个实例仅在使用 portlet context 时有效。是用于在基于 Portlet 的 Web 应用程序中管理和访问各个 Portlet 的上下文信息的对象。 Spring如何处理线程并发问题 Spring中Bean默认是Singleton作用域同时大部分bean是无状态的所以不会有线程安全问题但如果bean是有状态的那么在并发情况下会有线程安全问题可以通过设置bean为prototype作用域或者是采用 ThreadLocal ThreadLocal 为每个线程提供独立的变量副本将非线程安全的变量放入ThreadLocal中来保证线程安全 无状态:表示bean属性不会发生变化不能保存数据是不变的类。比如: controller、service、dao。 有状态: 表示bean的属性是可以发生变化的可以保存数据是线程不安全的比如: pojo。 Spring的自动装配 xml配置共有5种 no默认的方式不进行自动装配通过手工设置ref属性来进行装配bean byName通过bean的名称进行自动装配 byType根据参数类型 constructor利用构造函数进行装配并且构造函数的参数通过byType自动装配 autodetect自动探测如果有构造方法则通过constructor自动装配否则通过byType自动装配 基于注解的方式 Autowire可用于构造函数.成员变量.Setter 方法按照类型自动装配默认情况下它要求依赖对象必须存在可以设置它 required 属性为 false。 Resource跟Autowire类似默认按照名称自动装配也可以设置为按照类型 Inject与Autowire类似默认按照类型自动装配 Qualifier如果有多个相同类型的 bean就需要通过 Qualifier 注解来指定具体要使用的 bean。必须搭配上面三个一起使用 Spring框架中用到了哪些设计模式 1工厂模式BeanFactory 就是简单工厂模式的体现用来创建对象的实例 2单例模式Bean 默认为单例模式。 3代理模式Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术 4观察者模式ApplicationListener事件监听机制 5责任链模式BeanPostProcessor Spring事务的实现方式 编程式事务通过代码显示的commit, rollback 声明式事务常用的是声明式事务就是用注解Transactional声明式事务本质上是通过Aop功能对方法前后进行拦截将事务处理的功能编织到拦截的方法中也就是在目标方法开始前加入事务在执行完方法后根据执行情况提交或回滚。声明式事务优于编程式事务因为不需要在业务代码中参杂事务的代码但声明式事务最细粒度只能做到方法级别无法像编程式事务做到代码块级别 spring事务什么时候会失效 Spring事务的原理是AOP进行了切面增强事务失效就说明是AOP不起作用了常见情况有 发生自调用同一个类中使用this调用本类的方法this通常省略此时这个this指向的对象不是代理对象而是目标对象本身也就是绕过了代理对象解决方法 将被调用的事务方法放到另一个类通过依赖注入的方式使用代理对象进行方法调用 [事务] Transactional注解在同一个类中调用的失效问题_同一个类中方法调用transactional失效_fastjson_的博客-CSDN博客 方法不是public如果要用在非public方法上可以开启AspectJ代理模式 数据库不支持事务 没有被spring管理比如由new创建的对象 捕获异常但没有抛出(throw new RuntimeException) 没有设置事务的回滚策略spring默认只会回滚运行时异常如果是抛出的是被检查异常需要设置回滚策略 Spring的事务传播行为 spring的事务传播行为说的是当多个事务同时存在时spring如何处理这些事务的行为 REQUIRED默认如果当前没有事务就创建一个新的事务若当前存在事务则加入该事务 SUPPORTS支持当前事务如果当前存在事务就加入该事务如果不存在则以非事务执行 MANDATORY支持当前事务如果当前存在事务就加入该事务如果当前不存在就抛出异常 REQUIRES_NEW无论当前有无事务都创建新事务 NOT_SUPPORTED以非事务方式执行操作如果当前存在事务则挂起当前事务 NEVER以非事务方式执行如果当前存在事务则抛出异常。 NESTED如果当前存在事务则在嵌套事务内执行。如果当前没有事务则按 REQUIRED 属性执行 隔离级别 ① ISOLATION_DEFAULT这是个 PlatfromTransactionManager 默认的隔离级别使用数据库默认的事务隔离级别。 ② ISOLATION_READ_UNCOMMITTED读未提交允许另外一个事务可以看到这个事务未提交的数据。 ③ ISOLATION_READ_COMMITTED读已提交保证一个事务修改的数据提交后才能被另一事务读取而且能看到该事务对已有记录的更新。 ④ ISOLATION_REPEATABLE_READ可重复读保证一个事务修改的数据提交后才能被另一事务读取但是不能看到该事务对已有记录的更新。 ⑤ ISOLATION_SERIALIZABLE一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。 AOP的几个名词 切面(Aspect)被抽取的公共模块 连接点(joint point)指方法一个连接点代表一个方法的执行 通知(advice)在某个特点的连接点上执行的动作。通知有各种类型包括“around”before“after”等 切入点(pointcut)指我们要对哪些连接点进行拦截通过切入点表达式指定拦截的方法比如指定拦截 add*search* 织入(Weaving)通过织入切面逻辑到目标对象的方法中创建一个新的代理对象。这个代理对象包含了目标对象的原始功能并且织入了额外的切面逻辑以实现对目标对象的增强或修改。 AOP通知的类型 前置通知Before advice在目标连接点执行之前执行的通知 后置通知After advice在目标连接点执行之后执行的通知。 返回后通知After returning advice在目标连接点成功执行并返回结果后执行的通知。 异常通知After throwing advice在目标连接点抛出异常时执行的通知。 环绕通知Around advice在目标方法执行前后都执行的通知是最常用的通知 当方法符合切点规则不符合环绕通知的规则时候 执行的顺序如下Before-After-AfterRunning(如果有异常一AfterThrowing) 当方法符合切点规则并且符合环绕通知的规则时候执行的顺序如下Around-Before-Around-After执行 ProceedinaJoinPointproceed) 之后的操作-AfterRunning(如果有异常AfterThrowing) Mybatis 什么是Mybatis Mybatis是一个半ORM(对象关系映射)框架它内部封装了jdbc开发者只需关注sql语句本身sql语句写在xml文件中与业务代码解耦程序员直接编写原生sql可以严格控制sql执行性能还可以使用XML或注解来配置和映射pojo灵活度高。 Mybatis的优点 基于SQL语句编程Sql语句写在xml中与业务代码解耦便于统一管理提供标签支持编写动态Sql语句并且可以重用 减少jdbc的大量繁杂代码 很好的与各种数据库兼容因为Mybatis使用jdbc连接数据库所以只要是jdbc支持的数据库mybatis也支持 与spring很好的集成 提供映射标签支持对象与数据库的ORM字段关系映射 缺点 sql语句编写工作量大当字段多关联多时很考验sql功底 Sql语句依赖于数据库所以不能随意切换数据库 Mybatis的执行流程 通过核心配置文件 mybatis-config.xml 加载环境配置和映射文件 构建会话工厂全局单例 通过工厂创建 SqlSession每次操作都会创建一个会话包含了执行Sql语句的所有方法 操作Executor执行器封装了jdbc的操作这是真正操作数据库的接口也负责缓存的维护 Executor接口的执行方法中有一个MappedStatement类型的参数封装了映射信息 输入参数映射 输出结果映射 Mybatis和Hibernate有哪些不同 mybatis是半orm框架需要自己编写sql语句hibernate是个全orm框架基本不需要自己编写sql语句开发人员只需定义实体类和相应的映射关系即可。 mybatis直接编写sql语句SQL语句依赖数据库不能随意切换数据库hibernate对象关系映射能力强数据库无关性好 #{}和${} #{}是预编译处理${}是字符串替换 mybatis在处理#{}时会将sql中的#{}替换为?号调用preparedStatement的set方法赋值 最好使用#{}可以有效预防sql注入问题 Mybatis是如何将 SQL 执行结果封装为目标对象并返回的都有哪些映射形式 resultMap标签需要手动逐一定义数据库列名和对象属性名之间的映射 resultType标签指定返回结果类型一般是一个java对象如果列名和属性名不一致可以在sql语句中用as关键字将数据库列名写成对象属性名 Mybatis 动态 SQL 可以在xml映射文件中用标签的形式编写动态sql常用的动态sql标签有9种trim、where、set、foreach、if、choose、when、otherwise、bind if满足条件才会执行 choose相当于java中switch语句中的switch when相当于switch语句中的case otherwise相当于switch语句中的default where在至少满足一个if条件时才会插入WHERE关键字而且where标签会根据语法决定是否保留AND和OR set用于解决update语句存在的符号问题会消除无关的逗号 trim用于处理字符串的拼接可以去除多余的逗号、AND 和 OR 等冗余字符。 foreach遍历集合它包含这些属性collection, item, index, separator(在迭代后加上指定字符) bind可以用于模糊查询 Mybatis 的 Xml 映射文件中不同的 Xml 映射文件id 是否可以重复 不同的 Xml 映射文件如果配置了 namespace那么 id 可以重复如果没有配置 namespace那么 id 不能重复 MyBatis 实现一对一和一对多 一对一在 resultMap 标签内配置 association 标签 一对多在 resultMap 标签内配置 collection 标签 Mybatis 是否支持延迟加载 Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载association 指的就是一对一collection 指的就是一对多查询。在Mybatis 核心配置文件 mybatis-config.xml 中可以配置是否启用全局延迟加载 lazyLoadingEnabledtrue|false也可以在mapper文件中开启局部延迟加载 fetchTypelazy 延迟加载的原理 当执行查询语句时MyBatis并不立即加载关联对象的数据而是生成一个代理对象代理对象中包含了关联对象的引用和相关的属性。 当访问代理对象中的关联对象属性时MyBatis会判断是否需要进行延迟加载。 如果需要延迟加载则会执行查询语句获取关联对象的数据并将数据填充到代理对象中的属性上。 通过代理对象可以访问到关联对象的数据。 Mybatis一二级缓存 一级缓存SqlSession级别的缓存不同的 SqlSession 之间的缓存互相独立,默认开启且不能关闭 二级缓存namespaces和mapper级别的缓存需要手动开启二级缓存被多个SqlSession共享同时属性类需要实现序列化接口用于将对象转换为字节流以便于存储或传输 当一级缓存/二级缓存进行了 C/U/D 操作后默认该作用域下所有 select 中的缓存将被 clear。 SOA 微服务 分布式 SOA面向服务的体系结构Service-Oriented Architecture SOA架构是一种面向服务的架构系统的所有服务都注册在总线上当调用服务时从总线上查找服务信息然后调用 微服务架构 微服务是一种更彻底的面向服务的架构将系统中各个功能个体抽成一个个小的应用程序基本保持一个应用对应一个服务的架构 分布式架构 分布式架构是指将单体架构中的各个部分拆分然后部署不同的机器或进程中去SOA和微服务基本上都是分布式架构的 SOA 与微服务的关系 SOA 即面向服务的架构SOA 是根据企业服务总线ESB模式来整合集成大量单一庞大的系统。微服务可以说是 SOA 的一种实现将复杂的业务组件化但它比 ESB 实现的 SOA 更加的轻便敏捷和简单。 CAP和BASE CAP一致性可用性分区容错性 分布式系统节点通过网络连接一定会出现分区问题P 当分区出现时系统的一致性(C) 和可用性(A)就无法同时满足 BASE理论 基本可用 BA 软状态 S在一定时间内允许出现中间状态比如临时的不一致 最终一致 E Springboot Springboot使用“习惯优于配置”的理念整合了很多框架不需要手动写一大堆xml配置可以快速的完成spring和其他框架的构建整合进一步提升开发效率 内嵌了tomcat不需要额外配置 可以通过mvn clean package打包成一个jar包后直接通过java -jar 运行springboot项目 springboot常用配置 SpringbootApplication注解标识这是springboot工程这个注解包含了三个注解 SpringbootConfiguration跟Configuration一样表示这也是个配置类 EnableAutoConfiguration开启自动配置 这个注解内有个 import 注解会读取META-INF/spring.factories 中springboot提供的多个自动配置类根据条件注解决定是否导入到容器中比如RedisAutoConfigurationRedisAutoConfiguration类上方标识了一些注解有 // 标识配置类 Configuration(proxyBeanMethods false ) // 判断是否有redis字节码也就是当导入redis启动器依赖后就会加载RedisAutoConfiguration并将redis配置类中的bean全部放入springboot容器中 ConditionalOnClass({RedisOperations.class}) 又因为RedisAutoConfiguration类内部注入了 RedisTemplate 所以当我们导入redis相关依赖后就可以直接使用RedisTemplate 同时redistemplate这个Bean上还有个注解 ConditionalOnMissingBean用于判断容器中是否存在这个bean如果有则不用再加载。 ComponentScan标识扫描路径 springboot如何启动tomcat 首先springboot在启动时会创建一个spring容器 在创建spring容器过程中利用ConditionalOnClass注解判断是否存在tomcat依赖因为springboot内嵌了tomcat所以存在并会生成一个启动tomcat的bean spring容器创建完成后会获取启动tomcat的bean并创建bean对象绑定端口等然后启动tomcat springboot配置文件加载顺序 优先级从高到低高优先级的配置覆盖低优先级的配置所有的配置会形成互补配置 。 命令行参数。java -jar springbootdemo-0.0.1-SNAPSHOT.jar --server.port8081 --server.servlet.context-path/bcb Java系统属性 System.setProperty() 操作系统环境变量 System.setEnv() jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件 jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件再来加载不带profile jar包外部的application.properties或application.yml(不带spring.profile)配置文件 jar包内部的application.properties或application.yml(不带spring.profile)配置文件 Configuration注解类上的PropertySource指定外部属性文件的位置 SpringCloud spring Cloud是基于Spring框架的一套用于构建分布式系统和微服务架构的开发工具集合。它提供了一系列的组件和模块比如EurekaRibbon, Feign, Hystrix, Zuul, nacos SpringCloud和Dubbo的区别 Springcloud是一个微服务框架提供了很多功能组件Dubbo是一个RPC的分布式框架核心是解决服务间调用的问题SpringCloud更全面Dubbo则更侧重于服务调用 RPC是什么 远程过程调用, 是一种用于实现分布式系统中不同节点之间通信的协议它使得在网络上的不同计算机之间可以像调用本地方法一样进行方法调用可以基于http协议也可以直接基于TCP的连接Dubbo是一个RPC框架 分布式事务 分布式事务就是一次业务处理可能需要不同服务来实现比如用户发送一次下单请求会设计到库存服务如果没有分布式事务就可能出现订单创建成功库存却没有减少解决方案有 本地消息表在数据库中创建一个表创建订单时将减库存消息加入本地消息表中一起提交到数据库然后调用库存服务如果成功则修改本地消息表中的记录为成功如果失败则由定时任务从本地消息表中取出未成功的消息重新调用库存系统 Seata分布式事务框架 服务熔断、服务降级、服务雪崩、服务限流 服务雪崩当服务器A调用服务器BB调用C此时大量请求访问A假设A扛得住但是C扛不住就会导致B堆积了很多请求最终导致A也扛不住解决方法就是熔断和降级 服务降级当发现系统压力过载时为了确保服务不会受请求突增导致服务崩溃通过histrixfeign实现在feign接口编写fallback的降级逻辑来减轻服务压力。也就是如果该接口因压力过大无法访问就会走fallback的逻辑。 服务熔断默认关闭需要手动打开如果检测到 10 秒内请求的失败率超过 50%就触发熔断机制。之后每隔5 秒重新尝试请求微服务如果微服务不能响应继续走熔断机制。如果微服务可达则关闭熔断机制恢复正常请求 服务限流在高并发情况下为了保护系统对请求进行数量限制防止系统不被大量请求压垮在秒杀中限流很重要 Nacos和Eureka的区别 共同点都用作注册中心 都支持服务注册和服务发现 都支持服务提供者心跳检测 区别 nacos可以设置是否为临时实例模式通过ephemeral:true/false)默认是临时实例模式临时实例采用心跳检测非临时模式采用服务端主动检测模式 临时实例心跳不正常会被剔除非临时不会。 当服务提供者列表更新nacos会及时推送更新消息给消费者 nacos集群默认是AP(高可用)模式当集群中存在非临时实例时采用CP(强一致)模式。eureka只支持AP模式 nacos还有配置中心的功能 Redis Redis是什么 redis是一个key-value类型的非关系型数据库redis可以存储String, hashlistsetSorted Setredis的数据都是存在内存中的所以读写速度非常快但是如果电脑关机会导致数据丢失所以redis也提供了持久化策略RDB和AOF。也经常用来做分布式锁 Stringvalue可以是String也可以是数字 hashvalue是一个结构化的对象,存储键值对在做购物车时用到了userid作为key购物车id作为value的字段商品作为value的值 list既可以作栈也可以做队列可以做简单的消息队列的功能。 set存放不重复值的元素可以进行交集并集差集等操作。可以实现去重共同关注朋友圈点赞的功能 sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用 为什么是单线程的 代码更清晰处理逻辑更方便 避免不必要的上下文切换 不存在多线程导致的切换而消耗 CPU 不用去考虑各种锁的问题不存在加锁释放锁操作没有因为可能出现死锁而导致的性能消耗 redis持久化方式 RDB(Redis DataBase)设置指定时间间隔将redis存储的数据生成快照写入磁盘可以手动save执行RDB也可以bgsave开启子进程执行RDBRDB内部通过bgsave自动间隔执行RDB。适合可以容忍数分钟的数据丢失追求更高的启动速度 AOF(Append Only File)默认关闭将redis执行过的所有写操作记录到AOF文件在下次redis重启时将这些写操作从头到尾执行一遍就可以恢复数据了。通过配置redis.conf可以设置AOF记录命令的频率always同步刷新everysec每秒no操作系统控制适合对数据完整性要求高 数据过期策略 惰性删除访问key的时候判断是否删除如果过期则删除 定期删除定期检查一定量的key是否过期有SLOW模式和FAST模式SLOW模式是个定时任务默认10hz(1秒10次)FAST模式执行频率不固定两次清理时间不超过2ms Reids两种配合使用 缓存数据一致性 失效模式修改数据库时顺带删除缓存或者先删除再修改下次直接从数据库获取最新的不过会有问题脏数据 请求A进行写操作删除缓存 请求B查询缓存发现没有就查数据库的旧数据 请求B将旧值写入缓存 请求A将新值写入数据库 延迟双删先删除缓存然后更新数据库延迟比如100毫秒再次删除缓存但是依旧有脏数据的风险因为延时的时间不好把控 读写锁redissonClient.getReadWriteLock()强一致性能低 通过MQ异步通知修改数据后异步发布消息给MQ缓存服务通过监听MQ来更新缓存。这个方法的可靠性取决于MQ的可靠性 redis实现分布式锁 原生redis分布式锁 通过setnx(lock,uuid,10s)来获取锁 获取到锁 执行业务 因为要保证get和del操作的原子性所以通过lua脚本删锁。 没获取到锁 重试 public MapString, ListCatelog2Vo getCatelogJsonFromDbWithRedisLock() {String uuid UUID.randomUUID().toString();// 如果业务执行时间过长可能导致锁的自动释放所以使用uuid作为唯一标识防止被其他线程误删锁 Boolean lock redisTemplate.opsForValue().setIfAbsent(lock, uuid, 30, TimeUnit.SECONDS);if (lock) {MapString, ListCatelog2Vo dataFromDb null;try {// 加锁成功 执行业务dataFromDb getDataFromDb();} finally {// 因为要保证get和del操作的原子性所以通过lua脚本删锁。// 原子删锁String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;Long lock1 redisTemplate.execute(new DefaultRedisScriptLong(script, Long.class), Arrays.asList(lock), uuid);}return dataFromDb;}else {// 如果当前已有锁则调用自己重新加锁相当于synchronized()自旋的方式return getCatelogJson();} } redisson分布式锁 RLock rLock redissonClient.getLock(lockName); try {boolean isLocked rLock.tryLock(expireTime, TimeUnit.MILLISECONDS);if (isLocked) {// TODO} catch (Exception e) {rLock.unlock();} } redisson实现分布式锁主要利用setnx命令只需要一个RLockRLock是redisson的最核心接口通过 redissonclient.getLock() 获取一个RLock对象然后通过RLock实现加锁和解锁。Redisson实现加锁的原子性也是依赖lua脚本加锁解锁时设置了计数器通过计数器来实现可重入锁特性。常用的加锁有lock()和trylock()前者是阻塞式加锁后者是非阻塞式加锁有返回值可以根据返回值做判断。 加锁可以设置过期时间如果没设置则默认30s过期主要是为了防止死锁。虽然设置了默认过期时间但如果程序执行时间超过了30s此时锁过期释放其他线程就有可能加锁成功这样就会有线程安全问题所以redisson实现了一个看门狗机制是用来自动延长加锁时间的。看门狗机制本质上是一个定时任务当没有设置过期时间客户端会创建一个定时任务来延长加锁时间默认每10s执行一次。不过如果没有设置过期时间并且你的程序没有主动释放锁这样是会造成死锁的因为看门狗会不断延长过期时间。 redis主从复制的核心原理 全量同步 从节点请求主节点同步数据包括 replication id、offset 主节点判断是否第一次请求是的话就跟从节点同步版本信息包括replication id、 offset 主节点执行 bgsave生成rdb文件后发送给从节点去执行 在rdb生成执行期间主节点会将在这期间的写请求以命令的方式记录到缓冲区 最后rdb完成后再把刚才记录的命令文件日志发送给从节点同步 增量同步 从节点请求主节点同步数据主节点判断不是第一次请求就获取从节点的offset值 主节点从命令文件日志中获取offset值之后的数据发送数据给从节点同步 怎么保证redis的高并发高可用 通过哨兵机制哨兵作用如下 监控哨兵会不断检查master和slave结点是否按预期进行 哨兵会每隔一秒向集群发送ping命令如果某个哨兵发现某个节点没有及时响应则认为该节点 主观下线 如果超过指定数量的哨兵都认为该节点 主观下线 则判定为 客观下线 自动故障回复如果master故障哨兵会将一个slave提升为master故障回复也以新的master为主选主规则 首先判断主从节点断开时间长短排除断开时间超过指定值的slave 然后判断从节点的 slave-prority 值越小优先级越高 如果 slave-prority 一样则判断 offset 值 值越大优先级越高因为offset值越大表示跟主节点的数据越相似。 最后判断slave节点的运行id大小越小优先级越高 通知集群发生故障时哨兵会将最新消息推送给客户端告诉客户端连接哪一个redis redis集群脑变怎么解决。 由于主从节点和哨兵处于不同的网络分区由于网络时延或者其他原因导致哨兵无法感知到主节点的心跳导致选举推选一个从节点为master这样就存在了两个master这样会导致客户端依旧往旧的master写数据当网络正常后哨兵会将旧的master降为从节点再从新的master同步数据就会导致之前的数据丢失 解决方法修改配置设置最少存在的slave节点数量以及缩短主从同步的延迟时间 redis为什么单线程也这么快 基于内存操作 不需要线程上下文切换也不需要担心线程安全问题造成锁的开销 采用io多路复用非阻塞Io redis网络模型 采用io多路复用事件的处理器比如连接应答处理器接收请求命令回复处理器响应结果命令请求处理器转化命令执行语句在redis6中命令回复处理器采用了多线程来处理回复事件在命令请求处理器中将命令的转换使用了多线程但是在命令执行的时候依旧是单线程 Linux 常用命令 ls: 列出文件和子目录 cd切换到指定目录 pwd显示当前所在目录的路径 cat显示文件内容 touch创建新文件或更新文件的时间戳 cp复制 mv移动或重命名 rm删除文件或目录 mkdir用于创建新目录 rmdir删除空目录 grep在文件中查找匹配的字符串或模式 find查找文件 top显示当前运行的进程和系统资源的使用情况 ps显示当前运行的进程信息 ping测试网络连通 nginx 常见配置项 http块 配置server块: 1.1 listen字段监听端口 1.2 server_name字段服务名 1.3 location字段定义url匹配规则和相应的处理方式如 1.3.1 proxy_pass反向代理到指定的服务器 1.3.2 rewrite重写url 1.3.3 expires设置静态资源的过期时间 upstream块用于负载均衡可以将location块中的proxy_pass代理的服务器负载均衡分流缓解压力 proxy_cache_path 设置图片缓存路径然后需要在location块中配置 proxy_cache image_cache; 负载均衡策略 轮询法按请求顺序轮流分配 随机法随机选取一台机器 哈希法根据客户端的ip地址通过哈希算法得到一个数值用该数值对服务器列表大小进行取模运行得到的结果就是选取的服务器的序号 加权轮询法为机器配置权重根据权重和请求顺序分配 加权随机法按照权重随机分配 最小连接数法根据后端服务器当前的连接情况动态地选取其中积压连接数最少的一台服务器 git 什么是git?为什么要使用git? git是一个免费的开源的分布式版本控制系统为了保留之前开发的版本方便回滚和修改 集中化版本控制系统和分布式版本控制系统的区别 集中化的有svn当中央服务器发生故障故障期间谁都无法提交更新git是分布式git是把代码仓库完整地镜像到本地即使中央服务器故障也可以将代码先提交到本地仓库等恢复了再提交中央仓库 git命令 git init 初始化 git add ./filename 添加所有/文件名 git commit -m 提交-m可以添加提交信息 git status 查看工作区状况 git log 查看提交历史 git reset 回滚 git branch 查看分支 git branch 分支名称 创建分支 git checkout 分支名称切换分支 git merge 要合并的分支名称注意要先切换到目标分支再合并 git branch -d 分支名称删除分支 pull和fetch的区别 fetch只是把最新版本下载到本地不会merge。 git pull origin devgit fetch origin devgit merge origin/dev; git fetch更保险一些git pull操作更简单 git冲突 当多个开发者同时对同一文件的同一部分修改时就可能产生git冲突 产生的情况 合并分支时 拉取远程更新时 解决方法 打开冲突文件 修改文件同时删除冲突标记 git add 标记冲突解决 git commit fastdfs fastdfs是一个轻量级分布式文件系统 使用步骤 安装和配置 安装好后需要配置tracker和storage的配置文件 启动tracker和storage fastdfs分为tracker和storage两个角色tracker负责文件上传和下载storage负责文件存储需要先启动tracker再启动storate 配置项目代码 在代码中配置fastdfs配置如tracker地址端口等 实现操作 docker 常用命令 创建镜像docker build -f xxx -t xx -f指定DockerFile文件的路径名 -t镜像的名字及标签 查看镜像docker images | grep 镜像名 创建一个新容器并运行docker run -p 8089:8089 --name 镜像名 -d 镜像名:v0.1 -p指定端口号 主机端口:容器端口 -d后台运行 查看运行中的容器docker ps | grep 镜像名 查看所有容器docker ps -a 进入容器内部docker exec -it 容器id /bin/bash 删除容器docker rm -f 容器id -f强制删除 删除镜像docker rmi -f 镜像名:版本号
http://www.dnsts.com.cn/news/30508.html

相关文章:

  • 可信的专业网站建设外贸网站建站那家公司好
  • 大连网站排名优化公司机票搜索量
  • 软件开发相关文档蚌埠seo招聘
  • 商务网站建设毕业设计为什么要建设门户网站
  • 网站模板怎么改国外优秀设计网站大全
  • 如何建立一个学校网站广州建设工程信息网站
  • 织梦高端html5网站建设工作室网络公司网站模板响应式网站跟一般网站的区别
  • 清远网站seo网站建设首选唯美谷
  • 网站建设的书籍音乐中文网站模板下载
  • 资阳专业网络推广方案seo对网站的作用
  • 网站404页面模板织梦网站后台密码忘记
  • 运城市做网站价格园林景观设计公司发展规划
  • 网站建设程序流程wordpress 密码生成
  • 景区类网站河北建设银行石家庄分行招聘网站
  • 深圳市住房和建设局官网站首页服务器做jsp网站教程视频播放
  • 个人网站开发的背景实体店铺怎么引流推广
  • 电子政务与网站建设经验企业如何数字化转型
  • 哈什么网一个网站做ppt广告平面设计好学吗
  • 东莞外贸网站设计吉林省建设项目招标网站
  • it网站建设资讯网wordpress点赞代码
  • 网站二次备案天津规划设计公司
  • 西安网站建设建站系统上海百姓网免费发布信息网
  • 深圳分销网站设计包装设计网站免费
  • 电脑网站怎么制作郴州新网招聘手机版
  • 做传奇网站焦作网站开发
  • 域名备案的网站名称微信小程序平台登录入口
  • WordPress搭建流媒体网站局域网电脑做网站
  • 做网站用虚拟主机怎么样杭州pc手机网站建设
  • 金融服务网站建设平台推广方式方法是什么
  • 百度注册域名免费建站网站开发有什么点子