Hashcode与equals区别与关系(都继承自Object类):
Hashcode:用来获取hash值 返回的是一个int整数 这个hash值是用来确定该对象赛hash表中索引的位置
Equeals:如果重写了equals对象 对比的是内容是否相同 如果没有重写 则比较两个值的地址值是否相同
equals()相等的两个对象,hashcode()一定相等;
反过来:hashcode()不等,一定能推出equals()也不等;
hashcode()相等,equals()可能相等,也可能不等
Java中线程池有哪些:
1.newCachedThreadPool创建一个可缓存线程池程 如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
2.newFixedThreadPool 创建一个定长线程池 每当提交一个任务就创建一个工作线程,当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭了,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列(没有大小限制)中 由于该线程池只有核心线程并且不会被回收 所以可以更加迅速的响应外部请求
3.newScheduledThreadPool 创建一个定长线程池 核心线程数量是固定的 而非核心线程数量无限制 当非线程池闲置时会立即被回收 它可安排给定延迟后运行命令或者定期地执行。这类线程池主要用于执行定时任务和具有固定周期的重复任务。
4.newSingleThreadExecutor 创建一个单线程化的线程池 只有一个核心线程 以无界队列方式来执行该线程,这使得这些任务之间不需要处理线程同步的问题,它确保所有的任务都在同一个线程中按顺序中执行,并且可以在任意给定的时间不会有多个线程是活动的。
Springsecurity中用一个方法一步就可以将自定义的过滤器引入:
只需要实现attemptAuthentication方法即可
Jdk1.8后出现的迭代器:
Spliterator:java1.8引入的一种并行遍历机制
Rabbitmq死信队列:(当消息成为死信后 可以被重新发布到另一个交换机(exchange)这个交换机就是死信队列)
消息变成死信的情况:
消息被拒绝(basic.reject / basic.nack),并且requeue = false
消息TTL过期
队列达到最大长度
死信处理过程
DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
可以监听这个队列中的消息做相应的处理。
Freemarker缓存是怎么处理的:
FreeMarker 的缓存处理主要用于模版文件的缓存,一般来讲,模版文件改动不会很频繁,在一个流量非常大的网站中,如果频繁的读取模版文件对系统的负担还是很重的,因此 FreeMarker 通过将模版文件的内容进行缓存,来降低模版文件读取的频次,降低系统的负载。
当处理某个模版时,FreeMarker直接从缓存中返回对应的 Template 对象,并有一个默认的机制来保证该模版对象是跟模版文件同步的。如果使用的时候 FreemarkerServlet 时,有一个配置项template_update_delay用来指定更新模版文件的间隔时间,相当于多长时间检测一下是否有必要重新加载模版文件,0 表示每次都重新加载,否则为多少毫秒钟检测一下模版是否更改。
FreeMarker定义了一个统一的缓存处理接口CacheStorage,默认的实现是 MruCacheStorage 最近最少使用的缓存策略。一般情况下,很少需要对缓存进行扩展处理。您可以通过下面的代码指定最大缓存的模版数:
1 cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250))
其中第一个参数是最大的强引用对象数,第二个为最大的弱引用对象数。这两个值FreeMarker默认的是0和 Integer.MAX_VALUE,表明模版缓存数是无限的。
当业务执行中 消费方去下载文件过程失败了怎么办 比如我手动在GridFS把文件删了,前端已经收到生产方成功得消息了
解决:
ACK机制:
由于通信过程的不可靠性,传输的数据不可避免的会出现丢失、延迟、错误、重复等各种状况,TCP协议为解决这些问题设计了一系列机制。这个机制的核心,就是发送方向接收方发送数据后,接收方要向发送方发送ACK(回执信息)。如果发送方没接收到正确的ACK,就会重新发送数据直到接收到正确ACK为止。比如:发送方发送的数据序号是seq,那么接收方会发送seq + 1作为ACK,这样发送方就知道接下来要发送序号为seq + 1的数据给接收方了 如下图:
应用场景:
(1)数据丢失或延迟。发送方发送数据seq时会起一个定时器,如果在指定时间内没有接收到ACK seq + 1,就把数据seq再发一次。
(2)数据乱序。接收方上一个收到的正确数据是seq + 4,它返回seq + 5作为ACK。这时候它收到了seq + 7,因为顺序错了,所以接收方会再次返回seq + 5给发送方。
(3)数据错误。每一个TCP数据都会带着数据的校验和。接收方收到数据seq + 3以后会先对校验和进行验证。如果结果不对,则发送ACK seq + 3,让发送方重新发送数据。
(4)数据重复。接收方直接丢弃重复的数据即可。
懒汉模式和饿汉模式的区别
懒汉模式:在类加载的时候不被初始化。
饿汉模式:在类加载时就完成了初始化,但是加载比较慢,获取对象比较快。
饿汉模式是线程安全的,在类创建好一个静态对象提供给系统使用,懒汉模式在创建对象时不加上synchronized,会导致对象的访问不是线程安全的
多线程下如何实现单例模式:
由上可知 饿汉模式 类加载时就完成初始化 提前创建好实例 影响加载效率 线程安全
懒汉模式则 类加载时不需要提前创建好实例,加载效率较快,需要安全处理,否则线程不安全
懒汉模式安全处理方案:
(1)在方法区上加锁 但是运行效率低下 下一个线程调用同步方法必须等待上一个释放锁执行完毕
(2)同步代码快加锁 但是将全部代码锁上了 所以跟上面一样 效率低下
(3)在需要同步执行的代码块加锁,不影响执行效率且线程安全。
(4)双检查锁Double Check Locking
这里在声明变量时使用了volatile关键字来保证其线程间的可见性(还可以防止指令重排序:只有Happens-Before才会出现这样的指令重排问题 所以解决:可以在每个volatile关键字写操作前加一个StoreStore屏障;在每个volatile写操作后面插入一个StoreLoad屏障;在每个volatile读操作后面插入一个LoadLoad屏障;在每个volatile读操作后面插入一个LoadStore屏障);在同步代码块中使用二次检查,以保证其不被重复实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性
Volatile关键字:
(4)静态内部类实现:
(5)静态代码块实现:
(6)内部枚举类实现:
Spring
是一个轻量级IOC和AOP容器框架 用于简化企业级应用程序开发 常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置
优点:
(1)入侵式设计 代码污染低
(2)DI机制将对象间的依赖管理交给框架处理 减轻组件耦合性
(3)提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用
(4)对于主流应用框架提供集成支持
Spring Aop的理解:
OOP面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用
AOP,一般称为面向切面,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理
Aop实现:
关键在于 代理模式 AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
Spring的IoC理解:
(1)IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
(2)最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
解决线程安全问题:
(1)同步代码块:
在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
(2)同步方法:
在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法中的锁对象是 this
静态同步方法: 在方法声明上加上static synchronized
静态同步方法中的锁对象是 类名.class
(3)同步锁
Lock接口提供了与synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
补充:
异步锁:当多个jvm或者服务器 操作同一个变量时 会出现线程安全问题 需要异步锁进行
处理:
(1)数据库的乐观锁 悲观锁 不推荐 容易造成锁表 死锁
(2)Redis分布式锁 设置一个flag标识 当一个服务拿到锁之后立即将标识设置为false 用完后释放锁 并且将标识设置为true
Springmvc执行流程:
Springboot自动配置原理:
SpringBoot启动的时候加载启动类,@EnableAutoConfiguration注解开启了自动配置功能 @EnableAutoConfiguration的作用是利用AutoConfigurationImportSelector给容器中导入一些组件查看selectImports()方法的内容该方法中的 getCandidateConfigurations()方法获取候选的配置 内部的loadFactoryNames()方法扫描所有jar包类路径下 META‐INF/spring.factories把扫描到的这些文件的内容包装成properties对象 从properties中获取到EnableAutoConfiguration.class类名对应的值,然后把他们添加在容器中(整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到容器中)
每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;每一个自动配置类进行自动配置功能
以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理:
根据@Conditional指定的条件判断,决定这个配置类是否生效 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;所有在配置文件中能配置的属性都是在xxxxProperties类中封装者;配置文件能配置什么就可以参照某个功能对应的这个属性类
类加载:(类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构)
类加载器有哪些:(类加载器采用双亲委派模型)
类加载器的三大特性:委托性、可见性、单一性
1.启动类加载器:这个类加载器负责放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库。用户无法直接使用。不是ClassLoader的子类 其他两个是
2.扩展类加载器:这个类加载器由sun.misc.LauncherKaTeX parse error: Undefined control sequence: \lib at position 32: …。它负责<JAVA_HOME>\̲l̲i̲b̲\ext目录中的,或者被jav…AppClassLoader实现。是ClassLoader中getSystemClassLoader()方法的返回值。它负责用户路径(ClassPath)所指定的类库。用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。
4.自定义加载器:用户自己定义的类加载器。
1:类的加载过程:
当使用Java命令运行java程序时 此时jvm(虚拟机)启动 并去方法区下寻找java命令后面跟的类是否存在 若不存在 则将这个类加载到方法区下
当类加载到方法区后 会分为两部分执行:先加载非静态内容到非静态方法区域 再加载静态内容到静态方法区域内 当非静态内容加载完后 就会加载所有的静态内容到方法区下的静态区域内
当所有静态内容加载完后 对所有的静态成员变量进行默认初始化 默认初始化完成后 对所有静态成员变量进行显示初始化
完成后 jvm会自动执行静态代码块 (静态代码块在栈中执行)(当有多个静态代码块时 执行顺序为你书写代码的先后顺序)所有代码块执行完毕 也就证明类加载完成
2.对象的创建过程:
3.当使用java命令运行java程序时 此时jvm启动 并去方法区下寻找有没有所创建对象的类存在 有则创建对象 没有就将这个类加载到方法区
在创建类的对象时 首先会去堆内存中开辟一块空间 开辟后分配空间(指定地址) 空间分配完后 会加载该对象中所有的非静态成员变量进行默认初始化 默认初始化后 调用相应构造方法到栈内存中 在栈中执行构造函数时,先执行隐式,再执行构造方法中书写的代码构造方法中的隐式:
第一步:执行super()语句 调用父类的没有参数的构造方法
第二步:对所有的非静态成员变量进行显式初始化(在定义成员变量时后面有赋值)
第三步:显式初始化完成之后,执行构造代码块
Ps.第二步第三步按照书写顺序执行
最后执行构造方法中书写的代码
当整个构造方法全部执行完,此对象创建完成,并把堆内存中分配的空间地址赋给对象名(此时对象名就指向了该空间)