java高级-面试

1.      ArrayList与LinkedList的区别

ArrayList和LinkedList的大致区别如下:

1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。

3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

 ArrayList内部是使用可増长数组实现的,所以是用get和set方法是花费常数时间的,但是如果插入元素和删除元素,除非插入和删除的位置都在表末尾,否则代码开销会很大,因为里面需要数组的移动。

LinkedList是使用双链表实现的,所以get会非常消耗资源,除非位置离头部很近。但是插入和删除元素花费常数时间。

 

1、ArrayList和LinkedList可想从名字分析,它们一个是Array(动态数组)的数据结构,一个是Link(链表)的数据结构,此外,它们两个都是对List接口的实现。前者是数组队列,相当于动态数组;后者为双向链表结构,也可当作堆栈、队列、双端队列

2、当随机访问List时(get和set操作),ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。

3、当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

4、从利用效率来看,ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。

5、ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。

 

2.      HashMap的结构及特点,底层实现

    HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

    java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个链表散列的数据结构,即数组和链表的结合体。

    从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

 

HashMap的实现原理:

1.    利用keyhashCode重新hash计算出当前对象的元素在数组中的下标存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中,获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

2.    理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

 

3.      HashMap扩容

当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

     那么hashmap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

 

4.      Hashmap 与ConcurrentHashMap

和 HashMap 非常类似,唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。

原理上来说:ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。

5.HashMap、HashTable和ConcurrentHashMap的线程安全问题

HashMap:线程不安全的。

HashTable:锁住整张hash表,让线程独占。hashMap同意为空。

通过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表

让线程独占。安全的背后是巨大的浪费。

ConcurrentHashMap:一个更快的hashmap,它提供了好得多的并发性。多个读操作差点儿总能够并发地运行。

他是锁段(默认:把hash表分为16个段),在get,put,remove等操作中,ConcurrentHashMap仅仅锁定当前须要用到的段,仅仅有在求size的时候才锁定整张hash表。

 

6.      多线程的锁机制(悲观锁和乐观锁,读写锁)

  悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

   乐观锁:1. 版本号机制 2. CAS算法

 

7.      线程池的流转过程,当核心线程数满的时候还有线程进来会怎样

 下面解释下一下构造器中各个参数的含义:

corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

 

8.      线程的状态及生命周期

当我们在Java程序中新建一个线程时,它的状态是New当我们调用线程的start()方法时,状态被改变为Runnable。线程调度器会为Runnable线程池中的线程分配CPU时间并且讲它们的状态改变为Running其他的线程状态还有WaitingBlocked Dead

 

9.      线程阻塞

sleep()和wait()方法都是Java中造成线程阻塞的方法。

而使用sleep()和wait()两种方法对于“CPU执行权”和“同步锁”的方式不同:

①sleep()释放CPU执行权,但不释放同步锁;

②wait()释放CPU执行权,也释放同步锁,使得其他线程可以使用同步控制块或者方法。

以上,就是sleep()和wait()方法的两个关键性区别。

 

10.   Java多线程中调用wait() 和 sleep()方法有什么不同

Java程序中wait sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

 

11.   Redis的穿透及雪崩

 redis缓存穿透:查询一个数据库中不存在的数据,比如商品详情,查询一个不存在的ID,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成过大地压力

解决方案:当通过某一个key去查询数据的时候,如果对应在数据库中的数据都不存在,我们将此key对应的value设置为一个默认的值,比如“NULL”,并设置一个缓存的失效时间,这时在缓存失效之前,所有通过此key的访问都被缓存挡住了。后面如果此key对应的数据在DB中存在时,缓存失效之后,通过此key再去访问数据,就能拿到新的value了。

 

reds缓存雪崩:是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案:将系统中key的缓存失效时间均匀地错开,防止统一时间点有大量的key对应的缓存失效。比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

 

redis缓存击穿(热点Key):缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案:对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

 

 

缓存雪崩

  缓存雪崩是由于原有缓存失效(过期),新缓存未到期间。所有请求都去查询数据库,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

1.碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。

    2. 加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

还有一个解决办法解决方案是:给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存。

缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。

这样做后,就可以一定程度上提高系统吞吐量。

 

缓存穿透

  缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

 

解决的办法就是:如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。

 

缓存预热

  缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样避免,用户请求的时候,再去加载相关的数据。

  解决思路:

    1,直接写个缓存刷新页面,上线时手工操作下。

    2,数据量不大,可以在WEB系统启动的时候加载。

    3,定时刷新缓存,

 

缓存更新

  缓存淘汰的策略有两种:

    (1) 定时去清理过期的缓存。

    (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

  两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂,具体用哪种方案,大家可以根据自己的应用场景来权衡。1. 预估失效时间 2. 版本号(必须单调递增,时间戳是最好的选择)3. 提供手动清理缓存的接口。

 

12.   Redis的数据结构

1、String

   可以是字符串,整数或者浮点数,对整个字符串或者字符串中的一部分执行操作,对整个整数或者浮点执行自增(increment)或者自减(decrement)操作。

2、list

一个链表,链表上的每个节点都包含了一个字符串,虫链表的两端推入或者弹出元素,根据偏移量对链表进行修剪(trim),读取单个或者多个元素,根据值查找或者移除元素。

3set(无序集合)

包含字符串的无序收集器(unordered collection)、并且被包含的每个字符串都是独一无二的。添加,获取,移除单个元素,检查一个元素是否存在于集合中,计算交集,并集,差集,从集合里面随机获取元素。

4、hash

包含键值对无序散列表,添加,获取,移除当键值对,获取所有键值对。

5zset(有序集合)

字符串成员(member)与浮点数分值(score)之间的有序映射,元素的排列顺序由分值的大小决定。添加,获取,删除单个元素,根据分值范围(range)或者成员来获取元素。

 

13.   Spring的aop原理及生命周期

  AOP:面向切面编程。(Aspect-Oriented Programming)

AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理

 

AOP常用的实现方式有两种,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ

14.   Spring的 aop应用场景

我们主要用来事务控制,比如运行日志,权限控制等,除此以为,aop在模块的耦合,及管理,功能升级方面多有很好的用途

对一个方法或功能做 前置处理或后置处理

 

15.   Spring的ioc原理

  IOC:控制反转也叫依赖注入,IOC利用java反射机制,AOP利用代理模式。所谓控制反转是指,本来被调用者的实例是有调用者来创建的,这样的缺点是耦合性太强,IOC则是统一交给spring来管理创建,将对象交给容器管理,你只需要在spring配置文件总配置相应的bean,以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。

 

16.   依赖注入的实现方式

1.  构造器注入

Setter方法注入

2.  接口注入

 

17.   Spring的bean的生命周期

在说明前可以思考一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;

    Spring上下文中的Bean也类似,如下

    1、实例化一个Bean--也就是我们常说的new;

    2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;

    3、如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值

    4、如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);

    5、如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);

    6、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;

    7、如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。

    8、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、;

    注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。

    9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;

    10、最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

 

Spring中bean的实例化过程:

1247358-20190218164941407-418512882.jpg

  与上图类似,bean的生命周期流程图:

1247358-20190218164942234-482676254.jpg

  Bean实例生命周期的执行过程如下:

Spring对bean进行实例化,默认bean是单例;

Spring对bean进行依赖注入;

如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;

如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;

如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;

如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;

如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用;

如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;

此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;

若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用;

18.   Spring的事务的传播行为

1、PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

2、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘

3、PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

4、PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

5、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

 

19.   Mysql的索引原理

索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址,在数据十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。

BTree索引,B+Tree索引,哈希索引,全文索引

   常见的索引类型有:主键索引、唯一索引、普通索引、全文索引、组合索引

20.   索引的使用策略

  什么时候要使用索引?

主键自动建立唯一索引;

经常作为查询条件在WHERE或者ORDER BY 语句中出现的列要建立索引;

作为排序的列要建立索引;

查询中与其他表关联的字段,外键关系建立索引

高并发条件下倾向组合索引;

 

什么时候不要使用索引?

经常增删改的列不要建立索引;

有大量重复的列不建立索引;

表记录太少不要建立索引;

 

*在组合索引中不能有列的值为NULL,如果有,那么这一列对组合索引就是无效的;

*在一个SELECT语句中,索引只能使用一次,如果在WHERE中使用了,那么在ORDER BY中就不要用了;

*LIKE操作中,'%aaa%'不会使用索引,也就是索引会失效,但是‘aaa%’可以使用索引;

*在索引的列上使用表达式或者函数会使索引失效,例如:select * from users where YEAR(adddate)<2007,将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′。

*在查询条件中使用正则表达式时,只有在搜索模板的第一个字符不是通配符的情况下才能使用索引。

*在查询条件中使用<>会导致索引失效。

*在查询条件中使用IS NULL会导致索引失效。

*在查询条件中使用OR连接多个条件会导致索引失效,这时应该改为两次查询,然后用UNION ALL连接起来。

*尽量不要包括多列排序,如果一定要,最好为这队列构建组合索引;

*只有当数据库里已经有了足够多的测试数据时,它的性能测试结果才有实际参考价值。如果在测试数据库里只有几百条数据记录,它们往往在执行完第一条查询命令之后就被全部加载到内存里,这将使后续的查询命令都执行得非常快--不管有没有使用索引。只有当数据库里的记录超过了1000条、数据总量也超过了MySQL服务器上的内存总量时,数据库的性能测试结果才有意义。

 

21.   什么情况下对于索引在什么情况下会失效

1、索引列有函数处理或隐式转换,不走索引
2、索引列倾斜,个别值查询时,走索引代价比走全表扫描高,所以不走索引
3、索引列没有限制 not null,索引不存储空值,如果不限制索引列是not null,oracle会认为索引列有可能存在空值,所以不会按照索引计算)

 

22.   Mysql的事务隔离级别

一、事务的基本要素(ACID)

  1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

   2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

   3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

   4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

二、事务的并发问题

  1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

  3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

  小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

三、MySQL事务隔离级别

 

事务隔离级别

脏读

不可重复读

幻读

读未提交(read-uncommitted

不可重复读(read-committed

可重复读(repeatable-read

串行化(serializable

 

mysql默认的事务隔离级别为repeatable-read

23.   MySQL引擎

MyISAM是MySQL的默认数据库引擎(5.5版之前),由早期的ISAM(Indexed Sequential Access Method:有索引的顺序访问方法)所改良。虽然性能极佳,但却有一个缺点:不支持事务处理(transaction)。不过,在这几年的发展下,MySQL也导入了InnoDB(另一种数据库引擎),以强化参考完整性与并发违规处理机制,后来就逐渐取代MyISAM。

 

MyISAM和InnoDB两者的应用场景:

1) MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。

2) InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能

 

24.   Mybatis的底层实现

  Mybatis的运行分为两个部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。相对而言,SqlSessionFactory的创建比较容易理解,而SqlSession的执行过程远远不是那么简单了,它将包括许多复杂的技术,我们需要先讨论反射技术和动态代理技术,这是揭示mybatis底层架构的基础。

   代理类的要求是实现InvocationHandler接口的代理方法,当一个对象被绑定后,执行其方法的时候就会进入到代理方法里

 

25.   Mybatis 的二级缓存

从上面三张图中我们得出结论,一级缓存是sqlsession级别、二级缓存是Mapper级别。mybatis定义了【Cache】接口,支持自定义缓存,同时还支持集成第三方缓存库,现在为了更细粒度的控制缓存,更多的集成【ehcache】、【redis】。

那么mybatis的二级缓存主要是在Executor对象上来做文章,当mybatis发现你在mybatis.xml配置文件中设置了cacheEnabled=true时,mybatis在创建sqlsession时创建Executor对象,同时会对Executor加上装饰者【CacheExecutor】。CacheExecutor对于查询请求,会判断application级别的二级缓存是否有缓存结果,如果有查询结果则直接返回,如果没有再交给查询器Executor实现类,也就是【SimpleExecutor】来执行查询。再就是缓存结果,返回给用户。

1. 只能在【只有单表操作】的表上使用缓存不只是要保证这个表在整个系统中只有单表操作,而且和该表有关的全部操作必须全部在一个namespace下。
   2. 在可以保证查询远远大于insert,update,delete操作的情况下使用缓存

1247358-20190218164943040-551297133.jpg

1247358-20190218164943974-904297653.jpg

1247358-20190218164944542-192814411.jpg

26.   Cas单点登录的原理

客户端消息流程

1.第一次访问 http://localhost:8080/A

  CLIENT:没票据且SESSION中没有消息所以跳转至CAS

  CAS:拿不到TGC故要求用户登录

2.认证成功后回跳

  CAS:通过TGT生成ST发给客户端,客户端保存TGC,并重定向到http://localhost:8080/A

  CLIENT:带有票据所以不跳转只是后台发给CAS验证票据(浏览器中无法看到这一过程)

3. 第一次访问http://localhost:8080/B

  CLIENT:没票据且SESSION中没有消息所以跳转至CAS

  CAS:从客户端取出TGC,如果TGC有效则给用户ST并后台验证ST,从而SSO。【如果失效重登录或注销时,怎么通知其它系统更新SESSION信息呢??

  TicketGrantingTicketImpl类grantServiceTicket方法里this.services.put(id,service);可见CAS端已经记录了当前登录的子系统】

4.再次访问 http://localhost:8080/A

CLIENT:没票据但是SESSION中有消息故不跳转也不用发CAS验证票据,允许用户访问

 

基础模式 SSO 访问流程主要有以下步骤:

1. 访问服务: SSO 客户端发送请求访问应用系统提供的服务资源。

2. 定向认证: SSO 客户端会重定向用户请求到 SSO 服务器。

3. 用户认证:用户身份认证。

4. 发放票据: SSO 服务器会产生一个随机的 Service Ticket 。

5. 验证票据: SSO 服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。

6. 传输用户信息: SSO 服务器验证票据通过后,传输用户认证结果信息给客户端。

 

27.   垃圾回收机制GC

堆:所有的对象实例与数组,GC堆,分为新生代与老年代

栈:栈帧包含局部变量表(基本数据类型 8种、对象引用类型)、操作数栈、动态链接、方法出口

方法区:类信息、常量、静态变量、即时编译器编译后的代码等数据,也成为永久代

 

标记清除算法 Mark-sweep 存在效率问题与空间问题(产生大量不连续的内存碎片)

复制算法(Copying):应用十分广泛,将内存分为一块较大的Eden和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。回收时,将Eden与Survivor复制到另一块Survivor是哪个。 默认Eden与Survivor的比例是8:1

标记整理算法(Mark-Compact) 适用于老年代,将所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

28.   常用设计模式

单例模式:懒汉式、饿汉式、双重校验锁、静态加载,内部类加载、枚举类加载。保证一个类仅有一个实例,并提供一个访问它的全局访问点。

代理模式:动态代理和静态代理,什么时候使用动态代理。

适配器模式:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

装饰者模式:动态给类加功能。

观察者模式:有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

外观模式:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

命令模式:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

创建者模式:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

29.   消息队列

消息队列的主要特点是异步处理,主要目的是减少请求响应时间和解耦。

  主要的使用场景就是将比较耗时而且不需要立即生效返回结果的操作,我们把这种操作作为一个消息,放到消息队列中。处理方可以在任何时候去获取并处理这条消息。这里我们只要保证消息的格式不变,消息的发送方和接收处理方都认识这个消息,那么双方就不需要彼此通信,即可以完成一件事。

当然,如果我们使用消息队列的话,也有许多需要注意的点。比如,消息的发送方不需要接收方立即返回处理结果,否则的话只能等待处理结果;比如系统会有短暂的不一致性,发送方不可预知接收方什么时间处理完这个消息。当然,实际生产中还是有需要地方允许这些比如的,所以消息队列现在是异常的火爆。

30.   设计模式六大原则

单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

依赖倒转原则(Dependency Inversion  Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

接口隔离原则(InterfaceSegregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

迪米特法则(Law ofDemeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

31.   JWT

组成:header.payload.signature

工作原理:首先用户发出登录请求,服务端根据用户的登录请求进行匹配,如果匹配成功,将相关的信息放入payload中,利用上述算法,加上服务端的密钥生成token,这里需要注意的是secret_key很重要,如果这个泄露的话,客户端就可以随意篡改发送的额外信息,它是信息完整性的保证。生成token后服务端将其返回给客户端,客户端可以在下次请求时,将token一起交给服务端,一般来说我们可以将其放在Authorization首部中,这样也就可以避免跨域问题。接下来,服务端根据token进行信息解析,再根据用户信息作出相应的操作。

  优点:不易被攻击者利用,安全性提高。利用Authorization首部传输token,无跨域问题。额外信息存储在客户端,服务端占用资源不多,也不存在session共享问题。

  缺点

   登录状态信息续签问题。比如设置token的有效期为一个小时,那么一个小时后,如果用户仍然在这个web应用上,这个时候当然不能指望用户再登录一次。目前可用的解决办法是在每次用户发出请求都返回一个新的token,前端再用这个新的token来替代旧的,这样每一次请求都会刷新token的有效期。但是这样,需要频繁的生成token。另外一种方案是判断还有多久这个token会过期,在token快要过期时,返回一个新的token。下面是我在项目里的一个实现。

    用户主动注销。JWT并不支持用户主动退出登录,当然,可以在客户端删除这个token,但在别处使用的token仍然可以正常访问。为了支持注销,我的解决方案是在注销时将该token加入黑名单。当用户发出请求后,如果该token在黑名单中,则阻止用户的后续操作,返回Invalid token错误。这个地方我再稍微补充一下,其实这里的黑名单操作也比较简单,把已经注销的token存入比如说一个set中,那么在每次进行token验证时,先检查在set中是否已经存在,如果已经存在的话,则视为token已经失效,直接返回未授权。这一部分在上面的授权代码中也可以看到,不过我是放到redis缓存中的。

32.   http与https

  HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。

  HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

  HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。

 

 HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

  HTTPS和HTTP的区别主要如下:

  1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

  2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

  3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

  4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

 

 

转载于:https://www.cnblogs.com/gz9218/p/4ac9b337f41a45a476b67208bb0edb56.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值