小白成仙之路

目录

前言

一、设计模式

1.策略模式

2.模版方法/责任链模式/装饰模式

3.代理模式

4.观察者模式

5.单例模式

6.线程池

二、Redis

1.Redis应用场景/分为多少个库

2.Redis是否线程安全/Redis效率

3.Redis是否线程安全/Redis效率

4.Redis全量同步与增量同步实现

5.Redis内存满了,如何处理

6.Redis是否会丢失数据

7.Redis事务支持回滚吗

8.Redis-SetnX与Set命令的区别

9.Redis缓存穿透概念与解决方案

10.Redis集群方式

11.Redis如何存放对象

12.Redis淘汰策略6种

13.Redis启动方式

三、容器

1.Arraylist基本概念

2.ModCount++作用

3.如何防御fail-fast

4.Vector 与Arraylist区别

5.HashMap结构

6.HashMap与HashTable有那些区别

8.HashMap的put方法是如何实现

9.HashMap1.7扩容如何实现的呢

10.HashSet如何保证不重复/Jdk8中为什么要使用红黑树

11.时间复杂度O(1)、O(N)、O(Log n)区别

12.HashMap的工作原理是什么

13.HashMap根据key时间复杂度/红黑树时间复杂度

14.CocurrentHashMap(JDK 1.7)

15. CocurrentHashMap(JDK 1.8)

三、微服务相关

1.本地负载均衡算法

2.Ap与CP区别

3.为什么要使用服务网关/过滤器与微服务网关的区别

4.如何保证API的安全性

5.Zookeeper实际使用场景/如何实现分布式锁

6.分布式锁中代码出现业务逻辑问题,导致一直不释放锁

7.Zookeeper节点有那些类

8.Zookeeper选举的策略/集群节点为何一定要是奇数

9.Zookeeper如何保证节点一致性问题

10.Zookeeper在后期新增zk节点时如何提高选举效率问题

11.Zookeeper集群节点如何保证数据同步问题

12.什么场景下会导致ZooKeeper发生延迟通知

13.网站跨域解决方案

14.网站动静分离架构模式

15.DiscoveryClient的作用有那些

16.分布式事务的解决

四、Java基础

1.JVM常用的调优参数

2.JVM运行时数据区域有哪几部分组成,各自作用

3.gc算法有哪些/gc收集器有哪些

4.什么是Full GC?minor GC? major GC? STW?

5.JVM中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代

6.Java 中都有哪些引用类型

7.为什么要使用服务网关/过滤器与微服务网关的区别

8.什么是双亲委派模型

9.类加载的过程

10.Switch数据类型支持哪些

11.Java有哪些锁?区别在哪?底层如何实现的?为什么非公平锁效率高

12.线程池使用场景及其核心参数说明

13.数据库事务四大特性

14.List, Set, Map是否继承自Collection接口

15.接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)

16.构造器Constructor是否可被override

17.JSP 9大内置对象详解

18.八大基本数据类型

19.String、StringBuffer、StringBuilde

20.Queue 中 poll()和 remove()有什么区别

四、Spring

1.容器的理解,IOC,AOP,事务

2.类加载的过程

3.拦截器与过滤器区别

4.BeanFactory 和 ApplicationContext 有什么区别

5.Spring Bean 的生命周期

6.拦Spring框架中有哪些不同类型的事件

7.Spring支持的几种bean的作用域

8. Spring如何处理线程并发问题 

9.Spring的自动装配

10.Spring通知有哪些类型

五、锁/多线程

1.你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?问题二:你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁

2.为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

3.请谈谈 AQS 框架是怎么回事儿

4.请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。

5.如何让 Java 的线程彼此同步?你了解过哪些同步器?请分别介绍下

6.Java 中的线程池是如何实现的

7.线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?

8.既然提到可以通过配置不同参数创建出不同的线程池,那么 Java 中默认实现好的线程池又有哪些呢?请比较它们的异同

9.Java 中的线程池是如何实现的

10.volatile 有什么特点,为什么它能保证变量对所有线程的可见性

11.volatile 对比 Synchronized 的异同。

12.什么是 java 序列化?什么情况下需要序列化?

13.volatile 对比 Synchronized 的异同。

六、Mybatis

1.RowBounds 是一次性查询全部结果吗?为什么?

2.mybatis 逻辑分页和物理分页的区别是什么

3.mybatis 是否支持延迟加载?延迟加载的原理是什么

4.mybatis 如何编写一个自定义插件

七、MYSQL

索引

1:索引的优缺点

2:MySQL主要的几种索引类型

3:主键索引和唯一索引的区别

4:聚簇索引相对于非聚簇索引的区别?

5:什么是回表查询?

6;什么是索引覆盖?

7:什么是最左匹配原则?

8:索引失效场景有哪些?




前言

提示:这里可以添加本文要记录的大概内容:

一、设计模式

1.策略模式

设计模式总共有23种,分为三类 行为、创建、结构,策略主要解决多重if判断的问题,存在抽象共同行为,不同的策略,交给不同的子类实现,应用场景:联合登陆(QQ、微信、码云、其他)支付接口(支付宝、微信、银联等)

2.模版方法/责任链模式/装饰模式

1:模版方法一般的情况下和策略模式一起使用的。模版方法设计思想:提供共同的骨架,相同的代码放入到父类中,不同的代码交给子类实现

2:责任链模式应用场景:过滤器、风控系统、工作流、erp审批

3:多级缓存的设计  mybatis一级和二级就是采用装饰模式 、IO流

3.代理模式

Aop、mybatis mapper对象、RPC远程调用、事务、日志、权限控制等。

Jdk动态代理采用反射机制回调,必须依赖于接口。

CGLIB采用字节码技术调用,采用重写形式

4.观察者模式

观察者模式作用:实现对我们代码实现监听 (生产者与消费者模式),使用MQ的时候就可以采用观察者模式实现监听,消费监听MQ的消息。应用场景: MQ、Zookeeper、Redis、分布式配中心等。

5.单例模式

懒汉式:先天性线程不安全,当真正需要该实例的时候才去加载,需要我们自己人为上锁控制线程安全的问题。

饿汉式:先天性线程安全,当我们项目在启动的时候创建该实例,会导致项目启动比较慢。

单例模式双锁实现代码

  1. public class Test {
    private volatile  static Test t;
    private Test() {
    
    }
    public   static Test  getInstance(){
        if(t==null){
            synchronized (Test.class){
                if(t==null){
                    try {
                        Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            t=new Test();
                        }
                    }
            }
           return t;
        }
        public static void main(String[] args) {
            // 1.模拟线程不安全
            for (int i = 0; i < 100; i++) {
                new Thread(new Runnable() {
                    public void run() {
                        Test instance1 = Test.getInstance();
                        System.out.println(Thread.currentThread().getName() + "," +     instance1);
                    }
                }).start();
            }
        }
    }

    6.线程池

多线程的复用机制核心思路:我们创建了一个线程,执行了我们run方法的业务逻辑之后,不会立马停止线程,而是继续复用执行下一个任务。可以使用一个线程执行多个不同的任务,从而实现复用机制。怎么保证我们的线程不停止:写一个死循环的。创建方式:线程池创建方式核心靠的就是我们的Executors

1:corePoolSize 线程池核心线程大小;2:maximumPoolSize 线程池最大线程数量

3:keepAliveTime 空闲线程存活时间;4:unit 空闲线程存活时间单位;

5:workQueue 工作队列;6:threadFactory 线程工厂;7:handler 拒绝策略

二、Redis

1.Redis应用场景/分为多少个库

订单超时,分布式锁,tokken令牌

Redis默认的情况下分为16个库,为什么要分成16个库,在单个库中不允许存在重复的key

2.Redis是否线程安全/Redis效率

Redis线程安全

Redis的底层采用Nio中的多路IO复用的机制,能够非常好的支持这样的并发,从而保证线程安全问题;Redis单线程,也就是底层采用一个线程维护多个不同的客户端io操作。但是Nio在不同的操作系统上实现的方式有所不同,在我们windows操作系统使用select实现轮训时间复杂度是为o(n),而且还存在空轮训的情况,效率非常低, 其次是默认对我们轮训的数据有一定限制,所以支持上万的tcp连接是非常难。

所以在linux操作系统采用epoll实现事件驱动回调,不会存在空轮训的情况,只对活跃的 socket连接实现主动回调这样在性能上有大大的提升,所以时间复杂度是为o(1)

3.Redis是否线程安全/Redis效率

手动清除我们的Redis的缓存,直接从新查询数据库即可。

基于Cancnle ,MQ的形式订阅MySQL的BinLog文件保证数据的一致性的问题

4.Redis全量同步与增量同步实现

全量同步:每天定时避开高峰期,将所有的数据全部实现备份同步,优点:数据可以不用丢失,效率高、但是可能会产生数据同步的延迟。增量同步:对行为的操作实现对数据的同步,数据同步延迟的概率比较多,因为比较频繁效率效率低。

5.Redis内存满了,如何处理

扩容内存:但是治标不治本

     内存淘汰策略删除经常不被使用的key。

6.Redis是否会丢失数据

不会丢失,因为Redis有持久化的机制,采用RDB和AOF持久化方案

7.Redis事务支持回滚吗

Redis中存在事务,但是没有回滚只有取消事务。

8.Redis-SetnX与Set命令的区别

Setnx 可以返回该key 是否存在  存在返回0 不存在返回1如果该key存在的情况下,是不能做修改的。Set  每次直接覆盖该key对应的value

9.Redis缓存穿透概念与解决方案

缓存穿透: 查询不存在的

缓存雪崩:多个key同时失效

缓存击穿:单个key失效

解决方案:记录空查询key,设置简单的过期时间;布隆过滤器对我们api接口实现限流、黑名单、白名单的机制;Key有效时间随机生成或者Rediskey 不过期

10.Redis集群方式

主从复制:如果主宕机需要人工重新配置主节点

哨兵集群:数据冗余

Redis的Cluster集群模式 动态实现扩容和缩容 而且保证每个节点的数据不冗余存放。

传统一主多从复制存在那些问题:如果主宕机之后,需要手动的重启我们主的服务器或者是手动实现,服务器节点的选举。从节点如果越多的情况下,主节点复制给从节点的过程压力比较大。

RedisCluster分片集群实现原理:在我们的Redis集群模式中分为16384个卡槽,类似于数据库的中分表模式。当我们在写入一个key的时候,会对该key计算crc16算法得住一个数字16384=卡槽的位置。每个卡槽对应具体节点存放的位置,这样的话就可以将我们的数据可以均摊的存放各个节点。

11.Redis如何存放对象

1.基于JSON序列化存放 优点:阅读性强、可以跨语言 缺点: 明文的不安全

2.基于String二进制直接存放我们的对象  不可以跨语言,阅读性差、比较安全

12.Redis淘汰策略6种

noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。

allkeys-lru:在主键空间中,优先移除最近未使用的key。(推荐)

volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key。

allkeys-random:在主键空间中,随机移除某个key。

volatile-random:在设置了过期时间的键空间中,随机移除某个key。

volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除

13.Redis启动方式

启动项@EnableCaching

方法加上@Cacheable(cacheNames = "getMovieByIdV2", key = "'getMovieByIdV2'")

三、容器

1.Arraylist基本概念

默认扩容原数组50%:新数组长度=源数组+源数组/2

arraylist 插入删除慢,查询快速,非线程安全

2.ModCount++作用

checkForComodification()方法(就是上面内部类代码中最后一个方法)来实时检测expectedModCount变量和modCount变量的值是否相同,不相同就会抛出异常

3.如何防御fail-fast

使用迭代器中的remove()方法会先调用ArrayList中的remove()方法修改modCount,然后在将modCount赋值给expectedModCount来保证不会检测到错误抛出异

4.Vector 与Arraylist区别

一个线程安全一个非线程安全

Arraylist底层扩容是原数组的50%,Vector集合是原数组的100%

Arraylist底层数组没有默认长度为0,Vector底层数组默认长度为10

5.HashMap结构

1.7=数组+链表 1.8=数组+链表+红黑树

6.HashMap与HashTable有那些区别

HashMap线程不安全,所以效率比较高

HashTable线程安全,但是效率比较低

HashMap可以允许key为null,存放在数组第0个位置

HashTable不可以允许key为空 存放为空的key直接报错

8.HashMap的put方法是如何实现

HashMap1.7采用数组+链表实现  采用HashEntry封装我们的键值对。

根据我们的key计算hashCode得出index存放的下标位置,如果产生hashCode碰撞的话

则采用链表存放。

注意:1.7HashMap采用数组+链表、采用头插入法在多线程扩容我们的数组的可能会产生死锁的问题。查询HashMap产生冲突的key时间复杂度为on,所以在jdk1.8 当链表的长度大于8的情况下并且数组的长度是为64的情况下转换为红黑树存放

9.HashMap1.7扩容如何实现的呢

HashMap中的数组默认的容量为16   加载因子这个值 0.75

16*0.75=12 开始提前扩容 对原来的数组乘以2 =32;为什么加载因子不是 0.5 或者是1 那为什么是0.75如果加载因子太小的情况下,key 冲突的概率比较小的,但是非常浪费内存空间

如果加载因子太大的情况下,key的冲突概率非常大,但是利用的空间非常好

10.HashSet如何保证不重复/Jdk8中为什么要使用红黑树

通过equals比较

解决链表过长,影响查询效率问题

11.时间复杂度O(1)、O(N)、O(Log n)区别

O(1)就是最低的时空复杂度了,也就是耗时/耗空间与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间都不变。 哈希算法就是典型的O(1)时间复杂度,无论数据规模多大,都可以在一次计算后找到目标(不考虑冲突的话)

比如时间复杂度为O(n),就代表数据量增大几倍,耗时也增大几倍。遍历算法。 要找到一个数组里面最大的一个数,你要把n个变量都扫描一遍,操作次数为n,那么算法复杂度是O(n).

再比如O(log n),当数据增大n倍时,耗时增大log n倍(这里的log是以2为底的,比如,当数据增大256倍时,耗时只增大8倍,是比线性还要低的时间复杂度)。二分查找就是O(log n)的算法,每找一次排除一半的可能,256个数据中查找只要找8次就可以找到目标

12.HashMap的工作原理是什么

以下是 HashMap 初始化

简化的模拟数据结构:

Node[] table = new Node[16]; // 散列桶初始化,table

class Node {

    hash; //hash值

    key; //键

    value; //值

    node next; //用于指向链表的下一层(产生冲突,用拉链法)

}

以下是具体的 put 过程(JDK1.8)

对 Key 求 Hash 值,然后再计算下标

如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的 Hash 值相同,需要放到同一个 bucket 中);如果碰撞了,以链表的方式链接到后面;如果链表长度超过阀值(TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;如果节点已经存在就替换旧值

如果桶满了(容量16 * 加载因子0.75),就需要 resize(扩容2倍后重排)以下是具体 get 过程

考虑特殊情况:如果两个键的 hashcode 相同,你如何获取值对象?当我们调用 get() 方法,HashMap 会使用键对象的 hashcode 找到 bucket 位置,找到 bucket 位置之后,

会调用 keys.equals() 方法去找到链表中正确的节点,最终找到要找的值对

13.HashMap根据key时间复杂度/红黑树时间复杂度

O(1)

O(log n)

14.CocurrentHashMap(JDK 1.7)

CocurrentHashMap 是由 Segment 数组和 HashEntry 数组和链表组成

Segment 是基于重入锁(ReentrantLock):一个数据段竞争锁。每个 HashEntry 一个链表结构的元素,

利用 Hash 算法得到索引确定归属的数据段,也就是对应到在修改时需要竞争获取的锁。

ConcurrentHashMap 支持 CurrencyLevel(Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,

不会影响到其他的 Segment

核心数据如 value,以及链表都是 volatile 修饰的,保证了获取时的可见性

首先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put 操作如下:

将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。

遍历该 HashEntry,如果不为空则判断传入的  key 和当前遍历的 key 是否相等,相等则覆盖旧的 value

不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容

最后会解除在 1 中所获取当前 Segment 的锁

虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理

首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁

尝试自旋获取锁

如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取成功。最后解除当前 Segment 

15. CocurrentHashMap(JDK 1.8)

CocurrentHashMap 抛弃了原有的 Segment 分段锁,采用了 CAS + synchronized 来保证并发安全性。

其中的 val next 都用了 volatile 修饰,保证了可见性。

最大特点是引入了 CAS

借助 Unsafe 来实现 native code。CAS有3个操作数,内存值 V、旧的预期值 A、要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,

将内存值V修改为 B,否则什么都不做。Unsafe 借助 CPU 指令 cmpxchg 来实现。

CAS 使用实例

对 sizeCtl 的控制都是用 CAS 来实现的:

-1 代表 table 正在初始化

N 表示有 -N-1 个线程正在进行扩容操作

如果 table 未初始化,表示table需要初始化的大小

如果 table 初始化完成,表示table的容量,默认是table大小的0.75倍,用这个公式算 0.75(n – (n >>> 2))

CAS 会出现的问题:ABA

解决:对变量增加一个版本号,每次修改,版本号加 1,比较的时候比较版本号。

put 过程:根据 key 计算出 hashcode

判断是否需要进行初始化

通过 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功

如果当前位置的 hashcode == MOVED == -1,则需要进行扩容

如果都不满足,则利用 synchronized 锁写入数据

如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树

get 过程:根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值

如果是红黑树那就按照树的方式获取值

就不满足那就按照链表的方式遍历获取值

三、微服务相关

1.本地负载均衡算法

轮训、权重、取模、随机、hash等。

Nginx是客户端所有的请求统一都交给我们的Nginx处理,让后在由Nginx实现负载均衡转发,属于服务器端负载均衡器。

本地负载均衡器是从注册中心获取到集群地址列表,本地实现负载均衡算法,既本地负载均衡器。

2.Ap与CP区别

一致性(C):在分布式系统中,如果服务器集群,每个节点在同时刻访问必须要保持数据的一致性。

可用性(A):集群节点中,部分节点出现故障后任然可以使用 (高可用)

分区容错性(P):在分布式系统中网络会存在脑裂的问题,部分Server与整个集群失去节点联系,无法组成一个群体。

只有在CP和AP选择一个平衡点

3.为什么要使用服务网关/过滤器与微服务网关的区别

网关是整个微服务API请求的入口,可以实现日志拦截、权限控制、解决跨域问题、限流、熔断、负载均衡、黑名单与白名单拦截、授权等。

过滤器以系统为级别,网关以所有服务为级别

4.如何保证API的安全性

  1. 接口通讯协议必须采用https  ssl+证书加密传输的协议 443
  2. 对我们的接口实现加密, 不用明文传输参数  RSA 非对称公钥和私钥互换 
  3. 使用MD5加密验证签名的方式防止篡改我们的数据
  4. 使用网关或者是Nginx对我们Api接口实现限流
  5. 对频繁访问的请求使用图形验证码过滤,防止机器模拟访问我们接口
  6. 基于网关对我们的api接口参数实现防止xss、sql注入
  7. 微服务架构中使用sentinel我们服务保护(热词保护、动态限流、服务降级、系统自适应形式实现限流

5.Zookeeper实际使用场景/如何实现分布式锁

注册中心(Dubbo+Zookeeper)+分布式配置中心(统一存放配置文件)+分布式锁

因为Zookeeper节点路径保持唯一,不允许重复 且有临时节点特性连接关闭后当前节点会自动消失,从而实现分布式锁。

6.分布式锁中代码出现业务逻辑问题,导致一直不释放锁

设置Session连接超时时间,在规定的时间内获取锁后超时,自动回滚当前数据库业务逻辑。

7.Zookeeper节点有那些类

  1. 临时节点
  2. 临时顺序节点
  3. 持久节点
  4. 持久顺序节点

8.Zookeeper选举的策略/集群节点为何一定要是奇数

(1,100) , (2,100) ,(3,100) 为什么会选出了2 为leader:Zxid相等,取决于优先启动顺序比较myid

(1,103) ,(2,102),(3,101) ,为什么会选出了1为leader:优先比较zxid,1>2

加入Zookeeper的集群节点为5的话,宕机几个Zookeeper节点之后剩余的节点个数大于宕机个数;也就是必须大于剩下服务器个数n(集群节点总数)/2,Zookeeper才可以继续使用,无论是奇数还是偶数个数可以做选举leader的,如果是5台集群节点也就是最多只可以允许两台zk宕机;如果是六台zk集群节点那么也是最多只能宕机2台,如果宕机3台的话zk无法可能。

所以占用服务器空间利用成本角度考虑,建议zk集群节点一定是为奇数

9.Zookeeper如何保证节点一致性问题

Zookeeper核心是原子广播协议(ZAB原子广播协议),这种机制保证了各个节点的数据同步的问题,ZAB协议有有两种模式 分别为恢复模式和(选主)、广播模式(同步)。

恢复模式也就是当我们leader的节点宕机之后,会从新在剩余节点选出新的leader,新的leader选出之后采用广播模式实现各个节点与新的leader同步。

10.Zookeeper在后期新增zk节点时如何提高选举效率问题

首先在Zookeeper中分为三种角色:

  1. Leader(领导) Zookeeper集群中的主节点、负责写的请求操作;
  2. Follower(跟随者) 是领导(Leader)角色根随着,出读取操作还可以实现对Leader提议与选举
  3. Observer 如果后期当我们在扩展ZK集群节点时如果角色为Follower越来越多会导致选举的时间越来越长,所以Observer角色和Follower角色很相似,只是obServer不能够参与Leader角色选举;
  4. 增加obServer作用主要影响原来本身选举的时间效率、目的提高客户端读的请求效率

11.Zookeeper集群节点如何保证数据同步问题

所有follower节点写的请求统一交个leader实现,并且创建一个全局zxid(事务id)

Leader节点在第一阶段通知阶段,会带上zxid向每位follower节点发出确认同步通知,只要有过半数的follower节点确认同步ack,这时候leader就会向所有的follower发出commit事务数据提交

12.什么场景下会导致ZooKeeper发生延迟通知

watch事件延迟:节点被修改后,会有事件通知发往观察者,直到接收到watch事件,观察者才会知道节点被修改了;当观察者接到watch事件的那一刻,该节点又被其他修改者修改了,而 近的watch事件还没有通知到观察者,就会造成延迟通知。

13.网站跨域解决方案

1.在响应头中设置允许跨域的 响应配置response.setHeader("Access-Control-Allow-Origin", "*");

2.使用HttpClient转发 效率低

3.使用jsonp处理,json最大的缺陷支持get请求不支持post请求

4.使用nginx配置浏览器访问的项目与接口项目的域名或者端口号码一致性。

   www.mayikt.com/vue 转发到vue项目

   www.mayikt.com/api 转发到接口项目

 5.可以直接在nginx中配置允许跨域的代码

     "Access-Control-Allow-Origin", "*"

6.网关中也可以配置类似与nginx允许跨域的代码

      "Access-Control-Allow-Origin", "*"

7.使用SpringBoot注解形式解决跨域问题@CrossOrigin

8.使用微服务网关也可以配置配置浏览器访问的项目与接口项目的域名或者端口号码一致性。

    第五种和第六种 都是在入口解决我们跨域的问题,其他方案比较麻烦,

14.网站动静分离架构模式

动静分离架构模式就是将静态资源和动态资源分开到不同的服务器部署。静态资源包含:css、img、视频、js的等。动态资源:api的接口 后端代码

15.DiscoveryClient的作用有那些

客户端接口,使用discoveryclient直接从我们注册中心上根据服务名称获取接口地址

16.分布式事务的解决

分布式产生的背景:传统的项目多数据源的情况下、rpc远程调用调用中发起方调用接口成功之后,发起方突然报错了,会产生分布式事务的问题。

分布式事务解决的核心思想:采用最终一致性和2pc或者3pc,短暂的数据延迟这是允许,但是最终数据一定要保持一致。

分布式事务的解决方案有那些:

Jta+ Atomic 只适合于传统的项目

基于MQ补偿解决分布式事务  rabbitmq

Rocketmq中自带事务消息

基于LCN解决分布式事务 原理 代理我们自己的数据源重写连接commit和rollback方法实现假关闭,传递事务的全局的id

基于Seata解决分布式事务问题 原理和Lcn是一致的;

LCN与Seata最大的区别在于回滚方式,LCN回滚是假关闭容易造成死锁,但是我们的seata采用undo_log日志逆向生成sql语句实现回滚

四、Java基础

1.JVM常用的调优参数

-Xmx 堆内存最大值  -Xmx2g

-Xms  堆内存最小值 -Xms2g

-Xmn  设置我们新生代的大小 默认是堆的1/3

-Xss   我们线程栈空间大小 -Xss256k

2.JVM运行时数据区域有哪几部分组成,各自作用

线程共享

堆:new出来的对象放在我们队中,(对象可能会有栈上分配(内存逃逸。。。))

方法区(元空间):静态变量,常量,class对象

线程独占

栈:  栈的内部有栈帧组成先进后出  栈帧(局部变量表  操作数栈  动态链接,返回地址)

PC寄存器(程序计数器):指向线程执行到哪里

本地方法栈: native修饰的方法

3.gc算法有哪些/gc收集器有哪些

分代算法/复制算法/标记清除/标记压缩/引用计数/可达性分析法

Serial :单线程收集  非单核服务STW比较长

ParNew: 多核情况下执行速度比较快(和cms一起使用)

Ps:  重点在吞吐量:(用户线程执行时间)/(用户线程执行时间+gc执行时间)

CMS: 大体分为4部分

初始标记  : 标记老年代直接与gc root 相关连的对象 (STW)

并发标记 :并发标记是标记gcroot下面的  相比第一个要时间长一点(和用户线程一起执行的)

重新标记:标记那些新生代可达却引用到老年代的对象的。 还有一些刚进来老年代的对象(STW)

并发清除:清除老年代没有做标记的对象(和用户线程一起执行的)

4.什么是Full GC?minor GC? major GC? STW?

minor GC:新生代回收的gc( STW)

major GC:老年代回收的GC

Full GC:minor GC+major GC

STW:stop the world

5.JVM中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代

正常流程:经过15次的ygc (复制算法) 老年代

我们大对象直接回进入老年代

非正常:

动态年龄: S区50%以上的对象年龄》S区的平均值就会进入老年代 (老年代不足发生fullgc)

空间分配担保:s0或者S放不下这些对象,进行一次空间分配担保(老年代剩余空间大于历代s区进阶的平均值)担保成功  失败发生fullgc,方法区不足也会发生fullgc

6.Java 中都有哪些引用类型

强引用:Object o = new Object();  gc不会去回收我们强应用的对象。

软引用:SoftReference   当我们堆内存占满时候就会回收这里面的对象(用来做缓存)

弱引用:WearkReference  只能存在于下一次gc之前:反生minorgc  majorgc就会被回收

虚引用:Object o = new Object();

O=null;  提醒我们的gc来回收这个对象

7.为什么要使用服务网关/过滤器与微服务网关的区别

堆内存分配:
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64
JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。
因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。
非堆内存分配:
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
-Xmn2G:设置年轻代大小为2G。

8.什么是双亲委派模型

当需要加载一个类的时候,子类加载器并不会马上去加载,而是依次去请求父类加载器加载,一直往上请求到最高类加载器:启动类加载器。当启动类加载器加载不了的时候,依次往下让子类加载器进行加载。当达到最底下的时候,如果还是加载不到该类,就会出现ClassNotFound的情况。

好处:保证了程序的安全性。例子:比如我们重新写了一个String类,加载的时候并不会去加载到我们自己写的String类,因为当请求上到最高层的时候,启动类加载器发现自己能够加载String类,因此就不会加载到我们自己写的String类了

9.类加载的过程

加载:将java源代码编译后的.class字节码文件以二进制流的方式加载进内存连接  

验证:验证加载进来的二进制流是否符合虚拟机的规范,不会危害的虚拟机自身的安全

准备:给类变量(静态变量)赋予初始值,基本数据/引用类型数

 解析:将字符串引用转换为直接引用

初始化:变量赋予初始值、执行静态语句块、执行构造函数等等。

10.Switch数据类型支持哪些

在JDK1.5之前,switch循环只支持byte short char int四种数据类型.

JDK1.7在switch循环中增加了String类型-------》但实际上String类型有一个hashCode算法,结果也是int类型.

而byte short char类型可以在不损失精度的情况下向上转型成int类型.所以总的来说,可以认为switch中只支持int.

11.Java有哪些锁?区别在哪?底层如何实现的?为什么非公平锁效率高

公平锁/非公平锁;可重入锁;独享锁/共享锁;互斥锁/读写锁;乐观锁/悲观锁;分段锁;偏向锁/轻量级锁/重量级锁;自旋锁

公平锁是指多个线程按照申请锁的顺序来获取锁。非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序

又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁

独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。

对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
对于Synchronized而言,当然是独享锁。

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是ReentrantLock
读写锁在Java中的具体实现就是ReadWriteLock

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
悲观锁在Java中的使用,就是利用各种锁。
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

偏向锁/轻量级锁/重量级锁这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。自旋锁在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,

12.线程池使用场景及其核心参数说明

corePoolSize(线程池的基本大小)

runnableTaskQueue(任务队列)

maximumPoolSize(线程池最大大小)

ThreadFactory:用于设置创建线程的工厂

RejectedExecutionHandler(饱和策略)

keepAliveTime(线程活动保持时间)

TimeUnit(线程活动保持时间的单位)

13.数据库事务四大特性

原子性(Atomicity)

事务的原子性指的是,事务中包含的程序作为数据库的逻辑工作单位,它所做的对数据修改操作要么全部执行,要么完全不执行。这种特性称为原子性。

一致性(Consistency)    

事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。这种特性称为事务的一致性。假如数据库的状态满足所有的完整性约束,就说该数据库是一致的。

分离性(亦称独立性Isolation)

分离性指并发的事务是相互隔离的。即一个事务内部的操作及正在操作的数据必须封锁起来,不被其它企图进行修改的事务看到。假如并发交叉执行的事务没有任何控制,操纵相同的共享对象的多个并发事务的执行可能引起异常情况。

持久性(Durability)

持久性意味着当系统或介质发生故障时,确保已提交事务的更新不能丢失。即一旦一个事务提交,DBMS保证它对数据库中数据的改变应该是永久性的,即对已提交事务的更新能恢复。持久性通过数据库备份和恢复来保证

14.List, Set, Map是否继承自Collection接口

List,Set是,Map不是

15.接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)

接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承实体类,但前提是实体类必须有明确的构造函数

16.构造器Constructor是否可被override

 构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading

17.JSP 9大内置对象详解

1.输出输入对象:request对象、response对象、out对象

2.通信控制对象:pageContext对象、session对象、application对象3.Servlet对象:page对象、config对象

4.错误处理对象:exception对象

18.八大基本数据类型

整型:byte, short, int, long

字符型:char

浮点型:float, double

布尔型:boolean

19.String、StringBuffer、StringBuilde

String : final修饰,String类的方法都是返回new String。即对String对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。

StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。

StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以new StringBuilder对象,调用StringBuilder对象的append、replace、delete等方法修改字符串

20.Queue 中 poll()和 remove()有什么区别

poll()和remove()都将移除并且返回对头,但是在poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常

四、Spring

1.容器的理解,IOC,AOP,事务

AOP:动态代理技术,基于Jdk实现InvocationHandler 底层使用反射技术Aop面向切面编程,在方法之前和之后实现处理 应用场景在于:日志打印、事务实现、安全等。

SpringAop通知链采用递归+责任链设计模式实现

隔离级别

1、ISOLOCATION_DEFAULT:  数据库默认级别

2、ISOLOCATION_READ_UNCOMMITTED: 允许读取未提交的读, 可能导致脏读,不可重复读,幻读

3、ISOLOCATION_READ_COMMITTED:  允许读取已提交的读,可能导致不可重复读,幻读

4、ISOLOCATION_REPEATABLE_READ : 不能能更新另一个事务修改单尚未提交(回滚)的数据,可能引起幻读

5、ISOLOCATION_SERIALIZABLE: 序列执行效率低

传播级别

1、PROPERGATION_MANDATORY:方法必须运行在一个事务中,不存在事务则抛出异常

2、PROPERGATION_NESTED:存在事务则运行在嵌套事务中,不存在则创建一个事务

3、PROPERGATION_NEVER: 当前方法不能运行在事务中,存在事务则抛出异常

4、PROPERGATION_NOT_SUPPORT: 当前存在事务则将其 挂起

5、PROPERGATION_REQUIRED: 不存在事务则创建一个事务

6、PROPERGATION_REQUIRES_NEW:  新建一个自己的事务,不论当前是否存在事务

7、PROPERGATION_SUPPORT: 存在事务则加入,不存在也可以

脏读。是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交(commit)到数据库中

不可重复读。是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据

幻读。指同一个事务内多次查询返回的结果集不一样(比如增加了或者减少了行记录)

Propagation.REQUIRED:

在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰;外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

Propagation.REQUIRES_NEW:

在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰;在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

Propagation.NESTED

在外围方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰;在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务

2.类加载的过程

加载:将java源代码编译后的.class字节码文件以二进制流的方式加载进内存连接  

验证:验证加载进来的二进制流是否符合虚拟机的规范,不会危害的虚拟机自身的安全

准备:给类变量(静态变量)赋予初始值,基本数据/引用类型数

 解析:将字符串引用转换为直接引用

初始化:变量赋予初始值、执行静态语句块、执行构造函数等等。

3.拦截器与过滤器区别

拦截器和过滤器都是基于Aop实现,能够对请求执行之前和之后实现拦截。

过滤器是基于Servlet容器实现,对Web请求之前和之后实现拦截

拦截器不需要依赖于Servlet、不仅可以实现Web请求还有其他方法拦截等

4.BeanFactory 和 ApplicationContext 有什么区别

BeanFactory:

是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

ApplicationContext:

应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;

1) 国际化(MessageSource)

2) 访问资源,如URL和文件(ResourceLoader)

3) 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层  

4) 消息发送、响应机制(ApplicationEventPublisher)

5) AOP(拦截器)

ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册

5.Spring Bean 的生命周期

实例化Bean——设置对象属性(依赖注入)——处理Aware接口——Spring检测当前对象是否实现XXXAware接口,如果有就讲XXXAware实例注入给Bean

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

2:如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。

  3:如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文

4:BeanPostProcessor:如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术。

5:InitializingBean 与 init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。

6:以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

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

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

6.拦Spring框架中有哪些不同类型的事件

  • 上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
  • 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
  • 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
  • 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
  • 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。

7.Spring支持的几种bean的作用域

  • singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
  • prototype:为每一个bean请求提供一个实例。
  • request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
  • session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
  • global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。

8. Spring如何处理线程并发问题 

  • 在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
  • ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
  • ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLoca

9.Spring的自动装配

  • 在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。
  • 在Spring框架xml配置中共有5种自动装配:
  • (1)no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
  • (2)byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。 
  • (3)byType:通过参数的数据类型进行自动装配。
  • (4)constructor:利用构造函数进行装配,并且构造函数参数通过byType进行装配。
  • (5)autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
  • 基于注解的方式:
  • 使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
  • 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
  • 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
  • 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
  • @Autowired可用于:构造函数、成员变量、Setter方法
  • 注:@Autowired和@Resource之间的区别
  • @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
  • @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

10.Spring通知有哪些类型

  • (1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
  • (2)返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。 
  • (3)抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。 
  • (4)后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 
  • (5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕

五、锁/多线程

1.你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?问题二:你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁

如果 Synchronized 明确指定了锁对象,比如 Synchronized(变量名)、Synchronized(this) 等,说明加解锁对象为该对象。

2.如果没有明确指定:

若 Synchronized 修饰的方法为非静态方法,表示此方法对应的对象为锁对象;

若 Synchronized 修饰的方法为静态方法,则表示此方法对应的类对象为锁对象。

注意,当一个对象被锁住时,对象里面所有用 Synchronized 修饰的方法都将产生堵塞,

而对象里非 Synchronized 修饰的方法可正常被调用,不受锁影响

2.为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

Synchronized 显然是一个悲观锁,因为它的并发策略是悲观的:

不管是否会产生竞争,任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。

随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略。先进行操作,如果没有其他线程征用数据,那操作就成功了;

如果共享数据有征用,产生了冲突,那就再进行其他的补偿措施。这种乐观的并发策略的许多实现不需要线程挂起,所以被称为非阻塞同步。

乐观锁的核心算法是 CAS(Compareand Swap,比较并交换),它涉及到三个操作数:内存值、预期值、新值。

当且仅当预期值和内存值相等时才将内存值修改为新值。这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,

否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。

CAS 具有原子性,它的原子性由 CPU 硬件指令实现保证,即使用 JNI 调用 Native 方法调用由 C++ 编写的硬件级别指令,

JDK 中提供了 Unsafe 类执行这些操作。

3.请谈谈 AQS 框架是怎么回事儿

AQS(AbstractQueuedSynchronizer 类)是一个用来构建锁和同步器的框架,各种 Lock 包中的锁(常用的有 ReentrantLock、

ReadWriteLock),以及其他如 Semaphore、CountDownLatch,甚至是早期的 FutureTask 等,都是基于 AQS 来构建。

1.AQS 在内部定义了一个 volatile int state 变量,表示同步状态:当线程调用 lock 方法时 ,如果 state=0,说明没有任何线程占有共享资源的锁,可以获得锁并将 state=1;如果 state=1,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。

2.AQS 通过 Node 内部类构成的一个双向链表结构的同步队列,来完成线程获取锁的排队工作,当有线程获取锁失败后,就被添加到队列末尾。

Node 类是对要访问同步代码的线程的封装,包含了线程本身及其状态叫 waitStatus(有五种不同 取值,分别表示是否被阻塞,是否等待唤

醒,是否已经被取消等),每个 Node 结点关联其 prev 结点和 next 结点,方便线程释放锁后快速唤醒下一个在等待的线程,是一个 FIFO 的过程。Node 类有两个常量,SHARED 和 EXCLUSIVE,分别代表共享模式和独占模式。所谓共享模式是一个锁允许多条线程同时操作(信号量 Semaphore 就是基于 AQS 的共享模式实现的),独占模式是同一个时间段只能有一个线程对共享资源进行操作,

多余的请求线程需要排队等待(如 ReentranLock)。

3.AQS 通过内部类 ConditionObject 构建等待队列(可有多个),当 Condition 调用 wait() 方法后,线程将会加入等待队列中,

而当 Condition 调用 signal() 方法后,线程将从等待队列转移动同步队列中进行锁竞争。

4.AQS 和 Condition 各自维护了不同的队列,在使用 Lock 和 Condition 的时候,其实就是两个队列的互相移动。

4.请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。

1)Lock是一个接口,synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized发生异常时,会自动释放线程占用的锁,故不会发生死锁现象。Lock发生异常,若没有主动释放,极有可能造成死锁,故需要在finally中调用unLock方法释放锁;
3)Lock可以让等待锁的线程响应中断,使用synchronized只会让等待的线程一直等待下去,不能响应中断
4)通过Lock可以知道有没有成功获取到锁,synchronized就不灵
5)Lock可以提高多个线程进行读操作的效率

5.如何让 Java 的线程彼此同步?你了解过哪些同步器?请分别介绍下

JUC 中的同步器三个主要的成员:CountDownLatch、CyclicBarrier 和 Semaphore,通过它们可以方便地实现很多线程之间协作的功能。 CountDownLatch 叫倒计数,允许一个或多个线程等待某些操作完成。看几个场景: 跑步比赛,裁判需要等到所有的运动员(“其他线程”)都跑到终点(达到目标),才能去算排名和颁奖。 模拟并发,我需要启动 100 个线程去同时访问某一个地址,我希望它们能同时并发,而不是一个一个的去执行。 用法:CountDownLatch 构造方法指明计数数量,被等待线程调用 countDown 将计数器减 1,等待线程使用 await 进行线程等待。

6.Java 中的线程池是如何实现的

 Java 中,所谓的线程池中的“线程”,其实是被抽象为了一个静态内部类 Worker,它基于 AQS 实现,存放在线程池

的HashSet<Worker> workers 成员变量中;而需要执行的任务则存放在成员变量 workQueue(BlockingQueue<Runnable> workQueue)中。这样,整个线程池实现的基本思想就是:从 workQueue 中不断取出需要执行的任务,放在 Workers 中进行处理。

7.线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?

 显然不是的。线程池默认初始化后不启动 Worker,等待有请求时才启动。

每当我们调用 execute() 方法添加一个任务时,线程池会做如下判断:

如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;

如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;

如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常 RejectExecutionException。

当一个线程完成任务时,它会从队列中取下一个任务来执行。 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断。

如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小

8.既然提到可以通过配置不同参数创建出不同的线程池,那么 Java 中默认实现好的线程池又有哪些呢?请比较它们的异同

 1. SingleThreadExecutor 线程池

这个线程池只有一个核心线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,

那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

corePoolSize:1,只有一个核心线程在工作。

maximumPoolSize:1。

keepAliveTime:0L。

workQueue:new LinkedBlockingQueue<Runnable>(),其缓冲队列是无界的。

2. FixedThreadPool 线程池

FixedThreadPool 是固定大小的线程池,只有核心线程。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。

线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

FixedThreadPool 多数针对一些很稳定很固定的正规并发线程,多用于服务器。

corePoolSize:nThreads

maximumPoolSize:nThreads

keepAliveTime:0L

workQueue:new LinkedBlockingQueue<Runnable>(),其缓冲队列是无界的。

3. CachedThreadPool 线程池

CachedThreadPool 是无界线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)线程,

当任务数增加时,此线程池又可以智能的添加新线程来处理任务。

线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。SynchronousQueue 是一个是缓冲区为 1 的阻塞队列。

缓存型池子通常用于执行一些生存期很短的异步型任务,因此在一些面向连接的 daemon 型 SERVER 中用得不多。

但对于生存期短的异步任务,它是 Executor 的首选。

corePoolSize:0

maximumPoolSize:Integer.MAX_VALUE

keepAliveTime:60L

workQueue:new SynchronousQueue<Runnable>(),一个是缓冲区为 1 的阻塞队列。

4. ScheduledThreadPool 线程池

ScheduledThreadPool:核心线程池固定,大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

创建一个周期性执行任务的线程池。如果闲置,非核心线程池会在 DEFAULT_KEEPALIVEMILLIS 时间内回收。

corePoolSize:corePoolSize

maximumPoolSize:Integer.MAX_VALUE

keepAliveTime:DEFAULT_KEEPALIVE_MILLIS

workQueue:new DelayedWorkQueue()

9.Java 中的线程池是如何实现的

 Java 中,所谓的线程池中的“线程”,其实是被抽象为了一个静态内部类 Worker,它基于 AQS 实现,存放在线程池

的HashSet<Worker> workers 成员变量中;而需要执行的任务则存放在成员变量 workQueue(BlockingQueue<Runnable> workQueue)中。这样,整个线程池实现的基本思想就是:从 workQueue 中不断取出需要执行的任务,放在 Workers 中进行处理。

10.volatile 有什么特点,为什么它能保证变量对所有线程的可见性

关键字 volatile 是 Java 虚拟机提供的最轻量级的同步机制。当一个变量被定义成 volatile 之后,具备两种特性:

1.保证此变量对所有线程的可见性。当一条线程修改了这个变量的值,新值对于其他线程是可以立即得知的。而普通变量做不到这一点。

2.禁止指令重排序优化。普通变量仅仅能保证在该方法执行过程中,得到正确结果,但是不保证程序代码的执行顺序。

Java 的内存模型定义了 8 种内存间操作:

lock 和 unlock

把一个变量标识为一条线程独占的状态。

把一个处于锁定状态的变量释放出来,释放之后的变量才能被其他线程锁定。

read 和 write

把一个变量值从主内存传输到线程的工作内存,以便 load。

把 store 操作从工作内存得到的变量的值,放入主内存的变量中。

load 和 store

把 read 操作从主内存得到的变量值放入工作内存的变量副本中。

把工作内存的变量值传送到主内存,以便 write。

use 和 assgin

把工作内存变量值传递给执行引擎。

将执行引擎值传递给工作内存变量值。

volatile 的实现基于这 8 种内存间操作,保证了一个线程对某个 volatile 变量的修改,一定会被另一个线程看见,即保证了可见性。

11.volatile 对比 Synchronized 的异同。

 Synchronized 既能保证可见性,又能保证原子性,而 volatile 只能保证可见性,无法保证原子性。

12.什么是 java 序列化?什么情况下需要序列化?

 序列化:将 Java 对象转换成字节流的过程。

反序列化:将字节流转换成 Java 对象的过程。

当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。

序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化

13.volatile 对比 Synchronized 的异同。

 Synchronized 既能保证可见性,又能保证原子性,而 volatile 只能保证可见性,无法保证原子性。

六、Mybatis

1.RowBounds 是一次性查询全部结果吗?为什么?

 RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出

2.mybatis 逻辑分页和物理分页的区别是什么

逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。

物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,比如需要大量的内存,对数据库查询压力较大等问题

3.mybatis 是否支持延迟加载?延迟加载的原理是什么

 MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true 即可。延迟加载的原理的是调用的时候触发加载,而不是在初始化的时候就加载信息。比如调用 a. getB(). getName(),这个时候发现 a. getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,然后再调用 a. setB(b),而这时候再调用 a. getB(). getName() 就有值了,这就是延迟加载的基本原理。。

4.mybatis 如何编写一个自定义插件

MyBatis 自定义插件针对 MyBatis 四大对象(Executor,StatementHandler,ParamentHandler,ResultSetHandler)进行拦截。MyBatis 插件要实现 Interceptor 接口,接口包含的方

七、MYSQL

索引

1:索引的优缺点


优点:
    提高数据检索的效率,降低数据的排序成本,降低CPU的消耗。
缺点:
    需要占用物理空间,增删改时间变长。


2:MySQL主要的几种索引类型


1.普通索引 2.唯一索引 3.主键索引 4.组合索引 5.全文索引。
普通索引: 是最基本的索引,它没有任何限制
唯一索引: 索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一
主键索引: 是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。
组合索引: 一个索引包含多个列,实际开发中推荐使用组合索引。
全文索引: 全文搜索的索引。FULLTEXT 用于搜索很长一篇文章的时候,效果最好。只能用于InnoDB或MyISAM表,只能为CHAR、VARCHAR、TEXT列创建


3:主键索引和唯一索引的区别


主键必唯一,但是唯一索引不一定是主键,一张表上只能有一个主键,但是可以有一个或多个唯一索引。


4:聚簇索引相对于非聚簇索引的区别?


聚簇索引:每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。
建立机制:
    如果表设置了主键,则主键就是聚簇索引
    如果表没有主键,则会默认第一个NOT NULL,且唯一(UNIQUE)的列作为聚簇索引
    以上都没有,则会默认创建一个隐藏的row_id作为聚簇索引
非聚簇索引:除聚簇索引外的索引,即非聚簇索引,普通索引也叫二级索引,普通索引叶子节点存储的是主键(聚簇索引)的值。


5:什么是回表查询?


先通过普通索引的值定位聚簇索引值,再通过聚簇索引的值定位行记录数据,需要扫描两次索引B+树,它的性能较扫一遍索引树更低。


6;什么是索引覆盖?


只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。


7:什么是最左匹配原则?


如果我们创建了(age, name)的组合索引,那么其实相当于创建了(age)、(age, name)两个索引,这被称为最佳左前缀特性。因此我们在创建组合索引时应该将最常用作限制条件的列放在最左边,依次递减。
最左前缀匹配原则:在MySQL建立联合索引时会遵守最左前缀匹配原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。
因此,如果查询条件是age或age和name联查时,是可以应用到索引的。如果查询条件是单独使用name,因为无法确定age的值,因此无法使用索引。


8:索引失效场景有哪些?


1:组合索引未使用最左前缀,例如组合索引(age,name),where name='张三'不会使用索引;
2:or会使索引失效。如果查询字段相同,也可以使用索引。例如where age=20 or age=30(索引生效),where age=20 or name=‘张三’(这里就算你age和name都单独建索引,还是一样失效);
3:如果列类型是字符串,不使用引号。例如where name=张三(索引失效),改成where name=‘张三’(索引有效);
4:like未使用最左前缀,where A like '%China';
5:在索引列上做任何操作计算、函数,会导致索引失效而转向全表扫描

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CSDN JavaScript 百炼成仙是一个指导JavaScript编程学习和进阶的专栏。JavaScript作为一门非常流行的脚本语言,在前端开发中担当着重要的角色。CSDN JavaScript 百炼成仙旨在通过教授JavaScript的各种技巧和知识,帮助学习者成为JavaScript高手,达到“仙”的境界。 在该专栏中,通过一系列的文章和教程,深入浅出地讲解了JavaScript的基础知识和重要概念,包括变量、数据类型、函数、对象等。此外,还介绍了JavaScript中的一些高级特性,如闭包、原型链、异步编程等。不仅如此,CSDN JavaScript 百炼成仙还探讨了JavaScript与其他前端技术的结合,如HTML和CSS,以及常见的JavaScript框架和库,如React和Vue等。 除了理论知识,该专栏还提供了大量的实际案例和代码示例,让学习者能够通过实践来巩固所学。通过这种实践和反复练习,学习者能够更好地理解与运用JavaScript编程。另外,该专栏还分享了一些实用的工具和资源,帮助学习者在实际项目中更高效地开发和调试JavaScript代码。 总的来说,CSDN JavaScript 百炼成仙是一个非常有价值的学习资源,它能够帮助想要掌握JavaScript编程的人,从初学者到进阶者,逐步提升自己的技能。通过学习这个专栏,人们能够更好地理解和利用JavaScript,解决实际问题,并为自己的职业发展打下扎实的基础。无论是前端开发工程师还是Web应用程序员,都可以从CSDN JavaScript 百炼成仙中受益匪浅。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值