Java面试点总结


基础

JDK1.8的新特性(阿里)

①lambda表达式允许将函数作为一个参数传入,主要是用于简化匿名内部类的代码,lambda表达式通过函数接口来实现,函数式接口就是只有一个方法的普通接口,通过@FunctionalInterface注解。②方法引用,可以直接引用Java类或对象,可以进一步简化lambda表达式。可以引用构造方法、类静态方法、特定类的任意对象方法、某个对象的方法。③接口中可以使用default定义默认方法,也可以定义静态方法。引入默认方法是为了实现接口升级,在原有设计中如果给接口中添加一个新的方法,接口的所有实现类都需要修改,维护起来十分麻烦。④引入重复注解机制,相同的注解在同一个地方可以声明多次。扩展了注解的使用范围,可以给局部变量、泛型、方法异常等提供注解。⑤可以根据目标类型来推测泛型的参数,使代码更加简洁,例如new一个ArrayList时只用在定义变量时指定泛型。⑥增加了Optional类,用来处理空指针的情况,增强代码的可读性。⑦增加了Stream类,支持流式编程,提供了一些集合和数组类的操作。有foreach遍历、filter过滤、map映射、concat合并等功能。⑧对日期相关的API进行了改进,包含了处理日期、实践、时区、时刻和时钟等操作。

HashMap(招行、阿里)

HashMap是线程安全的吗?
①HashMap是线程不安全的,可以使用ConcurrentHashMap保证线程安全。②ConcurrentHashMap基于减小锁粒度的思想,通过使用分段锁来实现线程安全,内部细分为很多Segment数据段,默认情况下为16个,对每个Segment的数据都单独进行加锁操作,Segment的个数为锁的并发度。ConcurrentHashMap是由Segment数组和HashEntry数组组成的,Segment继承了可重入锁,HashEntry用来存储键值对数据。③Segment的结构和HashMap类似,是数组和链表结构,每个Segment里面都包含一个HashEntry数组,每个HashEntry都是一个链表结构的数据要对其进行i修改必须先获得对应的Segment锁。④多线程下只要加入的数据hashCode映射的数据段不一样就可以做到并行的线程安全。

HashMap的底层是怎么实现的?
①JDK1.8之前,HashMap的底层是数组加链表实现。数组中的每个元素都是一个单链表,链表中的每个元素都是Entry结点,Entry包括4个属性:key、value、hash值和用于指向单链表下一个元素的next。HashMap在查找数据时,根据hash值可以快速定位到数组的具体下标,然后对链表进行遍历查找数据的时间复杂度为O(n)。②JDK1.8起对HashMap进行了优化,每个元素是一个Node结点,底层改为数组+链表或红黑树,当链表中的元素超过8个之后,HashMap会将链表结构转换为红黑树以提高查询效率,红黑树一颗自平衡的二叉树,时间复杂度为O(logn)。

HashMap存放元素
①JDK1.7及之前:通过hash方法计算key的hash值,然后将其与数组长度-1进行与运算计算出元素的下标,然后根据key和hash值判断是新增一个链表结点还是代替已有结点的value值。如果增加元素后达到阈值则进行扩容,确定新的扩容阈值,填充因子默认为0.75。②JDK1.8起:计算key的hash值,方法是将key的hashCode值的高低16位进行异或,目的是为了让尽可能多的位数参与运算降低哈希冲突的概率并让01尽可能均匀。然后将计算出的hash值与数组的长度-1进行与运算,可以保证索引的范围小于数组的范围,并且由于数组长度必须是2的幂次方,-1后必然是一个奇数,这样和hash值与运算的结果就可以有奇数和偶数两种可能。③存放元素时,当下标位置没有结点的时候,直接增加一个链表结点;当下标位置为树结点的时候,增加一个树结点;当前面的情况都不满足时,说明当前下标位置有结点,且为链表结点,此时遍历链表,根据hash和key值判断是否重复,以决定是代替某个结点还是新增结点;添加链表结点后,如果链表深度达到或超过建树阈值(TREEIFY_THRESHOLD-1),那么调用treeifyBin方法将链表重构为树。注意TREEIFY_THRESHOLD是一个常量,固定为8,因此当链表长度达到7的时候,就会转为树结构。该树是一棵红黑树,由于链表的查找是O(n),红黑树的查找是O(logn)的,数值太小的时候查找效率相差无几,JDK1.8认为7是一个合适的阈值,因此这个值被用来决定是否从链表转为树结构。


并发

线程创建的方式(阿里)

①继承Thread类,重写run()方法即可。优点是编码简单,缺点是不能继承其他类,功能单一。②实现Runnable接口,重写run()方法,并将该实现类作为参数传入Thread构造器。优点是可以继承其他类,避免了单继承的局限性;适合多个相同程序代码的线程共享一个资源(同一个线程任务对象可被包装成多个线程对象),实现解耦操作,代码和线程独立。缺点是实现相对复杂。③实现Callable接口,重写call()方法,并包装成FutureTask对象,再作为参数传入Thread构造器。优点是相比方式二可以获取返回值,缺点是实现复杂。④可以通过线程池创建。


synchronized、volatile和显式锁(阿里)

volatile关键字的作用
①保证被修饰的变量对所有线程可见,在一个线程修改了变量的值后,新的值对于其他线程是可以立即获取的。②禁止指令重排序,被修饰的变量不会被缓存在寄存器中或者对其他处理器不可见的地方,因此在读取volatile修饰的变量时总是会返回最新写入的值。③不会执行加锁操作,不会导致线程阻塞,主要适用于一个变量被多个线程共享,多个线程均可对这个变量执行赋值或读取的操作。④volatile可以严格保证变量的单次读写操作的原子性,但并不能保证像i++这种操作的原子性,因为i++在本质上是读、写两次操作。

synchronized关键字的作用
①用于为Java对象、方法、代码块提供线程安全的操作,属于排它的悲观锁,也属于可重入锁。②被synchronized修饰的方法和代码块在同一时刻只能有一个线程访问,其他线程只有等待当前线程释放锁资源后才能访问。③Java中的每个对象都有一个monitor监视器对象,加锁就是在竞争monitor,对代码块加锁是通过在前后分别加上monitorenter和monitorexit指令实现的,对方是否加锁是通过一个标记位来判断的。

synchronized的内部都包括哪些区域?
synchronized内部包括6个不同的区域,每个区域的数据都代表锁的不同状态。①ContentionList:锁竞争队列,所有请求锁的线程都被放在竞争队列中。②EntryList:竞争候选列表,在锁竞争队列中有资格成为候选者来竞争锁资源的线程被移动到候选列表中。③WaitSet:等待集合,调用wait方法后阻塞的线程将被放在WaitSet。④OnDeck:竞争候选者,在同一时刻最多只有一个线程在竞争锁资源,该线程的状态被称为OnDeck。⑤Owner:竞争到锁资源的线程状态。⑥!Owner:释放锁后的状态。

synchronized的实现原理
①收到新的锁请求时首先自旋,如果通过自旋也没有获取锁资源,被放入ContentionList(该做法对于已经进入队列的线程是不公平的,体现了synchronized的不公平性)。②为了防止ContentionList尾部的元素被大量线程进行CAS访问影响性能,Owner线程会在是释放锁时将ContentionList的部分线程移动到EntryList并指定某个线程(一般是最先进入的)为OnDeck线程。Owner并没有将锁直接传递给OnDeck线程而是把锁竞争的权利交给他,该行为叫做竞争切换,牺牲了公平性但提高了性能。③获取到锁的OnDeck线程会变为Owner线程,未获取到的仍停留在EntryList中。④Owner线程在被wait阻塞后会进入WaitSet,直到某个时刻被唤醒再次进入EntryList。⑤ContentionList、EntryList、WaitSet中的线程均为阻塞状态。⑥当Owner线程执行完毕后会释放锁资源并变为!Owner状态。

JDK对synchronized做了哪些优化?
JDK1.6中引入了适应自旋、锁消除、锁粗化、轻量级锁以及偏向锁等以提高锁的效率。锁可以从偏向锁升级到轻量级锁,再升级到重量级锁,这种过程叫做锁膨胀。JDK1.6中默认开启了偏向锁和轻量级锁,可以通过-XX:UseBiasedLocking禁用偏向锁。

volatile和synchronized的区别?
①volatile只能修饰实例变量和类变量,而synchronized可以修饰方法以及代码块。②
volatile只能保证数据的可见性,但是不保证原子性,synchronized是一种排它机制,可以保证原子性。只有在特殊情况下才适合取代synchronized:对变量的写操作不依赖于当前值(例如i++),或者是单纯的变量赋值;该变量没有被包含在具有其他变量的不等式中,不同的volatile变量不能互相依赖,只有在状态真正独立于程序内的其它内容时才能使用volatile。③volatile是一种轻量级的同步机制,在访问volatile修饰的变量时并不会执行加锁操作,线程不会阻塞,使用synchronized加锁会阻塞线程。

扫描二维码关注公众号,回复: 10909371 查看本文章

ReentrantLock
①ReentrantLock是Lock接口的实现类,是一个可重入式的独占锁,通过AQS实现。②支持公平锁与非公平锁,还提供了可响应中断锁(线程在等待锁的过程中可以根据需要取消对锁的请求,通过interrupt方法中断)、可轮询锁(通过tryLock获取锁,如果有可用锁返回true否则立即返回false)、定时锁(通过带long时间参数的tryLock方法获取锁,如果在给定时间内获取到可用锁且当前线程未被中断返回true,如果超过指定时间则返回false,如果获取锁时被终断则抛出异常并清除已终止状态)等避免死锁的方法。③通过lock和unlock方法显式地加锁和释放锁。

synchronized和ReentrantLock有哪些区别?
①synchronized是隐式锁,ReentrantLock是显式锁,使用时必须在finally代码块中进行释放锁的操作。②synchronized是非公平锁,ReentrantLock可以实现公平锁。③ReentrantLock可响应中断,可轮回,为处理锁提高了更多灵活性。④synchronized是一个关键字,是JVM级别,ReentrantLock是一个接口,是API级别。⑤synchronized采用悲观并发策略,ReentrantLock采用的是乐观并发策略,会先尝试以CAS方式获取锁。


JVM

内存区域(阿里)

JVM的内存区域分为线程私有区域(程序计数器、虚拟机栈、本地方法区)、线程共享区域(堆、方法区)和直接内存。①程序计数器是一块很小的内存空间,用于存储当前线程执行字节码文件的行号指示器。②虚拟机栈是描述Java方法执行过程的内存模型,帧栈中存储了局部变量表,操作数栈,动态链接,方法出口等信息。③本地方法栈,和虚拟机栈作用类似,区别是虚拟机栈为Java方法服务,本地方法栈为Native方法服务。④JVM运行过程中创建的对象和生成的数据都存储在堆中,堆是被线程共享的内存区域,也是垃圾回收最主要的内存区域。⑤方法区用来存储常量,静态变量、类信息、即时编译器编译后的机器码、运行时常量池等数据。


类加载机制(阿里)

①加载:将Class文件读取到运行时数据区的方法区内,在堆中创建Class对象,并封装类在方法区的数据结构的过程。②验证:主要用于确保Class文件符合当前虚拟机的要求,保障虚拟机自身的安全,只有通过验证的Class文件才能被JVM加载。③准备:主要工作是在方法区中为类变量分配内存空间并设置类中变量的初始值。④解析:将常量池中的符号引用替换为直接引用。⑤初始化:主要通过执行类构造器的<client>方法为类进行初始化,该方法是在编译阶段由编译器自动收集类中静态语句块和变量的赋值操作组成的。JVM规定,只有在父类的<client>方法都执行成功后,子类的方法才可以被执行。在一个类中既没有静态变量赋值操作也没有静态语句块时,编译器不会为该类生成<client>方法。

类加载器,类加载器的加载模型(阿里)

①主要有启动类加载器,负责加载JAVA_HOME/lib中的类库;扩展类加载器,负责加载JAVA_HOME/lib/ext中的类库;应用程序类加载器,也称系统类加载器,负责加载用户类路径上指定的类库;也可以自定义类加载器。②类加载器之间的层次关系叫做双亲委派模型,要求除了顶层的启动类加载器外其余的类加载器都应当有自己的父类加载器。一个类收到类加载请求后会层层找父类加载器去尝试加载,因此所有的加载请求最终都会被传送到顶层的启动类加载器,只有当父类加载器反馈自己无法完成加载时子加载器才会尝试自己去加载。③双亲委派模型的好处是保障类加载的唯一性和安全性,例如加载rt.jar包中的java.lang.Object,无论哪一个类加载最终都会委托给启动类加载器,这样就保证了类加载的唯一性。如果存在包名和类名都相同的两个类,那么该类就无法被加载。④JVM如何判断两个类是否相同:全限定类名和类加载器都相同。


GC(阿里)

新生代是怎么分区的
①JVM新创建的对象(除了大对象外)会被存放在新生代,默认占1/3堆内存空间。由于JVM会频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。②新生代又分为Eden区,ServivorFrom区和ServivorTo区。③Eden区:Java新创建的对象首先会被存放在Eden区,如果新创建的对象属于大对象,则直接将其分配到老年代。大对象的定义和具体的JVM版本、堆大小和垃圾回收策略有关,一般为2KB~128KB,可通过-XX:PretenureSizeThreshold设置其大小。在Eden区的内存空间不足时会触发MinorGC,对新生代进行一次垃圾回收。②ServivorTo区:保留上一次MinorGC时的幸存者。③ServivorFrom区:将上一次MinorGC时的幸存者作为这一次MinorGC的被扫描者。

新生代的垃圾回收机制
新生代的GC过程叫做MinorGC,采用复制算法实现,具体过程如下:
①把在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区,如果某对象的年龄达到老年代的标准,则将其复制到老年代,同时把这些对象的年龄加1。如果ServivorTo区的内存空间不够,则也直接将其复制到老年代。如果对象属于大对象,则也直接复制到老年代。②清空Eden区和ServivorFrom区中的对象。③将ServivorFrom区和ServivorTo区互换,原来的ServivorTo区成为下一次GC时的ServivorFrom区。

老年代的垃圾回收机制
①老年代主要存放有长生命周期的对象和大对象,老年代的GC叫MajorGC。②在老年代,对象比较稳定,MajorGC不会频繁触发。在进行MajorGC前,JVM会进行一次MinorGC,过后仍然出现老年代空间不足或无法找到足够大的连续内存空间分配给新创建的大对象时,会触发MajorGC进行垃圾回收,释放JVM的内存空间。③MajorGC采用标记清除算法,该算法首先会扫描所有对象并标记存活的对象,然后回收未被标记的对象,并释放内存空间。因为要先扫描老年代的所有对象再回收,所以MajorGC的时间较长。容易产生内存碎片,在老年代没有内存空间可分配时,会出现内存溢出异常。

如何确定对象是否是垃圾
①Java采用引用计数法和可达性分析来确定对象是否应该被回收。引用计数法容易产生循环引用的问题,可达性分析通过根搜索算法实现。根搜索算法以一系列GC Roots的点作为起点向下搜索,在一个对象到任何GC Roots都没有引用链相连时,说明其已经死亡。根搜索算法主要针对栈中的引用、方法区的静态引用和JNI中的引用展开分析。②引用计数法:在Java中如果要操作对象,就必须先获取该对象的引用,因此可以通过引用计数法来判断一个对象是否可以被回收。在为对象添加一个引用时,引用计数加1;在为对象删除一个引用时,引用计数减1;如果一个对象的引用计数为0,则表示此刻该对象没有被引用,可以被回收。引用计数法容易产生循环引用问题,循环引用指两个对象相互引用,导致它们的引用一直存在,而不能被回收。③可达性分析:为了解决引用计数法的循环引用问题,Java还采用了可达性分析来判断对象是否可以被回收。具体做法是首先定义一些GC Roots对象,然后以这些GC Roots对象作为起点向下搜索,如果在GC Roots和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象要经过至少两次标记才能判断其是否可被回收,如果两次标记后该对象仍然不可达,则将被垃圾回收器回收。

GC算法
①标记清除算法:标记出所有需要回收的对象,然后清除可回收的对象。效率较低,并且因为在清除后没有重新整理可用的内存空间,如果内存中可被回收的小对象居多,会引起内存碎片化问题。②复制算法:将可用内存分为区域1和区域2,将新生成的对象放在区域1,在区域1满后对区域1进行一次标记,将标记后仍然存活的对象复制到区域2,然后清除区域1。效率较高并且易于实现,解决了内存碎片化的问题,缺点是浪费了大量内存,同时在系统中存在长生命周期对象时会在两区域间来回复制影响系统效率。③标记清除算法:结合了标记清除算法和复制算法的优点,标记过程和标记清除算法一样,标记后将存活的对象移动到一端,清理另一端。④分代收集算法:根据对象不同类型把内存划分为不同区域,把堆划分为新生代和老年代。由于新生代的对象生命周期较短,主要采用复制算法。将新生代划分为一块较大的Eden区和两块较小的Survivor区,Servivor区又分为ServivorTo和ServivorFrom区。JVM在运行过程中主要使用Eden和SurvivorFrom区,进行垃圾回收时将这个两个区域存活的对象复制到SurvivorTo区并清除这两个区域。老年代主要存储长生命周期的大对象,因此采用标记清除或标记整理算法。

垃圾回收器
①Serial:单线程,基于复制算法,JVM运行在Client时默认的新生代垃圾收集器。②ParNew:Serial的多线程实现,基于复制算法,JVM运行在Server时默认 的新生代垃圾收集器。③Paraller Scavenge:多线程,基于复制算法,以吞吐量最大化为目标,允许较长时间的STW换取吞吐量。④Serial Old:单线程,基于标记整理算法,是JVM运行在Client模式下默认的老年代垃圾回收器,可和Serial搭配使用。⑤Parall Old:多线程,基于标记整理算法,优先考虑系统的吞吐量。⑥CMS:多线程,基于标记清除算法,为老年代设计,追求最短停顿时间。主要有四个步骤:初始标记、并发标记、重新标记、并发清除。⑥G1:将堆内存分为几个大小固定的独立区域,在后台维护了一个优先列表,根据允许的收集时间回收垃圾收集价值最大的区域。相比CMS不会产生内存碎片,并且可精确控制停顿时间。分为四个阶段:初始标记、并发标记、最终标记、筛选回收。

Java中有哪些引用类型?
答:①强引用,最常见的引用类型,把一个对象指向一个引用变量时就是强引用。强引用的对象一定为可达性状态,所以不会被垃圾回收,是内存泄漏的主要原因。②软引用,通过SoftReference实现,如果一个对象只有软引用,当内存空间不足时将被回收。③弱引用,通过WeakReference实现,如果一个对象只有弱引用,在垃圾回收过程中一定会被回收。④虚引用,通过PhantomReference实现,虚引用和引用队列联合使用,主要用来跟踪对象的垃圾回收过程。

JVM有哪些内存回收与回收策略?
①对象优先在Eden区分配:大多数情况下对象在新生代Eden区分配,当Eden区没有足够空间时,虚拟机将发起一次MinorGC。②大对象直接进入老年代:大对象是指需要大量连续内存空间的Java对象,如很长的字符串及数组。虚拟机提供了一个参数-XX:PretenureSizeThreshold,大于该值的对象会直接进入老年代,防止它在新生代之间来回复制。③长期存活的对象进入老年代:虚拟机给每个对象定义了一个年龄计数器,若对象在Eden区出生、经过第一次MinorGC后仍存活且能被Survivor容纳,将被移到Survivor区并且对象年龄设为1。每经过一次MinorGC,年龄就加1。默认在年龄增加到15时晋升到老年代,可通过-XX:MaxTenuringThreshold设置晋升老年代的年龄阈值。④动态对象年龄判定:如果在Survivor空间中相同年龄所有对象大小超过了该空间的一半,大于等于该年龄的对象就可以直接进入老年代而不用等到达到阈值。⑤空间分配担保:发生MinorGC前,先判断老年代最大可用连续空间是否大于新生代所有对象的总空间,如果成立那么MinorGC是安全的。如果不成立会查看HandlePromotionFailure是否允许担保,如果允许会冒险进行MinorGC,否则改为一次FullGC。


Spring

IOC(招行)

Spring的IOC和DI是什么?
①IOC即控制反转,简单来说就是把对象的控制权委托给spring框架,作用是降低代码的耦合度。②DI即依赖注入,是IOC的一种具体实现方式。假设一个Car类需要Engine的对象,那么一般需要new一个Engine,利用IOC就是只需要定义一个私有的Engine引用变量,容器会在运行时创建一个Engine的实例对象并将引用自动注入给变量。

简述Spring中bean对象的生命周期
①Spring对bean进行实例化。②Spring将值和bean的引用注入到其对应的属性中。③调用BeanNameAware的setBeanName方法。④调用BeanFactoryAware的setBeanFactory方法。⑤调用AppicationContxtAware的setApplicationContext方法。⑥调用BeanPostProcessor的post-ProcessBeforeInitialization方法。⑦调用InitializingBean的after-PropertiesSet方法。如果bean使用init-method声明了自定义初始化方法,该方法也会被调用。⑧调用BeanPostProcessor的post-ProcessAfterInitialization方法。⑨使用bean。⑩调用DisposableBean的destroy方法,如果bean使用destroy-method声明了自定义销毁方法,该方法也会被调用。

简述bean的作用范围
通过scope指定bean的作用范围,有①singleton:单例的,每次容器返回的对象是同一个。②prototype :多例的,每次返回的对象是新创建的实例。③request:仅作用于HttpRequest,每次Http请求都会创建一个新的bean。④session:仅作用于HttpSession,不同的Session使用不同的实例,相同的Session使用同一个实例。⑤global session :仅作用于HttpSession,所有的Session使用同一个实例。

BeanFactory和FactoryBean,ApplicationContext的区别?
①BeanFactory是一个Factory接口,是用来管理bean的IOC容器或对象工厂,较为古老,不支持spring的一些插件。BeanFactory使用了延迟加载,适合多例模式。②FactoryBean是一个Bean接口,是一个可以生产或者装饰对象的工厂Bean,可以通过实现该接口自定义的实例化Bean的逻辑。③ApplicationConext是BeanFactory的子接口,扩展了其功能,ApplicationContext是立即加载,适合单例模式。一般推荐使用ApplicationContext。

使用XML配置有哪些创建Bean对象的方式?
①通过默认无参构造器。使用bean标签,只使用id和class属性,如果没有无参构造器会报错。②使用静态工厂,通过bean标签中的class指明静态工厂,factory-method指明静态工厂方法。③使用实例工厂,通过bean标签中的factory-bean指明实例工厂,factory-method指明实例工厂方法。

依赖注入可以注入哪些数据类型?有哪些注入方式?
①可以注入的数据类型有基本数据类型、String、Bean、以及集合等复杂数据类型。②有三种注入方式,第一种是通过构造器注入,通过constructor-arg标签实现,缺点是即使不需要该属性也必须注入;第二种是通过Set方法注入,通过property标签实现,优点是创建对象时没有明确限制,缺点是某个成员变量必须有值,在获取对象时set方法可能还没有执行;第三种是通过注解注入,利用@Autowired自动按类型注入,如果有多个匹配则按照指定bean的id查找,查找不到会报错;@Qualifier在自动按照类型注入的基础之上,再按照 Bean 的 id 注入,给成员变量注入时必须搭配@Autowired,给方法注入时可单独使用;@Resource直接按照 Bean 的 id 注入;@Value用于注入基本数据类型和String。

有哪些配置Bean的注解,各有什么区别?
①@Component,把当前类对象存入spring容器中,相当于在 xml 中配置一个 bean。value属性指定 bean 的 id,如果不指定 value 属性,默认 id 是当前类的类名,首字母小写。②@Service,一般用于业务层。③@Controller:一般用于表现层。④@Repository:一般用于持久层。⑤@Controller @Service @Repository都是针对@Component的衍生注解,作用及属性都是一模一样的,只是提供了更加明确的语义化。


AOP(阿里、招行)

Spring Aop的基本原理是什么?
Aop即面向切面编程,简单地说就是将代码中重复的部分抽取出来,在需要执行的时候使用动态代理的技术,在不修改源码的基础上对方法进行增强。优点是可以减少代码的冗余,提高开发效率,维护方便。Spring会根据类是否实现了接口来判断动态代理的方式,如果实现了接口会使用JDK的动态代理,核心是InvocationHandler接口和Proxy类,如果没有实现接口会使用cglib的动态代理,cglib是在运行时动态生成某个类的子类,如果某一个类被标记为final,是不能使用cglib动态代理的。

简单解释一下AOP的相关术语
①Joinpoint(连接点):指那些被拦截到的点,在 spring 中这些点指的是方法,因为 spring 只支持方法类型的连接点。例如业务层实现类中的方法都是连接点。②Pointcut(切入点):指我们要对哪些 Joinpoint 进行拦截的定义。例如业务层实现类中被增强的方法都是切入点,切入点一定是连接点,但连接点不一定是切入点。③Advice(通知/增强):指拦截到 Joinpoint 之后所要做的事情。④Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提下可以在运行期为类动态地添加一些方法或 Field。⑤Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。⑥Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。⑦Target(目标):代理的目标对象。⑧Aspect(切面):是切入点和通知(引介)的结合。

Spring Aop有哪些相关注解?
@Before前置通知,@AfterThrowing异常通知,@AfterReturning后置通知,@After最终通知,@Around环绕通知。最终通知会在后置通知之前执行,为解决此问题一般使用环绕通知。

如何使用XML方式配置AOP?
①aop:config用于声明开始 aop 的配置。②aop:aspect用于配置切面。属性:id给切面提供一个唯一标识。ref引用配置好的通知类 bean 的 id。③aop:pointcut用于配置切入点表达式,就是指定对哪些类的哪些方法进行增强。属性:expression用于定义切入点表达式,id用于给切入点表达式提供一个唯一标识。④aop:before用于配置前置通知,在切入点方法执行之前执行;aop:after-returning用于配置后置通知,在切入点方法正常执行之后执行,它和异常通知只能有一个执行;aop:after-throwing用于配置异常通知,在切入点方法执行产生异常后执行;aop:after用于配置最终通知,无论切入点方法执行时是否有异常,它都会在其后面执行。


MySQL

select语句执行顺序

查询涉及的关键字 ----------执行顺序
select 查询列表 -------------------⑦
from 表 ------------------------------①
连接类型 join 表2------------------②
on 连接条件-------------------------③
where 筛选条件--------------------④
group by 分组列表----------------⑤
having 分组后筛选----------------⑥
order by 排序列表-----------------⑧
limit 偏移,条目数 ----------------⑨

存储引擎(阿里)

①MyISAM,是5.5版本之前的默认存储引擎,支持表级锁,不支持事务和外键,并发效率较低,读取数据快,更新数据慢。适合以读操作为主,并且对并发性要求较低的应用。②InnoDB,MySQL目前的默认存储引擎,支持行级锁、事务和外键,并发效率好。适合对事务的完整性和并发性、数据的准确性要求比较高,增删操作多的应用。③Memory,所有的数据都保存在内存中,访问速度快,一旦服务关闭数据将丢失。适合更新不太频繁的数据量小的表用来快速得到访问结果。

隔离级别及问题(招行、阿里)

隔离级别
①未提交读,一个事务会读取到另一个事务没有提交的数据,存在脏读、不可重复读、幻读的问题。②已提交读,一个事务可以读取到另一个事务已经提交的数据,解决了幻读的问题,存在不可重复读、幻读的问题。③可重复读,MySQL默认的隔离级别,在一次事务中读取同一个数据结果是一样的,解决了不可重复读的问题,存在幻读问题。④可串行化,每次读都需要获得表级共享锁,读写互相阻塞,效率低,解决了幻读问题。

可重复读如何实现
①使用MVCC多版本并发控制方式,类似于乐观锁的一种实现方式。②InnoDB在每行记录后面保存两个隐藏的列,分别保存了这个行的创建时间和行的删除时间。这里存储的并不是实际的时间值,而是系统版本号,当数据被修改时,版本号加1。③在读取事务开始时,系统会给当前读事务一个版本号,事务会读取版本号<=当前版本号的数据。此时如果其他写事务修改了这条数据,那么这条数据的版本号就会加1,从而比当前读事务的版本号高,读事务自然而然的就读不到更新后的数据了。

读取数据库时可能出现哪些问题
①脏读,一个事务中会读取到另一个事务中还没有提交的数据,如果另一事务最终回滚了数据,那么所读取到的数据就是无效的。②不可重复读,一个事务中可以读取到另一个事务中已经提交的数据,在同一次事务中对同一数据读取的结果可能不同。③幻读,一个事务在读取数据时,当另一个事务在表中插入了一些新数据时再次读取表时会多出几行,如同出现了幻觉。


MySQL优化(阿里)


Redis

数据结构(招行)

string类型的基本操作和注意事项
①存储的数据:单个数据,最简单常用的数据存储类型。存储数据的格式:一个存储空间保存一个数据。存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用。②添加/修改数据:set key value、获取数据:get key、删除数据:del key、添加/修改多个数据:mset key1 value1 key2 value2 …、获取多个数据:mget key1 key2 …、获取数据字符个数(字符串长度):strlen key、追加信息到原始信息后部(如果原始信息存在就追加,否则新建):append key value。③string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发 带来的数据影响。注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围(java中long型数据最大值,Long.MAX_VALUE)将报错。

hash类型的基本操作和注意事项
①存储需求:对一系列存储的数据进行编组,方便管理,一般存储对象信息。存储结构:一个存储空间保存多个键值对数据。底层使用哈希表结构实现数据存储。②如果field数量较少,存储结构优化为类数组结构;如果field数量较多,存储结构使用HashMap结构。③添加/修改数据:hset key field value、获取数据:hget key field,hgetall key、 删除数据:hdel key field1 [field2]、添加/修改多个数据:hmset key field1 value1 field2 value2 …、 获取多个数据:hmget key field1 field2 …、获取哈希表中字段的数量:hlen key、获取哈希表中是否存在指定的字段:hexists key field。③hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到, 对应的值为(nil)。每个 hash 可以存储 2^32 - 1 个键值对。hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计的,不可滥用,更不可以将hash作为对象列表使用。hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈。

list类型的基本操作和注意事项
答:①存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分。存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序。保存多个数据,底层使用双向链表存储结构实现。②添加/修改数据:lpush key value1 [value2] …,rpush key value1 [value2] …、获取数据:lrange key start stop,lindex key index,llen key、获取并移除数据:lpop key,rpop key。获取数据时可以设置等待时间,list为空时等待获取。移除指定数据:lrem key count value。③list中保存的数据都是string类型的,数据总容量是有限的,最多2^32- 1 个元素(4294967295)。list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作。获取全部数据操作结束索引设置为-1。list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载。

set类型的基本操作和注意事项
答:①存储需求:存储大量的数据,在查询方面提供更高的效率。存储结构:能够保存大量的数据,高效的内部存储机制,便于查询。与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的。②添加数据:sadd key member1 [member2]、获取全部数据:smembers key、删除数据:srem key member1 [member2]、获取集合数据总量:scard key、判断集合中是否包含指定数据:sismember key member。③set 类型不允许数据重复,如果添加的数据在 set 中已经存在,将只保留一份。set 虽然与hash的存储结构相同,但是无法启用hash中存储值的空间。

sorted-set类型的相关操作和注意事项
①存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式。存储结构:新的存储模型,可以保存可排序的数据,在set的存储结构基础上添加可排序字段。②添加数据:zadd key score1 member1 [score2 member2]、获取全部数据:zrange key start stop [WITHSCORES],zrevrange key start stop [WITHSCORES]、删除数据:zrem key member [member …]。③score保存的数据存储空间是64位,超过该范围的话score保存的数据也可以是一个双精度的double值,但可能会丢失精度,使用时候要慎重。sorted_set 底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果。


MySQL和redis的一致性(招行)


中间件

zookeeper应用场景(阿里)

维护配置信息
Java编程经常会遇到配置项,例如数据库的user、password等,通常配置信息会放在配置文件中,再把配置文件放在服务器上。当需要修改配置信息时,要去服务器上修改对应的配置文件,但在分布式系统中很多服务器都需要使用该配置文件,因此必须保证该配置服务的高可用性和各台服务器上配置的一致性。通常会将配置文件部署在一个集群上,但一个集群涉及的服务器数量是很庞大的,如果一台台服务器逐个修改配置文件是效率很低且危险的,因此需要一种服务可以高效快速且可靠地完成配置项的更改工作。
zookeeper就可以提供这种服务,使用Zab一致性协议保证一致性。hbase中客户端就是连接zookeeper获得必要的hbase集群的配置信息才可以进一步操作。在开源消息队列Kafka中,也使用zookeeper来维护broker的信息。在dubbo中也广泛使用zookeeper管理一些配置来实现服务治理。

分布式锁服务
一个集群是一个分布式系统,由多台服务器组成。为了提高并发度和可靠性,在多台服务器运行着同一种服务。当多个服务在运行时就需要协调各服务的进度,有时候需要保证当某个服务在进行某个操作时,其他的服务都不能进行该操作,即对该操作进行加锁,如果当前机器故障,释放锁并fall over到其他机器继续执行。

集群管理
zookeeper会将服务器加入/移除的情况通知给集群中其他正常工作的服务器,以及即使调整存储和计算等任务的分配和执行等,此外zookeeper还会对故障的服务器做出诊断并尝试修复。

生成分布式唯一ID
在过去的单库单表系统中,通常使用数据库字段自带的auto_increment熟悉自动为每条记录生成一个唯一的id。但分库分表后就无法依靠该属性来标识一个唯一的记录。此时可以使用zookeeper在分布式环境下生成全局唯一性id。每次要生成一个新id时,创建一个持久顺序结点,创建操作返回的结点序号,即为新id,然后把比自己结点小的删除。


发布了92 篇原创文章 · 获赞 426 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_41112238/article/details/105567156
今日推荐