java面试问题总结

本文涵盖了Java虚拟机(JVM)的内存模型,包括堆、元数据区、栈等,并讨论了线程的概念。接着介绍了数据库的相关知识,如事务、锁的类型、数据库的ACID特性以及MySQL的B/S架构和事务处理。进一步讨论了Spring框架和SpringBoot,以及数据结构和设计模式,如二叉树、红黑树和设计模式的分类。最后提到了计算机网络中的TCP与UDP的区别以及数据通信的相关概念。
摘要由CSDN通过智能技术生成

JVM

JVM(Java Virtual Machine)java虚拟机:用来运行字节码文件,不同的操作系统运行的结果相同。Java中默认的虚拟机为HotSpot虚拟机。还有其他虚拟机(TaoBao VM,JRockit,j9)

线程共享:元数据区,堆

线程独享:程序计数器,本地方法栈,虚拟机栈

JMM(Java Memory Model) java内存模型

  1.程序计数器:每当执行一条字节码指令都需要程序计数器改变值来完成,通过程序计数器执行字节码指令来完成顺序,选择,循环等语句。

               当进行线程切换后,利用程序计数器来确定切换后线程的位置。

               每个线程都有独立的程序计数器,且永远不会发生outofMemoryError错误。

  2.本地方法栈:该栈中存放本地方法(native修饰的方法),以及方法中的操作数栈,局部变量表等。

  3.虚拟机栈: 当我们调用方法时,形成一个栈帧,每一个栈帧都代表一个方法,栈帧中有局部变量表,操作数栈,动态链接,方法返回操作。

                注:存储未逃逸的对象(返回值为void,无法将对象带出来)和对象的引用

               利用栈的后进先出的特征,类似递归的操作。

  4.堆:我们一般创建的对象都存在堆中(jdk1.7后,字符串常量池从方法区移到堆中)。

         为了垃圾回收的精准以及管理,从而分为新生代,老生代。8

         特点:新生代与老生代占比 1:2

               新生代中的Eden:fromSurvior:toSurvior=8:1:1

         一个对象在堆中的分配流程:首先检查Eden是否可以分配,若可以分配则分配空间。若分配不了,则通过minorGC对旧对象进行垃圾回收,每次minorGC的时候先将没有引用的对象进行清除,清除完成后若是无法进入survior则进入老生代,若是可以进入survior那么将幸存的对象放到s0/s1的未使用空间中,在将原有的对象通过复制全部放到未使用空间,从而留出一个未使用空间,下次若是来时,则进行空间交换。每次交换age加1,直到15时则放置到老生代中。垃圾回收后再进行判断Eden是否可以存的下,若放不下,说明是一个大对象,进入老生代,若老生代可以放则放置,若也不行发生outofMemoryError错误。

         错误:java heap space:java堆中的空间不足以放新对象

               GC Overhead limit Exceeded:当JVM浪费过多时间用在垃圾回收中,且只能回收到小部分空间时,触发的错误。

  5.  元数据区:在jdk1.8之前是方法区,1.8之后为元数据区,从而使容量更大,体现在,元数据区在本地内存中,内存有多大,就可以使用多大,也可以指定初始容量和最大容量。在默认的HotSpot中,元数据区也称为永生代。

                永生代与老年代绑定到一起,无论谁满,都会触发永生代和老年代的垃圾回收机制。

JVM的内存分配策略:

  1. 对象优先分配到Eden中
  2. 大对象直接进老生代
  3. 存活时间旧的直接进入老生代

垃圾收集算法:

  1. 标记-清除算法

先标记出所有不需要回收的对象,然后将没有标记的对象进行回收

缺点:容易产生大量不连续的内碎片

       影响性能

  1. 复制算法

标记不需要回收的对象,将内存分为两部分,将标记的对象复制一份到另一份空间中,将原有的空间进行删除。

缺点:每次只能使用1/2的空间,内存利用率太低

  1. 标记-整理算法

与标记清除策略前期一样,先将不需要回收的对象进行标记,然后将没有标记的对象进行回收,并将存活的对象向一端移动。

优点:不会产生大量内碎片

  1. 分代收集算法

Java堆分为新生代和老生代,根据不同的代来设计不同的垃圾收集算法

由于新生代,会有大量对象死亡,则可以采取标记-复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。

老生代,有大量的存活对象,可采用标记-清除/标记-整理算法进行垃圾回收

垃圾收集器:

  1. Serial收集器

Serial(串行)收集器,新生代采用复制算法,老生代采用标记整理算法。是一个单线程收集器。通过一条垃圾收集线程去完成垃圾收集工作,在进行垃圾收集时,必须暂停所有的工作线程(Stop The World),直到收集结束为止。

适用场景:客户端模式下,如用户桌面的应用场景以及部分微服务应用中。

  1. Serial Old 收集器(老生代)

进行老生代回收,通过标记整理算法

  1. ParNew收集器(新生代)

新生代采用复制算法,老生代采用标记整理算法。是一个多线程收集器,其余与Serial收集器一样的。

适用场景:服务器模式下,jdk1.7之前首选的新生代收集器

  1. Parallel Scavenge收集器(新生代)

是jdk1.8默认的收集器,新生代采用复制算法,老生代采用标记整理算法。

是一种多线程收集器。原有的收集器,都是为了通过减少用户线程的暂停时间,提高响应度。而该收集器关注点是吞吐量(每秒cpu可以执行的数据量),尽快完成程序的运算任务,适合用于后台运算而不需要太多的交互任务。

适用场景:高吞吐量,在服务器端,充分利用cpu资源尽快完成程序的运算任务。

  1. Parallel Old 收集器(老年代)

Parallel Scavenge收集器的老年代版本。

  1. CMS收集器(老年代)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,是HotSpot虚拟机第一款有意义的并发收集器,实现用户线程与垃圾收集线程并发工作。采用标记清除算法进行(存活的对象过多)。

四个步骤:

          1 初始标记:暂停所有的线程,并记录与root直接相连的对象,速度快

          2 并发标记:同时开启GC和用户线程,通过闭包结构和可达性分析,对可达性的对象进行标记

          3 重新标记:由于用户线程在运行时可能产生新的对象,从而进行重新标记,暂停其他所有线程,时间一般比初始标记长,但远小于并发标记

          4 并发清除:开启用户线程,同时GC线程并对未标记的对象进行清除。

       优点:并发收集,低停顿

       缺点:

1对cpu资源敏感:由于并发标记和并发清除是和用户线程一起运行的,收集中占用了用户线程的cpu资源,从而导致用户的响应度受到影响。

              2无法处理浮动垃圾:当并发清除时,用户线程产生的新的对象,可能有新的垃圾,从而在本次清除中无法清除干净,只能等到下次收集的时候才能回收

              3 产生大量空间碎片,由于CMS算法基于标记清除算法实现的,所以进行大量的垃圾回收时,会产生大量的内碎片,从而产生大量不连续的空间。

        适用场景:较为关注用户的响应度,希望暂停时间较短,为用户带来更好的交互体验的应用。

            

强引用:当对象进行new操作时,就为一个强引用,不会被垃圾回收器回收。当内存空间不足,不会被垃圾回收器回收。当内存不足,Jvm宁可抛出OutOfMemoryError错误,也不会回收这种对象

软引用:描述一些有用但没必要的对象,只有当内存不足时JVM才会回收对象

弱引用:当JVM进行垃圾回收时,无论内存是否充足都是要回收被弱引用关联的对象。也是值我们一个短生命期的引用指向长周期的对象。不过由于垃圾回收器的优先级很低,不一定及时发现只具有弱引用的对象。当弱引用对象被垃圾回收后,jvm将这个弱引用加入到与之相关的引用队列中

       虚引用:若持有当前引用,任何时候都可以被垃圾回收器回收,必须和引用队列联合使用。若发现引用队列中由某个虚引用,那么可以在所引用的对象的内存被回收之前采取必要的行动

数据类型

基本数据类型(8)

数值型:整型 byte(1字节)short(2字节)int(4字节)long(8字节)

浮点型 float(4字节)double(8字节)

非数值型:char(2字节)boolean(编译为int时为4字节;byte数组时,元素为1字节)

  1. String属于什么数据类型

String属于引用数据类型,位于堆区(heap)

  1. 两个整数做和运算,你会怎么做

若为int类型,整数值小于int最大值,则直接相加,若溢出则使用long类型,同理

若超出long类型的最大值,则使用BigInteger+String来处理

  1. String a=new String(“b”)创建了几个对象

创建了2个对象,new关键字创建一个对象;在字符串常量池创建了b”对象

泛型

  1. 什么是泛型

泛型就是类型参数化。在不创建新类时,可以替代传入的数据类型

例如:在使用集合时的T,S,V等都是泛型,当使用时,可以代替传入的参数类型

Java特征

  1. 谈谈你对面向对象的理解

面向对象的特征:封装,继承,多态

封装:隐藏事物内部细节,确保安全性,只为外部提供一个接口

继承:代码复用,在一个类的基础上扩充的新类,可以在新类中添加新的方法和功能

多态:一个对象具有多种状态,父类的引用指向子类的对象,继承,重载,重写,向上转型,向下转型等。只有在运行时才确定具体子类的方法

  1. JDK1.8的新特性
  1. 接口中出现Default的默认方法
  2. Lambda表达式(匿名内部类)
  3. 函数式接口简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解:@FunctionalInterface
  4. 常用的函数型接口:

Predicate接口:断言型接口,返回boolean型

Funcation接口:接受参数后可以返回结果

Consumer接口:不做任何处理

  1. 对象变流的方式:

对象.stream()将对象变成流进行处理。

常用的方法有

Foreach(consumer接口):遍历

Map(funcation接口)映射

Filter(predicate接口)过滤

Collect(Collectors.toList/map/set()接口)收集

  1. 编程规范
  1. 接口命名时,开头用Ixxx表示
  2. 接口实现类,结尾用xxxImpl表示
  3. 数据库查询时,避免使用select* ,应该使用select 字段名
  4. 常量命名:全部采用大写
  5. 包名使用:域名反转+项目名
  6. 小驼峰:单词首字母小写,但其余单词首字母大写
  7. 大驼峰:单词首字母大写
  1. 匿名内部类与Lambda表达式的区别
  1. 所需类型不同:

匿名内部类:可以是接口,抽象类,具体类。

Lambda表达式:只能是接口

  1. 若接口有且只有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类

若接口中多于一个抽象方法,只能使用匿名内部类

   3. 结构不同:

       匿名内部类:new 方法名(){}

       Lambda表达式:方法名忽略 (参数1,参数2,)->{}

重写和重载

  1. 什么时重写和重载

重载:编译期,当方法名相同,参数列表不同,与返回值无关时,则为重写

重写:运行期,发生在子类对父类方法进行重写时。方法名,参数类型必须与父类方法一致,但子类的修饰符要大于等于父类。而异常类型要小于父类

异常

  1. 常见的异常有哪些

NullPointerException(空指针异常)

ClassCastException(类型转换异常)

IndexOutofBoundException(数组下标越界异常)

SQLException(sql异常)

IOException(io异常)

ClassNotFound(类没有找到异常)

NumberFormateException(数字格式化异常)

NoSuchMethodException(方法未找到异常)

IllegalArgumentException(参数不合法异常)

  1. Java的异常体系

Exception:继承Throwable类;异常就是程序在运行时,由于网络问题,输入错误,程序逻辑等原因,导致程序暂时性停止去处理异常,处理后并输出

异常分为:Checked检查型异常(直接继承Exception,必须要处理)和NoChecked非检查型异常(继承RunTimeException,可以不用处理)

关键字:try/catch/throws/throw

Try:将可能出现异常的代码包围

Catch:若try中出现异常,则跳转到catch中更加异常类型进行打印堆栈信息

Throw:创建异常对象

Throws:在方法后声明异常类型

  1. 如何处理异常

关键字:try/catch/throws/throw

Try:将可能出现异常的代码包围

Catch:若try中出现异常,则跳转到catch中更加异常类型进行打印堆栈信息

Throw:创建异常对象,若为检查型异常必须处理,若为非检查型异常则可以不用处理

Throws:在方法后声明异常类型

反射

  1. java反射机制

反射:Java应用程序运行时对自身的一种”自审”,当jvm加载类时,创建了该类的class对象,并保存了该类的全部信息。

Class类中有field(成员变量),method(方法),constructor(构造方法),superClass(父类)

集合

  1. 谈谈对集合的理解

集合是java.util包中的,是一种数据存储容器

分为Collection接口(value)和Map接口(key-value)

Collection:

List接口:有序可重复的

Set接口:无序不可重复的

Map接口:无序不可重复的

List接口:

ArrayList: 有序可重复----实现RandomAccess接口,实现下标查询

底层使用Object数组,初始化容量为10,扩容到1.5倍。

特点:基于数组,查询效率快,插入删除效率慢

LinkedList:基于双向链表;有序;查找速度慢,添加和删除效率快

Vector:线程安全,有大量的synchronized线程安全的方法;

  底层是object数组,初始容量为10,扩容到2倍

Set接口:

HashSet:底层使用HashMap,以key为主,value为空对象

LinkedHashSet:底层使用LinkedHashMap,有序

TreeHashSet:底层使用TreeMap,可自动排序,实现Compareator,Compareable接口

Map接口:key-value键值对为主,无序不可重复

HashMap: key-value可以为空;底层使用数据+链表+红黑树,提高搜索效率

无参为16,有参则根据原有容量*0.75来扩容到原来的二倍

数组长度大于64,链表长度大于8则转为红黑树

LinkedHashMap: 底层是单向链表;继承于HashMap;有序

TreeMap:底层红黑树;可以自动排序;实现Compareable(compareTo()方法)和Compareactor(compare()方法)接口

HashTable:key,value不允许为空,为空发生空指针异常

线程安全;底层数组+链表,无参:11 有参扩容到2倍+1

  1. 集合中哪些线程是安全的

CopyOnWirteArrayList:

Java.util. concurrent并发包下的线程安全的List集合,兼顾安全和性能

在线程安全的情况下代替vector和ArrayList.进行增删改操作时,通过ReentrantLock加锁,复制出来一个新数组的方式来实现线程安全,用于读多写少的情况。

对原有的数据进行一次复制,将修改的内容写入新副本,写完后,再将新数据代替原有数据,保证写操作不会影响读操作。只能保证数据的最终一致性,但不保证数据实时一致性。增删改为新数组,读写为原数组。

缺点:由于每次都要创建一个新数组,消耗内存。

 只能保证数据最终一致性,不能保证数据实时性。

ConcurrentHashMap:

基于线程安全的HashMap,底层采用分段的数组+链表+红黑树;将数据分为一段一段的存储,给每一段数据分配一把锁,当一个线程占用这个数据段的锁时,其他段也可以被其他线程访问。

Jdk1.7: 一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构,一个segment包含一个HashEntity数组,每个Entity是一个链表结构的元素,一个segment守护一个Entity数组的全部元素

Jdk1.8:取消了segment分段锁,采用CAS和synchronized来保证并发安全。Synchronized只锁定当前链表或红黑二叉树的首节点,提高了效率。

    

  1. HashMap在多线程环境中存在线程安全问题,一般如何处理
  1. 使用Collections.synchronizedMap(Map)创建线程安全的map集合
  2. 使用Hashtable
  3. 使用ConcurrentHashMap

SynchronizedMap内部维护了一个普通的Map对象,还有排斥锁mutex,若构造方法中没有传入mutex,则排他锁为当前对象,若构造方法有则采用传入的对象做锁

Hashtable内部的方法全部采用Synchronized进行加锁,封锁粒度过大且为重量级锁。

ConcurrentHashMap:

   Jdk1.7:数组+链表;采用sgement分段锁进行处理,sgement数组中包含了hashEntry数组,保证一个线程访问的同时,其他线程可以访问其他的分段数据。此外,为value添加了volatile(可见性,禁止指令重排,无原子性),当进行put操作时,先通过sagment中的(table-1)&hash(key)>>>低16位得到index,先尝试获取锁,若获取不到则代表其他线程在竞争,从而进行自旋,若自旋失败进行锁升级,从而进行赋值。其get操作就是通过key和hash获取指定位置进行拿值即可。

Jdk1.8 数组+链表+红黑树,采用synchronized+CAS保证线程安全性。底层可是给value加volatile保证其他线程拿到的value是最新的。在put操作时,通过一系列的运算获取index下标,若下标为null,则代表可以进行添加,先通过CAS尝试写入,若写入失败进行自旋,若自旋次数超过指定此时后,通过synchronized锁进行写入,其他线程等待。若不为空,则判断key是否一致,若一致则覆盖,若不一致,则进行向链表尾部添加数据。(synchronized只在链表首部和红黑树首部进行加锁)

   

4.  fail-fast和fail-safe的理解

fail-fast(快速失败):java集合中的一种机制,适用于hashmap,在迭代器遍历一个集合对象时,如果遍历过程中对象的内容进行了修改(添加,修改,删除),从而抛出ConcurrentModification Exception.

原理:迭代器遍历时直接访问集合中的元素,并在遍历过程中使用一个modcount变量。

   集合在遍历期间若内容发生变化,就改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,先检查modCount是否为expectedmodCount值,是的话返回遍历,否则抛出异常,终止遍历。

由于集合元素发生改变时,若将modCount设置为expectedmodCount,则不抛出异常,因此,不能依赖是否抛出异常而进行并发操作,该异常仅用于检测并发修改的bug

Fail-safe(安全失败):java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

   原理:由于迭代时是对原有集合的拷贝进行遍历,所以在遍历过程中对原有集合所作的修改并不能被迭代器检测到,从而不引发fail-fast.

   缺点:在迭代器遍历拷贝集合时,无法知道原集合的修改

Mysql数据库

  1. 谈谈对事务的理解

事务:对数据操作的管理

ACID:原子性:事务不可被分割,要么提交要么回滚

     一致性:数据的两边改变的一致的

 隔离性:每个事务都是独立的

 持久性:提交的事务,不能在改变

事务的隔离级别,用于解决隔离问题:

  1. read uncommitted:读未提及:事务a读取了事务b没有提交的数据
  2. read committed:读已提交:事务a读取了事务b提交的数据,解决了脏读
  3. repeatable read:允许重复读:事务a可以读到重复数据,哪怕事务b已经修改提交(解决了脏读,不可重复读)
  4. serializable:单事务,同时只有一个事务操作,另一个事务挂起(解决了脏读,不可重复读,丢失读写)

事务的隔离级别产生的原因:

  1. 丢失修改:事务A与事务B同时对数据进行修改,事务B的提交破坏了事务A的提交,导致事务A的修改丢失
  2. 不可重复读:事务A读取某一数据后,数据B对其进行了修改,删除,从而导致事务A读取的数据无法再现前一次的读取结果
  3. 幻读:事务A读取某一数据后,事务A对其进行了添加操作,导致事务A读取时发现了一些原有不存在的数据,和梦幻一样。
  4. 脏读:事务A的修改,事务B进行读取,事务A进行回滚,事务B读取时出现错误

事务的实现原理:

  1. redo log(重做日志持久性):记录数据修改后的变化,当MySQL宕机后。InnoDB存储引擎通过使用重写日志进行恢复,保证数据的持久性
  2. undo log(回滚日志原子性):保存了事务发生之前的版本,用来回滚
  3. 锁机制/MVCC(多版本并发控制事务隔离型):MVCC是锁机制的一个变种,通常在很多情况下避免加锁,开销低,实现了非阻塞的读操作,写操作了该锁的某一行

  1. MyISAM与InnoDB的区别

Innodo是第一个完整支持ACID事务的mysql存储引擎

  1. MyISAM只支撑表级锁;InnoDB支持表级锁和行级锁
  2. MyISAM不支持事务,InnoDB支持事务
  3. MyISAM不支持外键,InnoDB支持外键
  4. InnoDB支持在线热备份(运行期间备份;冷备份:运行结束后进行备份)
  5. MyISAM崩溃后损坏率大于InnoDB,并且恢复速度也很慢

  1. 表级锁和行级锁的区别

表级锁:封锁力度最大的锁,作用在表上,实现简单,资源利用较少,但并发度较低,冲突最高,但不会发生死锁

行级锁:封锁力度最小的锁,作用在行上,大大减少发生冲突的概率并发度高,但由于太多的行级锁,导致开销大,加锁慢

  1. InnDB存储引擎的锁的三大算法是:

Record lock:记录锁,单个行记录上的锁

Gap lock:间隙锁,指行范围的锁,不包括记录自身

Next-key: lock:record+gap 临建锁,锁定一个范围,包括记录自身

  1. 谈谈数据库事务的实现原理

MySQL InnoDB引擎使用redo log(重做日志),保证其持久性;使用undo log(回滚日志),保证事务的原子性。

redo log:重做日志,记录事务操作的变化,记录的是数据修改后的值,当数据库宕机可通过重做日志可达到数据恢复的目的。

undo log:回滚日志,保存在事务操作变化之前的数据,用来进行回滚操作(全提交和全回滚)。

InnoDB支持锁机制和MVCC(多版本并发控制)来保证数据的隔离性。

MVCC:行级锁的一个变种,通过非阻塞方式,可以进行多并发操作,避免加锁,对读操作不加锁,对写操作也只是为该行进行加锁。

当持久性+隔离性+原子性,从而一致性也就可以保证。

  1. 数据库的三大范式

函数依赖:表中不存在某两个元组,在属性a相等,在属性b不相等的情况,从而称a函数确定b函数。

第一范式(1NF):表中数据的每一列都不可再分,满足了原子性

第二范式(2NF):表中的非主属性对主属性的完全函数依赖,消除了部分函数依赖

第三范式(3NF):消除了传递函数依赖,要求每张表的每一列和主键直接依赖,而不是间接依赖

  1. 索引

索引:提高数据库查询速度,向图书目录一样,可快速查询到想要获得的数据。索引记录了数据位置。通过位置查询。

索引优点:查找速度快,避免了全表扫描,提高了数据的搜索速度。

          创建唯一索引,保证该列的数据唯一性

索引缺点:  索引的创建和维护都要消耗许多时间;

          若为修改操作,索引也会动态发生改变,导致sql执行效率下降

          当数据库数据量不大时,索引的速度可能不如全表查询。

          索引的创建消耗内存空间

索引的内存结构:B+树,hash

B+树 特点:平衡二叉树(左右子树高度差的绝对值不能超过1)

            所有节点都是依次递增,有序,可分组的。

            父节点在叶子节点中,是叶子节点的最大或最小值

            叶子节点之间维护了一条有序链表

            数据Date放在叶子节点中

   主索引:叶子节点中保存了完整的数据记录。由于数据无法存在不同的两个地方。以访问频率高的属性作为主索引,例如主键。

   辅助索引:叶子节点包含了主键的值,在使用辅助索引后找到主键。通过主索引找到其全部信息。

   查找:通过根节点进行二分查找,找到对应的叶子节点,根据叶子节点进行二分查找,找到key对应的value。由于区间查找时,叶子节点维护了一个链表,无需回表操作,通过指针寻找下一个相邻的叶子节点,提高搜素效率。

    Hash: 数组+链表的结构

          特点:以时间复杂度为O(1),无序,无法用于排序和分组

                只支持精确查询,不支持范围查询

          查找:通过hash运算,得到hash值,再通过与数组长度-1进行&运算得到位置,若位置有元素,查找该元素下的链表。

    自适应哈希索引:当某个索引频繁使用时,可以在B+树上建立哈希索引,让B+树上有了哈希索引的优点,提高快速的哈希查找。

     主键索引:主键索引   

 辅助索引:唯一索引;普通索引;全文索引;前缀索引

 唯一索引:以unqiue索引代表该列数据不能重复,但可以为null

 普通索引:index;为了提高查询速率

 全文索引:Full Text用来进行大量的关键字查询

 前缀索引:Prefix只适合于字符串类型的数据,对前几个字进行索引。

 创建索引注意的事项

  1. 添加索引的字段不能为null,数据库难以优化
  2. 被频繁更新的字段谨慎添加索引
  3. 尽可能考虑联合索引,而不是单列索引
  4. 尽量避免冗余索引
  1. sql优化
  1. 避免select *的用法,使用字段名,否则为全表搜素
  2. 考虑在where和order by后面进行添加索引
  3. Where 后面避免使用 != <> 否定操作,会导致放弃索引
  4. Where 后避免使用对字段进行判null操作,会导致放弃索引
  5. where 后避免使用函数或者运算,导致放弃索引
  6. where 后避免使用全模糊和左模糊,会导致放弃索引
  7. where 后避免使用 or,应该使用 union all
  8. 避免使用 in/not in,导致索引失效

  1. redis和mysql的区别

redis存储与内存中,mysql存储在磁盘中

reids数据存储是key-value键值对,而mysql是值

redis存储内存,速度快,官方文档中介绍redis每秒读110000次

  1. 创建mysql连接的时候主要用到哪些

导jar包/添加依赖

Class.forName(mysql.cj.jdbc)

DriverManager.getConnection(url,username,passowrd)

Con.preparedStatement(sql);

ResultSet set=Pst.executeQuery()

  1. C3p0是干什么的

C3p0是数据库连接池,用到了ComboPooledDataSource类

C3p0用来解决数据库频繁创建和释放连接,从而采用的数据库连接池

C3P0我们可以设置连接池的初始化容量,最大连接数,最小连接数,每次连接的增量,设置url,username,password

  1. MySQL8新特性

从Mysql8.0开始,数据库的缺省编码将改为utf8mb4

Mysql8.0进行了加密处理,其中url进行加密变的更为安全

Mysql8.0加入了账号安全,限制重复使用以前的密码

  1. 如何提高数据库查询速率

查询目标,避免使用*,应使用字段名

用or的子句可以分解成多个查询(索引会失效),并通过union连接多个查询

用exites代替in去多表查询

在where及order by涉及的列上建立索引

  1. 将姓名为李四的年龄改为15

Update table set age=15 where name=’李四’

  1. 求张三成绩的平均值

Select avg(grade) from table where name=张三

  1. 数据库的常见函数

count()max()---min()---avg()---length()---substring()---mod()---now()

  1. 如何判断SQL中的字符串为空?除了is null还有别的吗?

Select * from table where column is null

Select * from table where trim(column)!=null;

Select * from table where ifnull(“不为null返回的值,为null返回的值)

  1. 说一个你写过最难的sql语句

多表查询,当查超过3个表的时候,里面的条件较为复杂,不易编写

  1. MySQL基本操作之-DDL,DML,DQL,DCL

DDL:数据定义语言

作用:数据定义语言主要用来定义数据库中的各类对象,包括用户,库,表,视图,索引,触发器,事件,存储过程和函数等

DML:数据操纵语言

作用:用来操作数据库中的表对象,包括:insert,update,delete

DQL:数据查询语言

作用:用来查看表中的数据,也是平时使用最多的操作,主要命令为:select

DCL:数据控制语言

作用:用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果(GRANT授予用户权限,REVOKE收回用户权限)

  1. Redo log日志和binlog日志的区别

Redo log属于innoDB层面,binlog属于MySQL Server层面

Redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录这个更新语句的原始逻辑

Redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖

Binlog可以作为恢复数据使用,主从复制搭建;redo log作为异常宕机后的数据恢复用

    

  1. MVCC(多版本并发控制)

当前读和快照读:

当前读:读取的是记录的最新版本,读取时要保证其他并发事务不能修改当前记录

快照读:不加锁的操作,其次隔离级别不能是串行化(串行化的快照读退化成当前读)

       读取某一时刻的版本,因此不能保证读取的版本是最新的。

   并发场景的三种情况:

        读-读:不存在任何问题,不需要并发控制

        读-写:有线程安全问题,可能导致脏读,幻读,不可重复读的问题

        写-写:线程安全问题,可能导致数据更新丢失的问题

   MVCC的作用:解决读-写冲突的无锁并发控制

   MVCC的实现原理:

     依赖记录中的3个隐式字段,undo日志,Read View来实现的

     3个隐式字段:

       DB_ROW_ID:隐含的自增ID(隐藏主键),若数据库中没有主键,InnoDB会自动产生一个聚簇索引。

       DB_TRX_ID:最近一次修改的事务ID;记录创建这条记录的最后一次修改该记录的事务ID

       DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本

       执行流程:当我们记录被事务1进行修改时,先进行加排他锁,将该行数据拷贝到undo log中,当拷贝结束后,修改数据并修改隐藏字段的事务ID为当前事务1的ID.当又来事务2修改时,先加锁,将数据拷贝到undo log中,作为旧纪录,发现该记录已在undo log中,最新的旧数据作为链表头部,插入到undo log之前。数据修改后,再修改隐藏字段事务ID作为当前事务2的ID,回滚指针指向undo log.

       Read View(读视图)

           是在进行快照读操作时候生成的读视图

           在进行快照时,会记录并维护系统当前活跃事务的ID

           组成:未提交事务id数组(活跃事务)和已创建的最大事务ID组成

           作用:进行快照读时,该记录创建了一个ReadView读视图,用来判断当前事务能够看到哪个版本的数据,也可能是该行记录的undo log里面的某个版本的数据

           流程:先将当前事务id取出来

                 活跃事务id和已创建的最大事务id

                 小于最小id:已经提交过的事务,数据可见

                 活跃事务之间:活跃事务和已提交的事务

                 大于最大事务:未开始的事务,数据不可见

   MVCC的执行流程:

       1.

   

Redis数据库

  1. Redis的常用数据类型有哪些

String:内部采用SDS(简单动态字符串),除了分配使用的空间外,还有空闲空间

当字符串被修改后,程序会为SDS分配所需要的必须空间,还会分配额外的未使用空间。当对SDS进行缩短后,空闲空间不会被回收,而是等下次append操作时,将使用空闲空间,大大减少了内存的分配和消耗。

List:双向链表,压缩列表

增删效率较高。

Zlbytes-zltail-zllen-entry1-entry2--zlend

Zlbytes保存了整个zipList的结构的占用空间大小,不需要遍历就可得到zipList的大小

Zltail:保存了最后一个entry的位置,在尾部可以只用使用pop操作

Zlen:entry的个数

Zlend:列表的结束标识

Hash:哈希表+压缩列表(zipList)

Set:哈希表+数组  节省内存

ZSet:有序的 skipList跳表

有序唯一,利用不断取中,从而更快的查找到元素

  1. Redis有什么好处

缓解主数据的压力

读取速度快

给数据设置生存时间,从而可以节省redis的空间

通过AOF日志记录,可以记录redis创建以来所有修改性指令,并可以重放,恢复数据结构状态

  1. Redis缓存雪崩,击穿,穿透

雪崩:

服务器关闭或大量key在某一时间段失效时,大量请求访问直接请求数据库,导致雪崩

解决方案:进行限流策略,防止数据库被干掉

  给不同的key设置不同的过期时间,时失效时间点尽量均匀

  设置一级,二级缓存,一级为短期,二级为长期

  加锁,某个key只允许让一个线程到数据库进行查询

穿透:

非法请求绕过redis直接作用到主数据库中,当请求量大时,则造成穿透

解决方案:使用bitMap集合(放的是所有可能的key),若访问的与bitMap中的一样则设置为1,否则设置为0,为0的让其返回,为1的进行查找

使用booleanFilter进行过滤,将请求不存在的过滤掉

击穿:大量的请求去访问同一个失效的key时,从而造成击穿

解决方案:在失效前设置一个短期的key,等访问结束后进行删除

定义一个定时任务,当快到失效时间时,刷新数据(从数据库里查询,返回给缓存中)

读写一致性:当我们要更新信息时,要将缓存和数据库全部更新,我们需要先将更新数的数据写入到数据据库的中,删除对应缓存中数据。在下一次读的时候,先去数据库中查找,并将结果返回到缓存中即可

  1. RDB和AOF

RDB(数据快照):由于redis在内存是不断改变的,我们记录某个时刻,将数据放到磁盘中;

   若只用RDB进行Redis的持久化操作会导致我们数据不完整

   AOF(日志文件):记录每次redis启动后的所有命令,每次可以通过重放命令维持Redis持久化操作

  1. 若在AOF时断电怎么办?

在AOF中有sync属性配置,若不考虑性能方面,可以每次sync(异步操作)到磁盘中,当然在考虑性能后,进行定时的sync,例:1s1次

  1. RDB的原理

RDB中有folk,cow(copy on write).我们通过folk创建子线程来进行RDB操作;

Copy on write,当子线程创建后,父子进程共享数据段。父进程继续执行读写服务,写脏的数据会逐渐和子进程分开。

线程

  1. 谈谈对线程的理解

线程是cpu执行和调度的最小单元

线程不是独立的,多个线程支撑了一个进程,一个线程错误可以导致进程的退出

在Java中JVM进程中线程有独自的本地方法栈,虚拟机栈,和程序计数器

也共享了元数据区和堆

线程也算是轻量级的进程,cpu分给的时间片与时间都较少

  1. 常见线程池有哪些

newFixedThreadPool(固定数目线程的线程池)

newCacheThreadPool(可缓存线程的线程池)

newSingleThreadExecutor(单线程的线程池)

newScheduledThreadPool(定时及周期执行的线程池)

  1. 线程的创建方式

继承Thread类,实现run()方法,通过start()方法启动

实现Runnable接口,代表可执行的类,通过new Thread(runable).start()方法启动

实现Callable接口,有返回值,通过FurterTask类进行封装,再通过new Thread(furterTask).start()进行启动

通过线程池的方法创建线程,Executor.newFixedThreadPool()

  1. 线程的生命周期

New(初始状态):代表线程处于创建状态,调用start()方法代表可以有运行状态

Runnable(运行状态):running正在运行状态  ready:就绪状态,等待时间片的到来

Waiting(等待状态):放入等待池,通过notify()/notiflyAll()进行唤醒到Runnable状态

Time_Waiting(超时等待状态):通过sleep(millis)/wait(millis)可以置于超时等待状态,等时间到了后,返回到Runnable状体

Blocked(阻塞状态):没有锁的情况下进入阻塞挂起状态

Terminated(终止状态):线程结束

  1. 线程的锁了解吗

Synchronized锁:同步锁,使线程获取锁后,只允许一个线程进入,其余线程等待

  1. 线程和进程的区别

进程是操作系统资源分配的基本单位,线程是cpu执行调度的最小单元

进程之间是独立的,线程之间是多线程的

进程切换消耗资源较大(由于分配的资源和时间片更多),线程切换消耗较少(轻量级进程)

进程之间互不影响,线程若有问题可导致进程直接退出

  1. 什么是线程池

为了解决线程的频繁创建,线程池中维护了多个线程。没有任务时则进行等待,当有任务时,分配一个空闲线程执行。

Java标准库中提供了ExecutorService接口表示线程池。

FixedThreadPool:线程数固定的线程池。

CachedThreadPool:可缓存的线程池,线程数根据任务动态调整线程池。若线程池中有建成的线程,则直接使用。若没有线程,就创建一个新线程去执行任务。通常执行一些短期的异步任务

SingleThreadExecutor:单线程化的线程池,只用唯一的工作线去执行任务,执行一些生命期长的顺序任务。

ScheduledTreadPool: 周期性,任务调度式的线程池。

线程池的核心参数:

  1. 核心线程数量(corePoolSize):线程池的核心线程数量,核心线程是最小的线程数,且核心线程不会被回收,当线程超过核心线程数,且超过线程存活时间时,进行回收。新建线程池后,没有线程数,用execute()方法添加一个任务时,若线程数小于核心线程数,则马上创建一个新的线程并执行这个任务。
  2. 最大线程数量(maximumPoolSize):允许创建的最大线程数量
  3. 空闲线程存活时间(keepAliveTime): 当线程超过核心线程数,且超过线程存活时间时,进行回收,直到小于核心线程数
  4. Unit:设置存活时间的单位
  5. 工作队列(workQueue):一个阻塞的队列,用来存储等待的任务
  6. 拒绝策略(handler),当线程用尽或者工作队列满时,新任务提交,进行拒绝策略。

丢弃任务,并抛出异常;直接丢弃;

  1. 线程池的执行流程:
  1. 提交一个任务,线程池里存活的线程数小于核心线程数时,则创建一个核心线程去处理提交的的任务
  2. 线程里存活的线程数等于核心线程数时,工作队列也满了,判断线程数要是小于最大线程数时,创建一个非核心线程执行提交的任务
  3. 当线程数达到最大线程数量时,还有新的任务提交,则采用拒绝策略

  1. 线程池的任务存储结构

阻塞工作队列BlockingQueue:内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。

常见的BlockingQueue:

 LinkedBlockingQueue: FixedThreadPool和SingleThreadExecutor线程池的使用,大小不固定的BlockQueue,其构造时指定大小。

SynchronizedQueue:CacheThreadPool线程池使用,对其的操作必须是放(生产者)和取(消费者)交替完成

DelayedWorkQueue: ScheduledThreadPool线程池使用,优先级队列,根据任务延时时间的不同进行排序,将延时时间越短的排在前面,先被执行。

  1. 谈谈对ThreadLocal的理解

ThreadLocal是用来数据隔离的,当前线程的变量与其他线程进行隔离,使其他线程无法访问。在spring中的TranscationalSynchorionzedManager类中采用ThreadLocal进行数据隔离,保证每个线程只需要访问同一个数据库,在spring中体现在事务的传播级别,通过传播级别去管理事务的切换。Spring的事务通过ThreadLocal和AOP进行实现。

ThreadLocal的原理:每个线程都有自己的一个ThreadLocals变量,而ThreadLocalMap是在ThreadLocals中的,而我们的ThreadLocal放在ThreadLocalMap中(ThreadLocalMap没有实现Map接口,实现了弱引用,底层是一个数组),因此每个线程的ThreadLocal是隔离的,不允许其他线程获取。

打破数据隔离,使其共同访问的方法:

我们使用InheritableThreadLocal,父线程进行实例化,子线程可以得到父线程中的变量。

传值过程:若inheritThreadLocal不为null,且父线程InheritableThreadLocal存在,则将父线程的变量给子线程即可。

ThreadLocal的不足是内存泄漏,由于是实现了weakRerefence接口,所以在垃圾回收时

本来是弱引用的都需要被回收,但垃圾回收的优先级很低,导致不会立即发现。因此若创建ThreadLocal的线程一直存在,且为弱引用,从而导致短期的对象不会被回收,造成内存泄漏。

解决方案:在得到值以后,通过remove()方法进行移除。

  1. SPI(Service proniver interface):

B/S架构

  1. 谈谈你对B/S架构的了解

B/S架构:Brower浏览器到Server服务器端

 客户只需通过安装一个浏览器,让浏览器通过Web Server同数据库进行交互

 B/S架构有三层(数据层,服务层,表现层)

 流程:浏览器请求,后端处理,并与数据库交互,最后返回给浏览器。

  1. Get和Post的区别,哪个效率高?为什么?

Get:根据地址栏路径去提交,参数可见,安全性低

Post:根据表单提交,参数不可见,安全性高

效率:Post速度小于Get,由于Post可传输的东西可以更多,从而传输的速率较慢

Post请求包含更多的请求头,同时在接受数据前会将请求头发给服务器进行确认,然后再真正的发送数据。

  1. 写过过滤器吗,是怎么写的

会;通过实现Filter接口,并通过注解(@WebFilter(指定需过滤的页面)),通过实现doFilter()方法,从而在里面实现需要的功能,结束后调用doFilter.chain(request,response)方法去调用其他过滤器。

  1. 说一下对servlet的认识

Servlet在B/S架构中,充当了控制器,服务器,通过Servlet上可为页面渲染,下可与数据库进行交互,但是serlvet压力大,职责不够明确,也不单一,从而耦合性高。Serlvet也可作为页面但太过复杂,从而生成了jsp处理视图

  1. Servlet的生命周期

创建(create)>初始化(Initializer)>服务(Service)>销毁(destory)

MyBatis

  1. 谈谈对MyBatis的理解

MyBatis是一个半自动化(需要自己写sql语句),持久性框架;MyBatis封装了原有jdbc的操作,减少了jdbc的大量冗余代码。MyBatis基于sql映射,将字段与属性映射起来,封装了数据库操作后的结果集。带有一二级缓存,提高数据查询速度。

  1. MyBatis的优点和缺点

优点:封装了原有jdbc的操作,减少了jdbc的大量冗余代码。采用二级缓存机制,提高了查找数据的速度,sql可灵活使用。

缺点:由于是半自动框架,sql语句还需自己手写,从而开发效率降低,不如全自动框架Hirbernate,类对象和数据库直接映射,不用手写sql语句

  1. MyBatis中$与#的区别

#是OGNL表达式,在SQL语句中充当?占位符,经过数据库词法分析后进行保存,下次相似语句可以直接解析,同时防止sql注入,兼顾了安全和性能

$是进行连接操作的,在sql语句中进行连接,但容易引发sql注入攻击

  1. MyBatis是如何传递参数的

基于xml:mapper中方法的参数类型与xml的parameterType一致,从而进行mapper方法的参数传递,要是不同参数类型,可以先将其封装到一个类中,parameterType设置该类完全限定名。

基于JavaConfig:基于注解的sql语句传入参数与方法类型和名字相同,若为多个参数,则通过@param(别名),进行别名匹配。若为集合,通过起别名的形式,在sqlprovider中传入map<String,Object> 通过别名拿到值(集合),进行操作。也可以直接通过传入集合,通过默认的(List,Collection,arg0)进行操作List[+i+]。

  1. MyBatis的二级缓存

MyBatis中的二级缓存在mybatis的主配置文件中开启cacheabled=true总开关。作用范围sqlsessionFactory中,不同的sqlsessionFactory缓存是不同的。在mapper中作用到接口上 @CacheNamespace(缓存清除策略,刷新时间,软引用,弱引用),不同的接口,缓存不影响。性能方法:缓存是用一个map进行存储.key:对应sql的方法,参数   value:返回结果集,提高数据库查询效率。

Spring

  1. 谈谈对Spring的理解

Spring是一个轻量级框架,简化了javaEE第三方中间件。主要用来进行解耦,通过三层思想(数据访问层,服务层,控制层)可以单一职责和用来解耦。

  1. 谈谈对IOC的理解

Invertion of controller:控制反转,将所需的对象从容器中拿去并注入到依赖对象中,常见的注入方式有,set注入,constractor注入,接口注入(实现aware),工厂注入(factory-method)。同时也具备解耦的特性

  1. 谈谈对AOP的理解

Aspect operation programming:面向切面,将原有纵向实现的方法,对象,进行水平切割进行封装,主要作用给目标方法进行增加职能。底层通过代理模式进行实现

基于jdk的动态代理,实现InvcationHandler接口,通过在运行时,创建代理类对象,通过其构造方法传入目标对象,根据newProxyInstance(classloader,共同接口,实现类对象)创建代理对象,调用目标方法时,将方法回调到invoke方法,进行增强返回到结果

基于cglib的动态代理:实现MethodInterceptor接口,通过enhancer创建代理对象,设置与目标对象的父类对象,传入实现类对象。通过intercept()方法对目标方法进行增强

  1. Spring常见的注解

@Reporsitory  @Service @Controller @Bean @Compoent @Autorwire @Quaility @CompoentScan @Configuration @EnableWebmvc @EnableSchulder @EnableAspectJAutoProxy

  1. Bean的生命周期

实例化对象—>属性赋值à初始化à销毁

  1. Spring中的声明式事务和编程式事务

编程式事务:PlatformTransactionManager;TransactionTemplate

TransactionTemplate设置setisolationLevel的事务隔离,该类继承了接口DefaultTransactionDefinition,简化事务管理,实现了execute方法,传入TransactionCallback类对象和其中的doInTransaction方法来定义需要事务管理的操作代码

声明式事务:@Transactional(isolation=Default):默认是可重复读。

            @Transactional(propagation=PropagationREQURED) 默认的传播级别

区别:编程式事务每次实现都要单独实现,业务量大功能复杂时,编程式消耗时间;而声明式事务不同,属于无侵入式,不影响逻辑实现。

  1. 传播级别:

Propagation_require:支持当前事务,若没有事务,则新建一个

Propagation_supports:支持当前事务,若没有事务,则已非事务性执行

Propagation_mandatory:支持当前事务,若没有事务,则抛异常

Propagation_requires_new:始终新建一个事务,若原有事务,则将原事务挂起

Propagation_not_supports:不支持当前事务:始终以非事务性方式执行。如当前事务存在,挂起该事务

Propagation_never:不支持当前事务;如果当前事务存在,则引发异常

Propagation_nested:当前事务存在,则在嵌套事务中执行。若没有存在,执行required操作

SpringMVC

  1. 谈谈你对SpringMVC的理解

  1. SpringMVC的执行流程

SpringBoot

  1. springboot的主配置类上面的注解是啥

SpringbootConfiguration: EableAutoConfiguration;SpringBootConfiguration;ComponetScan;

EableAutoConfiguration:根据spring-boot-autoconfigure的spring.factories声明的自动配置类列表

SpringBootConfiguration:相当于applicationContext.xml,提供了spring容器

ComponentScan:组件扫描的,扫描bean.

  1. springboot的启动流程

springboot将spring,springmvc等框架的进一步封装,框架中的框架(make java simple),对spring框架进行了自动配置(EableAutoConfiguration),根据spring-boot-autoconfigure的spring.factories声明的自动配置类。

SpringApplication.run()方法中:先是创建springApplication对象,通过构造方法去创建完成初始化,并读取核心配置类。

  1. 先资源加载设置为null
  2. 通过断言去确定加载启动类是否为空
  3. 将要加载的启动类通过LinkedHashSet去重
  4. 推断出当前Web应用类型(reactiv,servlet,null)
  5. 读取自动配置spring.factories(接口和实现类的映射)列表
  6. 设置监听器
  7. 推断主入口应用类

Run方法中,启动流程的监听,计时,创建spring容器,打印启动Banner,执行runner

  1. 创建计时器对象(StopWatch)
  2. 启动监听器,执行(加载主容器,刷新主容器)
  3. 根据web应用类型创建spring容器
  4. 刷新容器
  5. 记录启动的时间
  6. 当所有配置ok后,进行runner运行。

消息队列(message queue)

核心:异步,解耦,削峰

当并发量高的时候,导致我们用户响应度低,且可能造成主从数据库的瘫痪。

削峰:因此我们可以使用消息队列,将大量请求放在消息队列中,服务器根据自己的处理情况进行处理,达到削峰的效果,减缓了数据库的压力;

异步:好比当有一个下单操作时,要进行优惠红包,增加积分和发送短信的业务时,若为同步时,导致用户体验感贼差;因此将我们可将三个业务进行异步处理(并发处理),而我们的下单操作订阅发送的消息即可,从而得到响应度的提高。

解耦:原有操作可能要来回调接口,而现在将消息放到消息队列中,我们的下单操作只需要获取即可,相当于进行了解耦操作。

缺点:增加了系统的复杂度,原先只需要调用接口即可,而现在又一个消息中间件,为防止宕机,我们要维护消息队列,还需要使用时考虑(重复消费,消息丢失,顺序性消费),过于麻烦;

若其中某个业务(增加积分失败)怎么办

我们可以将这些业务放到一个事务中,要么全部成功,要么全部回滚

如何选取消息中间件

ActiveMQ:吞吐量低,万级的吞吐量,可能造成消息丢失,可用性较高

RabbitMQ: 吞吐量低,万级的吞吐量,基本不造成消息丢失,可用性较高

RocketMQ: 吞吐量高,十万级的吞吐量,消息丢失为0,可用性非常高,分布式架构

KafkaMQ: 吞吐量高,十万级的吞吐量,消息丢失为0,分布式架构,非常高(数据计算,日志采集),少数机器宕机,但不会使消息丢失

锁的分类与机制

锁是一种资源私有化,是最常用的并发控制机构,防止其他事务或线程访问指定资源控制,是实现并发控制的一种手段。

分类:

宏观:

公平锁:指多个线程获取锁时以先进先出的策略取获取锁。

非公平锁:多个线程根据分的时间片抢占锁,抢到后获取锁(Synchronized,ReentrantLock)

  重量型锁:绝对的加锁安全性,多用来处理多线程并发,依赖操作系统的互斥锁,所实现的锁(Synchronized);切换时消耗资源,该线程持有锁时,其他线程处于阻塞状态。当以对象作为锁时,利用Synchronized关键字,多个线程通过monitorenter指令,尝试获取对象头中的监视器锁,若进入数为0,则可以持有该锁,若不为0(可重入锁不算),则其余线程等待,而监视器锁依赖于操作系统的互斥锁,当进行线程切换时,从用户态转为内核态再到用户态,资源消耗极大。

       优点:安全程度高;线程矛盾,无需自旋,不需消耗cpu

       缺点:线程切换时,消耗大量资源;其他线程阻塞,响应度低;

  轻量型锁:多个线程同步进行(ReentrantLock),当有线程争夺时则升级为重量级锁,当线程要申请该对象锁时,jvm虚拟机栈为当前线程创建一个锁记录(lock record),将对象的mark word拷贝一份放到线程的所记录中,然后对象的mark word的指针指向线程中的锁记录,同时线程中的锁记录指向该对象(互相指向),从而说明线程持有这把锁,并mark word的锁标记为“00”。当mark word指向出错时,则证明为多个线程抢占,从而升级为重量级锁(进行锁自旋,循环获取锁的,若不成功则转为重量级锁;将锁标记改为10证明为mark word指向重量锁的指针,其余线程进行阻塞挂起)。

        解锁:通过CAS指令,将锁记录的东西拷贝一份给mark word,若替换成功,同步完成。若不成功,则说明已经升级为重量级锁,线程持有锁结束后,其他线程重新参与锁的抢夺。

        优点:竞争之间,没有阻塞,提升了响应速度。

        缺点:由于竞争不到锁的线程,从而自旋,消耗cpu资源;

  偏向锁:在单线程的情况下,尽量避免轻量级锁的路径。由于轻量级锁在加锁和解锁时需要进行大量的CAS原子性操作。而在偏向锁中通过ThreadID进行切换线程(查看对象头中是否有当前线程的ThreadID,若有则为偏向锁,若没有(多个线程)则进行一次CAS操作进行自旋,失败则锁撤销,然后暂停原有偏向锁的线程并检查,若退出同步代码块,则唤醒原持有的偏向锁的线程;若不退出,则升级为轻量级锁)。

      优点:加锁和解锁不需要额外消耗

      缺点:线程之间存在锁竞争,带来额外的锁撤销

   锁优化:

  1. 适应性自旋:若自旋成功,则下次自旋的次数增多;若自旋失败,则下次自旋的次数减少
  2. 锁粗化:将多次连接在一起的加锁,解锁合并到一起,将多个连续的锁扩大为一个范围更大的锁。

    ReentrantLock:非公平锁;可重入锁,当持有对象的monitor锁后,可以反复进入,monitor也一直再加。

                非公平锁->公平锁:按照指定时间获取锁;在获取锁时,设置时间,若超过时间仍在等待,则trylock()返回false,则执行别的处理,而不是一直等待、

    Condition:提供通讯操作;提高await(),signal(),signalAll()等原理与wait(),notify(),notifyAll()一样。

             await():指在指定一段时间中,如是signal()和signalAll()唤醒,否则是自己醒来。

    ReadWriteLock:读写锁;允许多个线程同时读取,提高性能,但当写的时候,则其他线程全部等待,直到写操作的锁释放。

AQS原理:

AbstractQuenedSynchronized抽象的队列式同步器。除java自带的synchronized关键字之外的锁机制,这个类在java.util.concurrent.locks包

核心思想:若请求共享资源为空,则当前请求资源的线程设置为有效的工作线程,并将共享资源为锁定状态;若共享资源被占用,需要一套线程阻塞等待以及被唤醒时所分配的机制,AQS是用CLH队列进行实现,将暂时获取不到锁的线程加入到队列中。

悲观锁与乐观锁:

悲观锁:每次在拿去数据时,总认为别人会修改数据,所以在每次拿去数据的时候先加锁,拿取数据就会阻塞直到它拿到锁

分类:表级锁,行级锁,共享锁,排他锁,synchronized锁

乐观锁:每次拿去数据的时候都认为别人不会修改,不进行上锁,但在更新后,提交任务前会判断在读取到提交数据此期间有没有更新过这个数据,若不相等,则进行重新检查,直到ok为止。

数据库的版本号:

每个数据库都有一个编号,在读取到提交数据前进行查询,容其他数据进行修改时,编号version的值加一,判断最后的version值于当前数据库中的version值是否相等,若相等则可以修改,若不相等则代表需要重新回滚。

CAS(Compare and Swap)比较并替换:3个基本操作数<内存地址,旧的期望,修改后的新值>

更新一个变量时,只有当变量的内存地址(v)与旧的期望值(A)相等时才会将内存地址修改为B。

若不相等,则令A更新为V,再次进行查找,直到相等为止。

缺点:CPU开销过大,若多个线程反复尝试更新某个变量,且又一直不断更新不成功。

      不能保证代码块的原子性:当又三个线程同时进行原子性的更新,使用Synchronized。

      ABA问题:来源于代码块的原子性。若线程1和3获得当前值,线程2还没获取当前值,线程1执行成果,将A更新为B;同时线程2某种原有被阻塞上1了。

      线程3在线程1更新之后,获取了当前值B。

      线程3执行,成功把当前值从B更新到A,通过compare检测,成功将变量值更新为A。在线程1,3执行后,线程2的恢复状态,发现没有任何变化(基本类型没变化,引用类型有变化) ,由A->B,再由B->A(ABA),但若为引用类型则发生变化,A已经为其他线程修改的。

解决办法:加版本号

第三方技术:

  1. Quartz的方法参数,类型,有无特定需求
  2. Json

计算机网络

  1. TCP与UDP的区别

TCP:面向连接  UDP:面向报文的

TCP:流量控制,拥塞避免   UDP:没有

TCP:一对一  UDP:一对一;一对多;多对多

TCP:首部20字节,开销大  UDP:首部8字节,开销小

  1. TCP的三次握手四次挥手
  2. 长连接和短链接

长连接:进行一次数据传输后,不关闭连接,长期保持联通状态。若两个应用程序之间有新的数据需要传输,则直接复用这个连接,无需再创建一个新的连接

   优点:多次连接省去连接的建立和是释放。

         传输的总耗时更少

   缺点:由于要维护这样的连接,从而消耗大量的资源

   确定是否保活:1.利用TCP自身的保活机制实现,发送探测报文来识别对方是否可达。

                2.应用主动的定时发送一个小数据包,探测是否成功发送到另一方

短连接:每次数据传输都需要建立一个新的连接,用完马上关闭,下次用的时候重新建立一个新的连接。

管线化链接:基于长链接基础上,并发进行http报文的请求与确认多批处理

  优点:每次创建连接,数据大概率都可以到达对方,不影响后续连接

  缺点:每个连接都要经过三次握手和四次挥手,增加耗时。

设计模式

  1. 你了解哪些设计模式?详细介绍一下观察者模式,说说使用场景
  2. 单例模式
  3. 动态代理(基于JDK与基于cglib的区别)
  4. 观察者模式
  5. 简单工厂,工厂,抽象工厂模式

简单工厂:我们用工厂进行创建对象,而简单工厂就是根据我们传入的参数不同,从而创建出不同的对象,冗余度较高。工厂中的一个方法中利用条件语句创建出不同的对象

工厂模式:指我们创建的对象,每个对象对应一个方法,而对象的创建通过实现工厂类的实现类进行创建。冗余度较低

抽象工厂:

    例:小米(手机,路由器)   华为(手机,路由器)

产品族:是指同一个等级下的不同产品(小米手机,小米路由器)(华为手机,华为路由器)

产品等级:指同一纵族下的产品(小米手机,华为手机)(小米路由器,华为路由器)

实现思路:创建一个产品工厂对应手机和路由器两个产品方法。而具体是哪个品牌,再通过创建华为工厂去实现产品工厂,同理创建小米工厂实现产品工厂。而各个品牌工厂中又可以通过对应的方法调用具体产品创建的类(而具体的产品又是一个新的类去创建),最后在进行调用即可。

算法分析

  1. 冒泡排序
  2. 二分查找
  3. 递归算法

数据结构

  1. 了解队列和链表吗,用java怎么实现
  2. 二叉树,红黑树,B树,B+树,B-树

左旋:旋转某个结点,则将该结点的右孩子代替原有结点,让原有结点做右结点的左孩子

        并左(ll)/右(lr)连接上右孩子的原有左结点

右旋:旋转某个结点,则将该结点的左孩子代替原有结点,让原有结点做左结点的右孩子

        并左(rl)/右(rr)连接上左孩子的原有右结点

二叉查找树(BST):每个结点存储一个关键字,等于命中;小于走左节点,大于走右结点

               当数据量过多时,会向链表靠拢。

平衡二叉树(ALV):在二叉查找树(BST)的基础上,使左子树与右子树的高度差的绝对值小于1,用来保证平衡。但由于每次存储一个关键字,导致高瘦的特点,查询速率下降。

B-树(B树):每个结点存储多个关键字,多路搜素树,每个结点存储M/2到M个关键字(开区间),非叶子节点存储执行关键子范围的子节点。

  所有关键字在整棵树中出现,且只出现一次,非叶子节点可以命中。

  查询到叶子结点,若没有,则进行回树查询。

B+树:在B-树基础上,为叶子节点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子节点的索引;叶子结点保存了所有信息。

  B+树总是在叶子结点才命中。

  查询上,无需回表。

B*树:在B+树基础上,为非叶子结点增加指向兄弟的链表指针,将节点的最低利用效率从1/2提高到2/3。

红黑树:R-B Tree,复杂度0(logN)平衡二叉树。结点上都有表示该节点的颜色,可以是红色/黑色

        特点:每个结点或是黑色,或是红色

              根节点为黑色,叶子节点为黑色

              结点与其子节点之间不能连续出现红色

              从一个结点到该结点的子孙结点的所有路径包含相同数目的黑结点。

              最长路径不能超过最短路径的2倍

        插入和删除通过换色和旋转,满足红黑树的条件,进而使红黑树平衡。

  1. B+树,B*树的分裂

B+树:当一个结点满时,分配一个新节点,并将原来1/2的数据复制给新结点,最后父节点中增加新节点的指针;B+树的分裂只影响原结点和父节点,而不影响新节点,不需指向兄弟结点。

B*树:当一个结点满时,它的下一个兄弟结点没有满,则将剩余部分数据移到兄弟结点中,在原结点插入关键字,最后父结点指向兄弟结点并修改关键字。(由于数据的转移,兄弟结点的关键字范围改变了);如果兄弟结点也满了,则创建新节点,并各自复制1/3的数据到新节点,最后增加父结点到新节点的指针。

       

总结:B*树分配新节点的概率比B+树低,空间利用率更高。

类加载,双亲委派,对象的创建

类加载:类在运行期进行在第一次使用时动态加载,若一次性全部类会带来内存上的占用。

流程:加载,验证,准备,解析,初始化(加验准备了析始菜);

加载:通过类的完全限定名(反射)得到二进制字节流

验证:  验证jvm是否可执行二进制字节流,不危害jvm的安全

准备:创建成员变量,静态数据,为其在元数据区开辟空间。

解析:将原有的符号引用变为直接引用

初始化:之前在准备的过程中已经初始化值,该初始化是执行类构造器。

主动引用:采用new,getStatic,setStatic,invokeStatic以及反射创建对象可直接触发类加载机制

被动引用: 常量放在常量池中,因此不引发类加载

           通过数组定义引用类,不会触发类加载,但数组会发生类加载

           子类引用父类的静态字段,不引发类加载

双亲委派:

 类加载器在加载类时,先去父类查询是否加载过,若已加载,则当前类不再加载,反之没有加载,当前类加载器再进行加载,保证类只加载一次即可。

BootstampClassLoader:启动类加载器,最顶级的类加载器,属于虚拟机的一部分,继承java.lang.ClassLoader

ExtentsionClassLoader:扩展类加载器,加载javax包下的类

ApplicationClassLoader:应用程序类加载器,加载我们自己的类,和jar包。若不指定自定义的类加载器,则称为默认的类加载器。

双亲委派的优点:  保证类加载只加载一次,父类加载,则当前类加载器不进行加载。

                可保证我们的核心类的内容不被篡改。当我们通过当前类去修改类时,则父类加载器已加载,我们当前要修改的类无法进行加载。

Class.forName()和classloader的区别:

    Classloader遵从双亲委派模型最终调用启动类加载器。通过加载类的完全限定名的的二进制字节流,获取后放到jvm中,此时是不进行初始化的

    Class.forName()方法实际上也是调用的ClassLoader来实现的,但加载类默认会进行初始化,也可以通过重载版本进行手动指定是否进行初始化。

如何打破双亲委派:

  1. 在双亲委派之前,我们通过自定义的类加载器,继承java.lang.ClassLoaderloadclass()方法,在引入双亲委派之后,java.lang.ClassLoader也是双亲委派的一种实现,因而我们相当于重写了loadclass()方法,进而打破双亲委派机制。

  1. 当父类加载器通过委派其他类加载器完成加载请求,可以打破双亲委派模型

使用上下文类加载器。

上下文类加载器:是定义在当前线程Thread中的一个ClassLoader类型的私有成员变量,指向了当前线程的类加载器。

SPI(Service proniver interface):java允许服务提供商可编写代码的接口。SPI是在java的核心库中,而厂商的具体实现在classpath中,我们无法加载。只能通过 应用类加载器加载classpath中的内容。

大体方向为:

通过ServiceLoader类获得上下文类加载器,后通过其load()方法对classpath进行加载即可。

对象的创建:

  1. 先判断类加载,若未加载,则先进行类加载
  2. 分配空间:

           指针碰撞:适用于preNew,parallelScavenge,serior垃圾回收器的标记复制和整方法,先让指针从中间位置向未分配对象的地方移动,进行分配对象

           空闲链表:适用于CMS垃圾回收器的标记复制和标记清除算法,链表中记录了可用的空间的位置,通过位置进行空间的分配。

  1. 初始化0值:进行变量赋0值
  2. 设置对象头:设置对象头的监视器锁,age,偏向锁的序号,hash码.
  3. Init()方法完成最后的创建:通过用户的需求去完成new init()初始化

BIO,NIO,AIO

  1. BIO(同步阻塞式io):默认情况下,每次请求,服务器端都要对其建立线程等待请求,每次请求都是等该线程响应后,才能进行下一个线程的使用,需要多个线程
  2. NIO(同步非阻塞式io):线程数为1个或尽可能的少,连接创建后,将该连接注册到多路复用器上,每当通过复用器轮询时,若有请求才开启一个线程进行处理;可能后面资源在等待,也会有阻塞的情况。
  3. AIO(异步非阻塞式io):每次进行读写操作时,read和write均为异步,代表两者可并发执行。

在进行read,write操作后立即返回,等到io操作完成后,应用程序会得到io操作完成的通知,然后进行操作处理。

阻塞:进程给CPU传达一个任务,一直等待CPU处理完成,再处理下一个

非阻塞:进程给CPU传达任务后,继续处理后续的操作,隔段时间再来询问之前的操作是否完成,也称为轮询。

DMA进行优化CPU的频繁操作,每次cpu去操作进程或线程时,只需要告诉cpu有请求到达cpu,cpu进行确认即可,同时也通过将io过程中进行DMAC的一个封装,提高io效率。

深拷贝和浅拷贝

作用:复制对象

基本数据类型:深拷贝和浅拷贝都是一样的

引用数据类型:

浅拷贝:每次只复制指针值,没有复制指向的对象

深拷贝:不仅复制指针值,而且复制指向的对象,且这两个对象不同,互相不产生影响。

Object.clone()方法属于浅拷贝:若进行深拷贝要重写clone()方法

当调用类的clone()方法时,要实现Cloneable接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值