无论是开发、测试、运维,每一个技术人员内心都有一个成为技术大牛的梦,毕竟“梦想老是要有的,万一实现了呢”!正是对技术梦的追求,促使咱们不断地努力和提高本身。java
今天分享Java重点面试知识 :linux
多线程(线程状态、线程并发,Synchronized与Lock的区别和底层原理,经常使用的锁及其使用场景和原理,面试
volatile和ThreadLocal解决了什么问题,CAS在Java中的实现算法
线程池原理和实现,阻塞队列和线程安全队列,sql
线程间通讯: synchronized + wait、notify/notifyAll, Lock + Condition 的多路复用,数据库
CountDownLatch、CyclicBarrier和Semaphore的做用和用法,使用场景)设计模式
JVM内存管理机制和垃圾回收机制(内存模型、GC策略、算法、分代回收GC类型,Full GC、Minor GC做用范围和触发条件)数组
JVM内存调优(内存调整的6个参数,了解是怎么回事,通常作项目过程当中使用较多)缓存
设计模式(熟悉常见设计模式的应用场景,会画类图,经常使用:代理,2个工厂,策略,单例,观察者,适配器,组合与装饰)安全
JAVA集合类框架(理解框架图、HashMap、ArrayList、HashSet等的关系和区别,其中HashMap的存储机制几乎每次都有问)
HashMap的原理,底层数据结构,rehash的过程,指针碰撞问题HashMap的线程安全问题,为何会产生这样的线程安全问题ConcurrentHashMap的数据结构,底层原理,put和get是否线程安全
JAVA的异常处理机制(异常的分类、常见的异常有哪些、Try catch finally的使用)
JVM运行机制(理解JVM是如何运行的,理解类加载机制和类的初始化顺序)
Java 的NIO 3个主要概念 Channel、Buffer、Selector,为什么提升了性能?加分项:熟悉Netty
Linux基础(面试笔试中对linux也有必定的要求,建议最好搭建一个linux虚拟机,并练习经常使用的命令)
框架
Spring
Spring IOC原理,Bean的生成和生命周期(工厂模式 + 反射生成 + 单例),Spring用到的设计模式
Spring AOP原理和应用(动态代理与cglib代理,使用场景和代理的本质区别)
Spring如何处理高并发?高并发下,如何保证性能?
单例模式 + ThreadLocal
单例模式大大节省了对象的建立和销毁,有利于性能提升,ThreadLocal用来保证线程安全性
Spring单例模式下,用ThreadLocal来切换不一样线程直接的参数,用ThreadLocal是为了保证线程安全,实际上,ThreadLocal的key就是当前线程的Thread实例
单例模式下,Spring把每一个线程可能存在线程安全问题的参数值放进了ThreadLocal,虽然是一个实例,但在不一样线程下的数据是相互隔离的,
由于运行时建立和销毁的bean大大减小了,因此大多数场景下,这种方式对内存资源的消耗较少,而且并发越高,优点越明显
特别注意:
Spring MVC的Controller不是线程安全的!!!
Spring MVC 是基于方法的拦截,粒度更细,而Spring的Controller默认是Singleton的,即:每一个request请求,系统都会用同一个Controller去处理,
Spring MVC和Servlet都是方法级别的线程安全,若是单例的Controller或Servlet中存在实例变量,都是线程不安全的,而Struts2确实是线程安全的
优势:不用每次建立Controller,减小了对象建立和销毁
缺点:Controller是单例的,Controller里面的变量线程不安全
解决方案:
1.在Controller中使用ThreadLocal变量,把不安全的变量封装进ThreadLocal,使用ThreadLocal来保存类变量,将类变量保存在线程的变量域中,让不一样的请求隔离开来
2.声明Controller为原型 scope="prototype",每一个请求都建立新的Controller
3.Controller中不使用实例变量
Spring 事务管理的使用和原理?事务的传播属性
声明式事务管理,在Service之上或Service的方法之上,添加 @Transactional注解
@Transactional如何工做?
Spring在启动时,会去解析生成相关的Bean,这是会查看拥有相关注解的类和方法,而且为这些类和方法生成代理,并根据 @Transactional的相关参数进行相关配置注入,这样就在代理中把相关的事务处理掉了(开启正常提交事务,异常回滚事务)真正的数据库层,事务提交和回滚是经过binlog和redo log实现的
Spring如何解决对象的循环依赖引用?( 只支持Singleton做用域的, setter方式的循环依赖!不支持构造器方式和prototype的循环依赖)
原理:
建立Bean A时,先经过无参构造器建立一个A实例,此时属性都是空的,但对象引用已经建立建立出来,而后把Bean A的引用提早暴露出来,
而后setter B属性时,建立B对象,此时一样经过无参构造器,构造一个B对象的引用,并将B对象引用暴露出来。
接着B执行setter方法,去池中找到A(由于此时,A已经暴露出来,有指向该对象的引用了),这样依赖B就构造完成,也初始化完成,而后A接着初始化完成,循环依赖就这么解决了!
总结:先建立对象引用,再经过setter()方式,给属性赋值,层层建立对象 !!!
Bean A初始化时,先对其依赖B进行初始化,同时,经过默认无参构造器,生成本身的引用,而不调用其setter()方法,
当B对象建立时,若是还依赖C,则也经过无参构造器,生成B的引用,
C对象建立时,若是引用了A,则去对象池中查到A的引用,而后调用setter()方式,注入A,完成C对象的建立
C建立完成后,B使用setter()方式,注入C,完成B对象建立,
B对象场景完成后,A使用setter()方式,注入B,完成A对象建立,
最终,完成setter()方式的循环依赖!
数据库
InnoDB和MyISAM区别和选择
1.InnoDB不支持FULLTEXT类型的索引。
2.InnoDB 中不保存表的具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,可是MyISAM只要简单的读出保存好的行数便可。注意的是,当count()语句包含 where条件时,两种表的操做是同样的。
3.对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,可是在MyISAM表中,能够和其余字段一块儿创建联合索引。
4.DELETE FROM table时,InnoDB不会从新创建表,而是一行一行的删除。
5.LOAD TABLE FROM MASTER操做对InnoDB是不起做用的,解决方法是首先把InnoDB表改为MyISAM表,导入数据后再改为InnoDB表,可是对于使用的额外的InnoDB特性(例如外键)的表不适用。
另外,InnoDB表的行锁也不是绝对的,若是在执行一个SQL语句时MySQL不能肯定要扫描的范围,InnoDB表一样会锁全表,例如update table set num=1 where name like “%aaa%”
任何一种表都不是万能的,只用恰当的针对业务类型来选择合适的表类型,才能最大的发挥MySQL的性能优点。
悲观锁和乐观锁的含义(悲观锁:真正的锁,只容许一个线程操做同一条记录,
乐观锁:一种冲突检测机制,通常经过版本号或时间戳方式实现,对性能影响较小)
索引使用及其索引原理(索引底层实现:B+树)
Query查询优化
1.explain sql查看执行效率,定位优化对象的性能瓶颈
2.永远用小结果驱动大的结果集
3.尽量在索引中完成排序
4.只取出本身须要的column,而不是*
5.使用最有效的过滤条件
6.用表链接代替子查询
7.当只要一行数据时,使用limit 1
8.为搜索字段创建索引
9.千万不要ORDER BY RAND(),避免select *
10.尽量使用NOT NULL
11.开启查询缓存,并为查询缓存优化查询语句
eg:
select username from user where add_time >= now()
注意:
1.这样的语句不会使用查询缓存,
2.像NOW()和RAND()或是其它的诸如此类的SQL函数都不会开启查询缓存,由于这些函数的返回是会不定的易变的。因此,你所须要的就是用一个变量来代替MySQL的函数,从而开启缓存
3.修改, 对now()进行处理,只取年月日 yyyy-MM-dd,变为一个不衣变的值
Redis的5种数据结构和使用场景
Redis的持久化机制
Redis中Hash类型的底层2种实现区别(压缩表: 省内存 和 跳跃表:查询更快)
Redis做为分布式消息队列使用,性能和注意点
在此我向你们推荐一个Java高级群 :725633148 里面会分享一些资深架构师录制的视频录像:(有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构、面试资料)等这些成为架构师必备的知识体系 进群立刻免费领取,目前受益良多!
数据结构和算法
常见的排序算法就不说了,须要理解其原理和会写代码,还有时间空间复杂度也要知道
队列、栈:须要理解其存取结构,并能在某些场景下使用
二叉树:树的遍历、树的深度、按层次输出、平衡二叉树、逆序打印树等
链表:逆序、合并两有序的链表、判断链表是否又环、链表倒数第K个元素等
字符串:KMP算法、动态规划(这个是重点,须要好好理解动态规划,常见的题有:求解最长回文子串、求解最长公共子串等)
海量数据处理:如今好多大公司都会问海量数据的处理,因此须要掌握常见的处理方法,好比Bit-map、分而治之、hash映射等,能够百度看看相关的文章,加深理解
经常使用算法
冒泡排序
快速排序
插入排序
希尔排序
归并排序
堆排序
桶排序
动态规划
最长公共子串
最长回文子串
数组的最大k个值
数字的最大连续子数组之和
左旋转字符串
字符串匹配算法:KMP算法
二分查找
链表
单链表逆序
两个有序单链表合并
两个单链表是否相交
相交处的节点
单链表倒数第K个数
单链表排序
栈和队列
设计包含min函数的栈
两个队列实现栈
两个栈实现队列
一个数组实现栈和队列
树
前序、中序、后续遍历
求二叉树的深度
按层次遍历二叉树
判断二叉树是否为彻底二叉树
判断二叉树是否镜面对称
判断两颗树是否相等
设计模式6大原则
单一职责原则(SRP)
定义:就一个类而言,应该仅有一个引发它变化的缘由。
从这句定义咱们很难理解它的含义,通俗讲就是咱们不要让一个类承担过多的职责。若是一个类承担的职责过多,就等于把这些职责耦合在一块儿,一个职责的变化可能会削弱或者抑制这个类完成其余职责的能力。这种耦合会致使脆弱的设计,当变化发生时,设计会遭受到破坏。
好比我常常看到一些Android开发在Activity中写Bean文件,网络数据处理,若是有列表的话Adapter 也写在Activity中,问他们为何除了好找也没啥理由了,把他们拆分到其余类岂不是更好找,若是Activity过于臃肿行数过多,显然不是好事,若是咱们要修改Bean文件,网络处理和Adapter都须要上这个Activity来修改,就会致使引发这个Activity变化的缘由太多,咱们在版本维护时也会比较头疼。也就严重违背了定义“就一个类而言,应该仅有一个引发它变化的缘由”。
固然若是想争论的话,这个模式是能够引发不少争论的,但请记住一点,你写代码不仅是为了你也是为了其余人。
开放封闭原则(ASD)
定义:类、模块、函数等等等应该是能够拓展的,可是不可修改。
开放封闭有两个含义,一个是对于拓展是开放的,另外一个是对于修改是封闭的。对于开发来讲需求确定是要变化的,可是新需求一来,咱们就要把类从新改一遍这显然是使人头疼的,因此咱们设计程序时面对需求的改变要尽量的保证相对的稳定,尽可能用新代码实现拓展来修改需求,而不是经过修改原有的代码来实现。
假设咱们要实现一个列表,一开始只有查询的功能,若是产品又要增长添加功能,过几天又要增长删除功能,大多数人的作法是写个方法而后经过传入不一样的值来控制方法来实现不一样的功能,可是若是又要新增功能咱们还得修改咱们的方法。用开发封闭原则解决就是增长一个抽象的功能类,让增长和删除和查询的做为这个抽象功能类的子类,这样若是咱们再添加功能,你会发现咱们不须要修改原有的类,只须要添加一个功能类的子类实现功能类的方法就能够了。
3.里氏替换原则(LSP)
定义:全部引用基类(父类)的地方必须能透明地使用其子类的对象
里氏代换原则告诉咱们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,若是一个软件实体使用的是一个子类对象的话,那么它不必定可以使用基类对象。
里氏代换原则是实现开闭原则的重要方式之一,因为使用基类对象的地方均可以使用子类对象,所以在程序中尽可能使用基类类型来对对象进行定义,而在运行时再肯定其子类类型,用子类对象来替换父类对象。
在使用里氏代换原则时须要注意以下几个问题:
子类的全部方法必须在父类中声明,或子类必须实现父类中声明的全部方法。根据里氏代换原则,为了保证系统的扩展性,在程序中一般使用父类来进行定义,若是一个方法只存在子类中,在父类中不提供相应的声明,则没法在以父类定义的对象中使用该方法。
咱们在运用里氏代换原则时,尽可能把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实如今父类中声明的方法,运行时,子类实例替换父类实例,咱们能够很方便地扩展系统的功能,同时无须修改原有子类的代码,增长新的功能能够经过增长一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。
Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。
4.依赖倒置原则(DIP)
定义:高层模块不该该依赖低层模块,两个都应该依赖于抽象。抽象不该该依赖于细节,细节应该依赖于抽象。
在Java中,抽象就是指接口或者抽象类,二者都是不能直接被实例化的;细节就是实现类,实现接口或者继承抽象类而产生的就是细节,也就是能够加上一个关键字new产生的对象。高层模块就是调用端,低层模块就是具体实现类。
依赖倒置原则在Java中的表现就是:模块间经过抽象发生,实现类之间不发生直接依赖关系,其依赖关系是经过接口或者抽象类产生的。若是类与类直接依赖细节,那么就会直接耦合,那么当修改时,就会同时修改依赖者代码,这样限制了可扩展性。
5.迪米特原则(LOD)
定义:一个软件实体应当尽量少地与其余实体发生相互做用。
也称为最少知识原则。若是一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽可能少地影响其余模块,扩展会相对容易,这是对软件实体之间通讯的限制,迪米特法则要求限制软件实体之间通讯的宽度和深度。迪米特法则可下降系统的耦合度,使类与类之间保持松散的耦合关系。
迪米特法则要求咱们在设计系统时,应该尽可能减小对象之间的交互,若是两个对象之间没必要彼此直接通讯,那么这两个对象就不该当发生任何直接的相互做用,若是其中的一个对象须要调用另外一个对象的某一个方法的话,能够经过第三者转发这个调用。简言之,就是经过引入一个合理的第三者来下降现有对象之间的耦合度。
在将迪米特法则运用到系统设计中时,要注意下面的几点:在类的划分上,应当尽可能建立松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类形成太大波及;在类的结构设计上,每个类都应当尽可能下降其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其余类的引用上,一个对象对其余对象的引用应当降到最低。
6.接口隔离原则(ISP)
定义:一个类对另外一个类的依赖应该创建在最小的接口上。
创建单一接口,不要创建庞大臃肿的接口,尽可能细化接口,接口中的方法尽可能少。也就是说,咱们要为各个类创建专用的接口,而不要试图去创建一个很庞大的接口供全部依赖它的类去调用。
采用接口隔离原则对接口进行约束时,要注意如下几点:
接口尽可能小,可是要有限度。对接口进行细化能够提升程序设计灵活性,可是若是太小,则会形成接口数量过多,使设计复杂化。因此必定要适度。
为依赖接口的类定制服务,只暴露给调用的类它须要的方法,它不须要的方法则隐藏起来。只有专一地为一个模块提供定制服务,才能创建最小的依赖关系。
提升内聚,减小对外交互。使接口用最少的方法去完成最多的事情。
concurrent包的实现基础
因为java的CAS同时具备 volatile 读和volatile写的内存语义,所以Java线程之间的通讯如今有了下面四种方式:
A线程写volatile变量,随后B线程读这个volatile变量。
A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操做,这是在多处理器中实现同步的关键(从本质上来讲,可以支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,所以任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操做的原子指令)。同时,volatile变量的读/写和CAS能够实现线程之间的通讯。把这些特性整合在一块儿,就造成了整个concurrent包得以实现的基石。若是咱们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
首先,声明共享变量为volatile;
而后,使用CAS的原子条件更新来实现线程之间的同步;
同时,配合以volatile的读/写和CAS所具备的volatile读和写的内存语义来实现线程之间的通讯。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。
在此我向你们推荐一个Java高级群 :725633148 里面会分享一些资深架构师录制的视频录像:(有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构、面试资料)等这些成为架构师必备的知识体系 进群立刻免费领取,目前受益良多!