1> 反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法,
这种JVM动态获取信息和调用对象方法的功能称之为反射.
2> 常用实例: 1.原生JDBC加载数据库驱动是标准的通过反射动态获取信息;
2.Spring框架的IOC原理底层运用工厂模式通过反射生成对象等;
3> 优点: 优点显而易见动态执行,动态调用,方便灵活.
缺点: 通过反射执行动作需要解析字节码,将内存中的对象解析出来,比直接执行代码创建内存对象慢,一定程度上会影响性能.
4> 解决方案: 1.关闭JDK安全检查提升反射速度; 2.对常用的实例对象,做缓存处理;
集合分为collection单列集合和map双列集合,单列集合又分为list和set接口,list分为arraylist,linkedlist,vector,ArrayList底层基于数组,查找快,增删慢。
LinkedList底层基于链表,增删快,查找慢,Vector是线程安全的,所以效率低下,set接口分为hashset,linkedhashset,hashset底层为哈希表,无序集合,不可重复。
LinkedHashSet底层为哈希表和链表结合,有序集合,不可重复。双列集合分为hashmap,linkedhashmap,hashtable,hashmap线程不安全,效率较高,支持key-value为null值,
linkedhashmap继承HashMap,为hashmap子类,保存插入顺序,hashtable 由数组+链表组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的,为线程安全,效率低,且key-value都不能为null
使用接口创建多线程的优势 : 使用接口创建可以继承其他类,多个线程可以共享同一个目标对象,所以适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想;
继承Thread类的优势 : 编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程
Runnable和Callable的区别:
Runnable接口中run()方法没有返回值,其方法只考虑执行run方法中的代码,Callable的call()方法可以通过FutureTask异步获取执行的结果返回值
1>新建状态: 创建线程对象, Thread t = new MyThread();
2>就绪状态: 处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行。
3>运行状态: 就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。
4>阻塞状态: 处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。
5>死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
CAS是一种基于锁的操作,属于乐观锁,乐观锁采取宽泛的态度,悲观锁是将资源锁住,等到之前获得锁的线程释放锁之后才允许下一个线程访问,相比较悲观锁来说CAS的性能比较高.
synchronized属于抢占式悲观锁,线程安全的标志字符,会引起线程阻塞。
CAS内部如何运行:操作包含三个操作数:内存位置,原值和新值,如果内存地址里值等于A,则更新B.此操作为无限循环执行,如果当前线程获取的地址里面的值被其他线程修改了,则当前线程要到下次循环才能修改回来.
缺点:
1>CAS容易造成ABA问题,可滚动版本号解决 ;
2>不能保证整个代码块的前后一致性 ,通过volatile关键字保证代码原子性;
3>因为无限循环,所以会一直占用cpu资源;
很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略.
volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。线程都会直接从内存中读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。
sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视,wait方法会释放锁需要调用notify方法才能唤醒线程.
单例模式:某个类的实例在多线程环境下只会被创建一次出来,懒汉式(线程不安全),饿汉式以及双检锁单例模式.
两个请求线程彼此阻塞,在申请锁的时候发生交叉闭环,造成死锁(A线程持有a锁没有释放去申请b锁,B线程持有b锁没有释放申请a锁);
java定义了专门负责输入输出的流对象,所有的输入流都是InputStream(字节流)或Reader(字符流)的子类,所有的输出流都是OutputStream或Writer的子类;因为java的字符是Unicode双字节编码,InputStream字节流处理字符文本不方便,所以引入一套字符流.
字符流处理的单元为2个字节的Unicode字符,操作字符,字符数组或字符串,字节流处理单元为1个字节,操作字节和字节数组.所以字符流是由java虚拟机将字节转化为2个字节的unicode字符为单位的字符而成,如果是音频文件,图片歌曲等用字节流避免数据丢失,如果是中文文本则用字符流好些;字符流以字符或字符数组的形式读写数据,只能读写二进制文件,字节流可以读写各种类型数据.
因为NIO是面向缓冲区操作,所以每次处理数据前都要判断缓冲区的数据是否完整或者是否读取完毕,因此结合NIO单独线程可以管理多个输入输出通道的特性可以在需要打开千万数量级的链接,且发送数据量少的场景下使用,比如通讯服务器,聊天;如果只有少量链接,且每次发送的数据量比较大,使用传统IO有更好的效果,可以结合数据读取响应时间和缓冲区检查数据时间来权衡选择.
对象序列化:将对象以二进制的形式保存到硬盘上;
反序列化:将二进制文件转化为对象读取.
将需要序化的类实现Serializable接口
1>原子性(Atomicity):指一个事务所有操作要么做完,要么不做,不存在中间环节;
2>一致性Consistency):事务在执行前后,数据保持一致;
3>隔离性(Isolation):隔离多个事务并发交叉执行防止数据不一致;
4>持久性(Durability):事务一旦结束,数据永久性修改;
1>脏读:a修改age为21,b查询到21后,a回滚事务;
2>幻读:a查询age>20的员工,b插入一条age=30的数据,a发现数据查询多了一条;
3>不可重复读:a查询age为21,b修改为22后,a二次查询age变为22;
4>丢失更新:a修改age为22,b修改age为23,a二次查询发现age变为23;
1>读未提交:最低隔离级别,指不同事务之间可以读取到未提交的数据;
2>读已提交:高于前者,指事务之间只能读取已提交的数据,可以避免脏读;
3>可重复读:指各事务可对同一字段多次,读取的结果一致,可以避免脏读,不可重复读;
4>可串行化:最高事务隔离级别,完全串行化执行事务,可以避免脏读,不可重复读,幻读;
JVM分为两个子系统类加载器,执行引擎和两个组件运行时数据区,本地方法库本地接口;
运行时数据区 : 由虚拟机栈,本地方法栈,程序计数器,以及java堆和方法区五部分组成,其中java堆和方法区是线程共享区,其他三个区域为线程隔离区
运行流程:首先编译器将代码转换为字节码class文件,之后通过类加载器将字节码加载到内存中的方法区内,但字节码文件只是JVM的一套指令规范,操作系统并不能直接识别,需要通过命令解析器执行引擎来解析后再交给CPU执行,解析过程中需要调用其他语言的本地库接口来实现.
java堆:是虚拟机内存最大的一块区域,被所有线程共享,所有的对象实例,都在堆内分配内存;
虚拟机栈:存储局部变量表,操作数栈,动态链接,方法出口等数据;
本地方法栈:同虚拟机栈,但本地方法栈为虚拟机调用Native方法服务;
方法区:存储已经被加载过得类信息,常量,静态变量和编译后的代码;
程序计数器:字节码的行号指示器,字节码解析器就是通过改变计数器的值来选取下一条需要执行的字节码指令;
1> 堆的物理地址分配对对象是不连续的,性能要慢,栈遵循后进先出原则,物理地址分配是连续的,性能快;
2> 堆内存分配是在运行期确认的,比栈大且不固定,栈内存在编译期就确认,固定大小;
3> 堆存放对象实例,更关注数据的存储,栈存放局部变量,操作数栈,返回信息,更关注程序执行;
4> 堆内对象全线程共享可见,栈线程私有;
队列和栈的区别: 队列是后面进前面出,栈进出都是在栈顶,队列采取先进先出原则,栈是后进先出方式.
当在main方法中创建对象时,JVM会先去方法区下找有没有所创建对象的类存在,有就创建,没有就把该类加载到方法区;在创建类的对象时,首先去堆内存中开辟一块空间,开辟完空间后分配该空间,当空间分配完成后,加载对象中所有的非静态成员变量到该空间下,所有的非静态变量加载完成后,对所有的非静态变量进行初始化,初始化完成后,调用相应的构造方法到栈中,在栈中执行构造函数时,先执行隐式,再执行构造方法中的代码。
堆内存溢出:
设置的jvm内存太小,对象所需内存太大,创建对象时分配空间,就会抛出堆内存泄漏异常;
应用程序自身处理存在一定限额,当数据激增或者超过预期阈值时,会触发堆空间异常;
解决方案: 调整jvm堆内存大小,参数Xmx,Xms;避免较大对象内存申请,比如文件上传,大批量数据数据库获取等;提高请求速度,优化gc效率;
堆内存泄漏:
Java中的内存泄漏是一些对象不再被应用程序使用但垃圾收集无法识别的情况。因此,这些未使用的对象仍然在Java堆空间中无限期地存在。不停的堆积最终会触发内存泄漏异常
常见的造成泄漏的原因: 单例对象持有一个不再被使用对象的引用;非静态内部类里创建一个静态实例;线程的匿名内部类持有Activity隐式引用;资源未关闭;静态集合容器不清空;
解决方案:
对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,将内部类改为静态内部类,静态内部类中使用弱引用来引用外部类的成员变量;
保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期
对于不再需要使用的对象,显示的将其赋值为null
栈内存溢出:
当一个线程执行一个Java方法时,JVM将创建一个新的栈帧并且把它push到栈顶。此时新的栈帧就变成了当前栈帧,方法执行时,使用栈帧来存储参数、局部变量、中间指令以及其他数据,当方法递归调用自身时,每次递归便会创建一个新的栈帧,随着无数次调用,栈中的内存越来越少,就会造成栈内存溢出.
解决方案: 如果程序中确实有递归调用,出现栈溢出时,可以调高-Xss大小,就可以解决栈内存溢出的问题了。递归调用防止形成死循环,否则就会出现栈内存溢出。
在java中,程序员不需要刻意释放一个对象的内存,由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
新生代(Eden区(对象出生区),Survivor from区(一次GC幸存者),Survivor to区(二次GC幸存者):
新生代通常用来存放新创建的对象,一般占堆内存的1/3,因为频繁创建对象,所以会频繁触发GC进行回收;
老年代:生命周期较长的对象,老年代比较稳定,不会经常进行GC操作;
永久代:永久保存区域,一般存放Class和元数据,不会进行GC操作;
因为如果没有,Eden对区每进行一次GC,就会把幸存者送到老年代里,老年代被填满后就会触发Full GC,消耗的时间要比Eden的GC时间长很多,Survivor存在的意义就是减少幸存者送到老年代的对象数量,从而减少Full GC的发生。两个Survivor最大的好处是解决碎片化,Eden区经历一次GC后就可以把存活的对象全部移动到第一块Survivor中,然后可以清空Eden区。当Eden再次满载后,再次触发GC,Eden区存活的对象和S0中的存活对象被复制送到第二块Survivor1中,这样保证了S1中来自S0和Eden两部分存活对象占用连续的内存空间,从而避免了碎片化。
CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
Spring是一个轻量级的企业应用框架,它的核心思想为IOC和AOP.
IOC为控制反转,意为通过Spring框架来创建对象,底层通过工厂模式使用反射的机制来创建对象,且支持加载服务时的饿汉式初始化和懒加载,通过注解注入把代码量降到最低,可 以松散耦合度;
AOP为切面类编程,在框架内执行某些操作时,可以将要做的一些交叉逻辑或校验规则封装成一个切面在操作触发之前或之后先执行,这种操作节点事件的特性称之为切面类编程。
AOP代理模式有两种:JDK动态代理和CJLIB代理;
触发增强有五种通知:前置,后置,异常,返回,环绕通知,包含整条业务线所有需要aop的事件间断;
织入: 把切面应用到目标对象并创建新的代理对象的过程,AspectJ在编译期内织入切面的,SpringAOP在运行期内织入;
JDK动态代理和CGLIB动态代理的区别:
使用注解前需要先在Spring配置里注册需要注入的类,在启动spring ioc时,容器自动装载一个AutowiredAnnotationBeanPostProcesser后置处理器,当容器扫描到@Autowired,@Resource,@Inject时,会在IOC容器自动查找需要的bean,如果查不到,则会抛异常,如果查到两个,则按照名称二次查找,如果只查到一个则指定给@Autowored指定数据并装配该对象属性
Spring Beans:
Spring Beans是形成spring应用主干的对象,被IOC容器初始化,装配和管理,这些beans通过容器中配置的元数据创建。
Spring bean的五种作用域:
1>singleton:bean在每个IOC容器中只有一个实例;
2>prototype:一个bean可以定义多个实例;
3>request:每次http请求都会创建一个bean对象,该作用域只在web的SpringApplicationContext下有效;
4>session:在一个http session中,一个bean对象只对应一个实例对象,作用域也只在web的SpringApplicationContext下有效
5>global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
此处应用可以通过注解方式,修改作用域为prototype将Controller从单例转为多例(@Scope(“prototype”))。
Spring bean的生命周期: (实例化,填充属性,初始化,销毁)
实例化: Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
填充属性: Bean实例化后对将Bean的引入和值注入到Bean的属性中
初始化: 如果实现了Aware接口,则通过不同类型的Aware接口取到Spring容器的资源;
如果实现了BeanPostProcessor接口,则回调该接口的postProcessBeforeInitialization和postPrrcessAfterInitialization()方法;
如果配置了init-method方法,则会执行该方法;
4. 销毁:如果Bean实现了DisposableBean接口,则调用destory接口方法;
如果配置了destory-method声明销毁方法,则会执行该方法;
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以做Spring容器,ApplicationContext是BeanFactory的子接口
依赖关系:BeanFactory作为spring最底层的接口,负责定义各种bean对象,bean配置读取,bean属性加载,实例化,控制生命周期,维护各bean对象依赖关系等;
ApplicationContext作为BeanFactory的子接口,拥有BeanFactory所有功能外,还提供了更完整的框架功能:
1>继承了MessageSource,支持国际化;
2>统一的资源访问方式;
3>提供监听器中注册bean的事件;
4>可以同时加载多个配置文件;
5>载入多个上下文;
加载方式:BeanFactory采用延迟加载的形式来注入bean,只有在使用到某个bean的时候,才对该Bean进行实例化,ApplicationContext则是在容器启动时,一次性创建所有Bean,这样在容器启动阶段就可以发现有哪些bean创建失败.当程序对象比较多时,ApplicationContext启动就比较慢
创建方式 : BeanFactory通过编程方式创建,ApplicationContext还可以以声明方式创建,比如ContextLoader
注册方式 : BeanFactory需要手动注册,ApplicationContext是自动注册
Spring事务:
SpringMVC:
SpringMVC是一个基于Java实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把模型视图控制器分离,将web层解耦,简化开发,方便使用.
模型(Model )封装了应用程序的数据和一般他们会组成的POJO。
视图(View)是负责呈现模型数据和一般它生成的HTML输出,客户端的浏览器能够解释。
控制器(Controller )负责处理用户的请求,并建立适当的模型,并把它传递给视图渲染。
MVC的运行流程:
SpringBoot是Spring开源组织的子项目,是Spring一站式解决方案,他简化了Spring的配置,提供了各种启动器
在SpringBoot的启动类有一个注解@EnableAutoConfigration里有一个AutoCOnfigurationImportSelector.class实现了ImportSelector接口,其中接口里给容器导入了META-INF下的spring.factories定义的自动配置类,通过这个配置类结合对应的properties配置文件进行对象自动装配。
消息队列相比于传统的串行,并行方式,其异步处理的方式提高了系统吞吐量,系统间通过队列进行消息通信,不用关心系统的其他处理,起到松散耦合的效果.还可以通过消息队列长度来控制请求量,可以缓解短时间内高并发问题,达到流量削峰的效果,可以解决大批量日志传输问题,一般消息队列都内置了高效的通信机制,所以可以应用于单纯点对点消息通讯.
如何保证RabbitMQ的顺序性: 拆分成多个队列,每个队列一个消费者
消息如何分发:若队列中至少有一个消费者订阅,消息将以循环的方式发送,通过路由可以实现多消费的功能
消息基于什么传输:RabbitMQ使用信道方式传输数据,信道是建立在真实TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制
如何保证消息消费时的幂等性:
正常情况下,消费者在消费完毕消费后,会发送一个确认消息给队列,队列知道被消费后,就会将消息从队列中删除
因为网络传输等原因,确认消息没有传给队列,导致队列没有删除消息,将消息再次发送给了消费者
解决方案:在写入消息队列是,对数据做唯一标识,消费者消费消息时,根据唯一标识和消费方的日志记录判断是否消费过
如何保证消息正确发送到RabbitMQ,如何确保接收方消费了消费:
发送方确认机制:信道设置为发送方确认模式(confirm),所有在信道发布的消息都会被指派一个唯一ID,消息被消费者接到或者被写入磁盘后,信道会发送一个确认信息给生产者.如果发生错误导致消息丢失,也会发送一条未确认消息
接收方确认机制:消费者接受每条消息都必须进行确认,只有确认了,MQ才会把消息从队列中删除.RabbitMQ只通过和消费者的连接中断确认是否需要重新发送消息,只要不中断就不会再次发送
如何保证RabbitMQ消息的可靠性:
如何保证高可用性:集群
普通集群模式:在多台机器上启动多个MQ实例,每台机器启动一个,你所创建的队列,只会放在一个MQ实例上,但每个实例都同步队列的元数据,所有的队列里消息是互通的,你消费的时候,实际如果连接了另外一个实例,那么那个实例会从队列所在实例上拉取数据过来,以此来提高吞吐量,让集群中多个节点来服务队列的读写操作
镜像集群模式:和普通集群模式不一样,镜像模式下,你创建的队列,无论元数据还是队列里的消息都会存放在多个实例上,也就是每个MQ节点都会有一个完整的队列镜像,每次写消息到队列的时候,都会自动把消息同步到多个实例的队列上,在MQ后台可以增加镜像集群模式的策略,可以配置数据同步到所有节点还是指定节点,在某一台机器宕机时,消费者可以去其他节点消费数据,但这样配置,消息需要同步到所有机器,对性能会大大打折,加大了网络带宽压力和消耗
mysql的锁: 表级锁(加锁快,不会死锁,锁冲突概率高,并发低),行级锁(加锁慢,会死锁,锁冲突概率低,并发度高)
数据库三大范式:
mysql数据库中MyISAM和InnoDB的区别:
为什么InnoDB必须要有主键,且推荐使用整型自增主键
数据库优化:
SQL优化:
描述索引,主键,唯一索引,联合索引的区别和对性能的影响:
SQL注入漏洞和预防:
程序开发时,由于不注意规范sql和特殊字符过滤,造成请求方通过全局变量POST和GET提交一些sql执行操作
预防:
Mysql的执行流程:
设计模式是一套被反复使用,多数人知晓,经过分类编目的,代码设计经验的总结,使用设计模式为了可重用代码,让代码更简洁,可靠.
六大原则: 开放封闭原则(扩展实体实现需求),里氏代换原则(子类实现),依赖倒转原则(接口编程)
接口隔离原则(多隔离接口),迪米特法则(减少依赖解耦),单一职责原则(独立职能)
简单介绍几种设计模式:
1.单例模式: 保证一个类只有一个实例,提供一个访问全局的访问点,主要使用懒汉和饿汉式
饿汉式: 类初始化时会立即加载对象,线程安全,调用效率高
懒汉式: 类初始化时,不会初始化对象,只有需要使用时才会创建对象,具备懒加载功能
//饿汉式
public class Demo1 {
//创建静态私有对象
private static Demo1 demo1 = new Demo1();
//创建私有构造方法
private Demo1() {
System.out.println("私有Demo1构造参数初始化");
}
//创建可调用的创建实例方法
public static Demo1 getInstance() {
return demo1;
}
}
//懒汉式
public class Demo2 {
//创建静态私有不初始化对象
private static Demo2 demo2;
//创建私有构造方法
private Demo2() {
System.out.println("私有Demo2构造参数初始化");
}
//创建静态线程安全的创建实例方法调用
public synchronized static Demo2 getInstance() {
if (demo2 == null) {
demo2 = new Demo2();
}
return demo2;
}
}
/**
* 双检锁
* 懒汉式升级版
*/
public class Demo3 {
private static volatile Demo3 instance;
private Demo3() {}
public static Demo3getInstance() {
//首先判断是否为空
if(instance==null) {
//可能多个线程同时进入到这一步进行阻塞等待
synchronized(Demo3.class) {
//第一个线程拿到锁,判断不为空进入下一步
if(instance==null) {
/**
* 由于编译器的优化、JVM的优化、操作系统处理器的优化,可能会导致指令重排
(happen-before规则下的指令重排,执行结果不变,指令顺序优化排列)
* new Demo3()这条语句大致会有这三个步骤:
* 1.在堆中开辟对象所需空间,分配内存地址
* 2.根据类加载的初始化顺序进行初始化
* 3.将内存地址返回给栈中的引用变量
* 但是由于指令重排的出现,这三条指令执行顺序会被打乱,
可能导致3的顺序和2调换,在instance变量分配了地址,但对象未初始化,则if判
断中结果为true,会造成多次初始化对象问题
* 解决方法在instance变量上加volatile,可以禁止变量指令重排,达到线程安全
*/
instance = new Demo3();
}
}
}
return instance;
}
}
单例模式懒汉式和饿汉式的区别:
2.工厂模式: 工厂模式提供了一种创建对象的方式,在工厂中创建对象不会暴露创建逻辑,使用统一接口创建对象,实现创建者和调用者分离.工厂模式是常用的实例化对象模式,通过工厂方法代替new的操作,有利于降低耦合度
3.代理模式: 通过代理控制对象的访问,可以在调用方法之前,之后添加新的功能,和spring的切面编程相似
4.策略模式:定义了一系列算法或逻辑,将每个算法逻辑操作封装起来,使他们可以相互替换,可以简化if/else应用
使用策略模式,其中的主算法逻辑可以自由切换,避免了大量的重复条件判断,扩展性良好
5.观察者模式:一种行为性模型,又叫发布订阅模式,定义了对象一种一对多的依赖关系,当一个对象改变状态,所有依赖于他的对象都会得到通知并自动更新
打开一个网页,计算机做了哪些工作:
http请求方式: get post head put patch delete options
http和https的区别:
get和post的区别:
Session和Cookie的区别:
Http的keep-alive有什么用: 早期的每次HTTP请求都要创建一个连接,频繁的创建会消耗资源和时间,keep-alive机制就是重用连接机制,告诉对方这个响应完成后不要关闭,下次还用这个请求交流,也叫长连接 (Connection: keep-alive)
为什么要三次握手和四次挥手:
三次握手是一个通信连接的过程:请求-->响应-->响应的响应,为什么一定要是三次握手,不是两次或者四次,实际服务端和客户端多次握手为了保证连接的可靠性,客户端请求,服务端同意了,则两次握手完成可以进行通信了.但不排除客户端第一次请求会发生丢包或者网络中断的现象,导致客户端多次发送请求,这样如果仅两次握手服务端会建立多个连接,为了避免这种问题发生,客户端要对服务端的响应再做一次响应,告诉服务端,我收到响应了,别的请求不建立连接了,就是三次握手.
四次挥手是通信关闭的过程:因为TCP是全双工通信的,双方发送和接收都是交叉的过程,所以关闭连接需要关闭两个发送通道和两个接收通道
第一次挥手: 客户端发送断开连接请求后进入FIN_WAIT_1状态,表示客户端不再发送数据,但可以接收数据
第二次挥手: 服务端收到请求后,回复确认进入CLOST_WAIT状态,当客户端收到回复后,进入FIN_WAIT_2状态
第三次挥手: 服务端发送完数据后,告诉客户端我发完了,关闭连接并进入LAST_ACK状态
第四次挥手: 客户端收到响应后,进入TIME_WAIT时间等待状态并回复服务端收到了,服务端就会关闭连接进入CLOSED状态.此时整个TCP连接在客户端还没有完全释放,要经过一个2MSL叫做最长报文段寿命的时间,才会进入CLOSED状态
原因:为了防止网络问题导致服务器没有收到客户端确认关闭的消息,服务端就会一直发送断开连接的请求,客户端会在发送回复后等待2MSL时间,如果在该时间内再次收到服务端断开连接的请求,则会重发确认关闭的请求并再次等待2MSL
TCP和UDP的特点和区别:
final: 用于修饰类,属性和方法,呗修饰的类不能被继承,被修饰的方法不能被重写,被修饰的变量不能改变,如果修饰引用,则引用不可变,引用指向的内容可变
finally:一般用在try-catch代码块中,表示不管是否出现异常,都会执行代码,常用以关闭资源
finalize:属于Object类的方法,一般由垃圾回收器调用,当我们手动调用System.gc()时,由gc调用finalize()回收垃圾
this:指向对象本身的一个指针,可以用来指向当前对象本身,区分形参和成员变量重名,引用本类的构造函数
super:指向父类对象的一个指针,用法和this相同,但本质上this是指向本对象的指针,super是java关键字
static:static主要用来创建独立于对象实例的域变量或方法,这样无需创建对象也能调用其方法或使用属性值,它还可以用来生成静态代码块,将只需要初始化一次的操作在类加载初期加载,可以优化程序性能,因为static是类的所有实例对象共享,所以如果一个类里有很多实例都使用了一个成员变量,则可以定义为静态变量.在使用时要注意的是,静态只能访问静态,但非静态即可以访问静态也可以访问非静态
成员变量:方法外部,类内部定义的变量,存储在对内存中,对象消失后销毁
局部变量:类中方法里的变量,存储在栈内存中,方法调用完后会销毁
实例变量:每次创建对象,都会为对象实例分会内存,实例变量属于实例对象的
静态变量:static变量,被类内所有对象共享,只会在类初次加载时被初始化
静态内部类:静态内部类可以访问外部类所有的静态变量,不可访问非静态变量,通过new外部类.静态内部类创建
成员内部类:可以访问所有静态和非静态的变量和方法,通过new外部类.new内部类创建
局部内部类:定义在非静态方法内的局部类,可以访问所有外部类的变量和方法,直接new
匿名内部类:没有名字的内部类,必须继承一个类或实现一个接口,内部类里不能定义静态成员变量和静态方法,当内部类需要访问局部变量时,变量需要声明为final,因为局部变量时存储在栈中,方法执行结束后,非final的变量会被销毁,那么内部类中变量的引用还会存在,就会出错
Dubbo调用过程:
服务容器启动,加载运行服务提供者; 服务提供者启动后向注册中心注册服务; 服务消费者启动时向注册中心订阅需要的服务; 注册中心将提供者的ip和端口地址返回给消费者,如果有更新,会基于长连接推送给消费者; 消费者从提供的地址里,基于负责均衡,选一台提供者服务调用; 消费方和服务提供方调用次数和时间,定时发送给数据监控中心
dubbo负载均衡策略:
缓存过期:
Redis的数据删除有定时删除、惰性删除和主动删除三种方式, Redis目前采用惰性删除+主动删除的方式。
淘汰策略:
在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,server.lrulock 的值是根据 server.unixtime 计算出来的。
另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。可以想象的是,每一次访问数据的时候,会更新 redisObject.lru。
LRU 数据淘汰机制:在数据集中随机挑选几个键值对,取出其中 lru 最大的键值对淘汰。不可能遍历key 用当前时间-最近访问 越大,说明访问间隔时间越长
volatile-lru
从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
allkeys-lru
从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
此处感谢作者,整理内容丰富有深度 : 设计模式面试题 (史上最全、持续更新、吐血推荐) - 疯狂创客圈 - 博客园