Java语言特性与设计模式
设计模式知识点
前面说了操作系统和网络知识,接下来是设计模式的考察点,一般有两个:
①常用设计模式的实现;
②设计模式的使用场景。
设计模式分为3大类型共23种:
1.创建型:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
2.结构型:适配器模式,装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
3.行为型:策略模式、模仿方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
最常见的设计模式有:单例模式、工厂模式、代理模式、构造者模式、责任链模式、适配器模式、观察者模式等,如下图所示。
面试中对于设计模式,你应该明白不同的设计用来解决什么场景问题,对于常用的设计模式能够灵活运用。下面重点介绍几种常用的设计模式。
单例模式
首先是单例模式,这个模式在实际业务中会经常用到,也是设计模式中的主要考察点。这里介绍线程安全的单例模式实现方式。
单例模式常见的实现方式有三种。
①第一种是静态初始化方式,也叫作饿汉方式。实现的思路就是在类初始化时完成单例实例的创建,因此不会产生并发问题,在这种方式下不管是否会使用到这个单例,都会创建这个单例。
②第二种实现方式是双重检查,也叫作懒汉方式,只有在真正用到这个单例实例的时候才会去创建,如果没有使用就不会创建。这个方式必然会面对多个线程使用实例时的并发问题。为了解决并发访问问题,通过synchronized或者lock进行双重检查,保证只有一个线程能够创建实例。这里要注意内存可见性引起的并发问题,必须使用volatile关键字修饰单例变量。
③第三种是单例注册表方式,Spring中Bean的单例模式就是通过单例注册表方式实现的。
下面结合设计模式的实际应用,来介绍常用的设计模式,如下图所示。在面试时遇到类似问题,记得要将设计模式与实际业务场景进行结合,来体现对设计模式的理解和应用能力。
工厂模式
工厂模式是创建不同类型实例时常用的方式,例如Spring中的各种Bean是有不同Bean工厂进行创建的。
代理模式
代理模式,主要用在不适合或者不能直接引用另一个对象的场景,可以通过代理模式对被代理对象的访问行为进行控制。Java的代理模式分为静态代理和动态代理。静态代理指在编译时就已经创建好了代理类,例如在源代码中编写的类;动态代理指在JVM运行过程中动态创建的代理类,使用动态代理的方法有JDK动态代理、CGLIB、Javassist等。面试时遇到这个问题可以举个动态代理的例子,比如在Motan RPC中,是使用JDK的动态代理,通过反射把远程请求进行封装,使服务看上去就像在使用本地的方法。
责任链模式
责任链模式有点像工厂的流水线,链上每一个节点完成对对象的某一种处理,例如Netty框架在处理消息时使用的Pipeline就是一种责任链模式。
适配器模式
适配器模式,类似于我们常见的转接头,把两种不匹配的对象来进行适配,也可以起到两个不同的对象进行解耦的作用。例如我们常用的日志处理框架SLF4J,如果我们使用了SLF4J就可以跟Log4j或者Logback等具体的日志实现框架进行解耦。通过不同适配器将SLF4J与log4j等实现框架进行适配,完成日志功能的使用。
观察者模式
观察者模式也被称作发布订阅模式,适用于一个对象的某个行为需要触发一系列事件的场景,例如gRPC中的Stream流式请求的处理就是通过观察者模式实现的。
构造者模式
构造者模式,适用于一个对象有很多复杂的属性,需要根据不同情况创建不同的具体对象,例如创建一个PB对象时使用的builder方式。
Java语言特性知识点
Java语言特性的知识点汇总如下图所示。
常用集合类实现与Java并发工具包JUC是常见考点,JUC会在后面的多线程课程中进行详细讲解。
Java的集合类中部分需要重点了解类的实现。例如,HashMap、TreeMap是如何实现的等。
动态代理与反射是Java语言的特色,需要掌握动态代理与反射的使用场景,例如在ORM框架中会大量使用代理类。而RPC调用时会使用到反射机制调用实现类方法。
Java基础数据类型也常常会在面试中被问到,例如各种数据类型占用多大的内存空间、数据类型的自动转型与强制转型、基础数据类型与wrapper数据类型的自动装箱与拆箱等。
Java对对象的引用分为强引用、软引用、弱引用、虚引用四种,这些引用在GC时的处理策略不同,强引用不会被GC回收;软引用内存空间不足时会被GC回收;弱引用则在每次GC时被回收;虚引用必须和引用队列联合使用,主要用于跟踪一个对象被垃圾回收的过程。
Java的异常处理机制就是try-catch-finally机制,需要知道异常时在try catch中的处理流程;需要了解Error和Exception的区别。
最后Java的注解机制和SPI扩展机制可以作为扩展点适当了解。
详解Map
关于Java的基础知识重点讲解最长考察点HashMap和ConcurrentHashMap,以及Java的不同版本新技术特性,如下图所示。
面试中,Map的实现这个题目能够考察到数据结构、Java基础实现以及对并发问题处理思路的掌握程度。
1.HashMap
a.先来看HashMap的实现,简单来说,Java的HashMap就是数组加链表实现的,数组中的每一项是一个链表。通过计算存入对象的HashCode,来计算对象在数组中要存入的位置,用链表来解决散列冲突,链表中的节点存储的是键值对。
b.除了实现的方式,我们还需要知道填充因子的作用与Map扩容时的rehash机制,需要知道HashMap的容量都是2的幂次方,是因为可以通过按位与操作来计算余数,比求模要快。另外需要知道HashMap是非线程安全的,在多线程put的情况下,有可能在容量超过填充因子时进行rehash,因为HashMap为了避免尾部遍历,在链表插入元素时使用头插法,多线程的场景下有可能会产生死循环。
2.ConcurrentHashMap
a.从HashMap的非线程安全,面试官很自然地就会问到线程安全的ConcurrentHashMap。ConcurrentHashMap采用分段锁的思想来降低并发场景下的锁定发生频率,在JDK1.7与1.8中的实现差异非常大,1.7中使用Segment进行分段加锁,降低并发锁定;1.8中使用CAS自旋锁的乐观锁来提高性能,但是在并发度较高时性能会比较一般。另外1.8中的ConcurrentHashMap引入了红黑树来解决Hash冲突时链表顺序查找的问题。红黑树的启用条件与链表的长度和Map的总容量有关,默认是链表大于8且容量大于64时转为红黑树。这部分内容建议详细阅读源码进行学习。
详解Java版本特性
Java近些年一改以往的版本发布风格,发布频率提高了很多。目前大部分公司的生产环境使用的还是1.8版本,一少部分升级到1.9或1.10版本,Java的1.8版本是一个长期支持的版本,最新发布的1.11版本也是一个长期支持的版本,1.11版本中已经包含了1.9、1.10版本的功能,所以1.8和1.11版本是我们要重点关注的版本。
在1.8版本中Java增加了对lambda表达式的支持,使Java代码的编写可以更简洁,也更方便支持并行计算。并且提供了很多Stream流式处理的API。1.8版本还支持了方法引用的能力,可以进一步简化lambda表达式的写法。
在1.8版本中,接口可以提供默认方法了,这样可以简化一些简单的抽象类。最后在1.8版本中对方法区进行调整,使用Metaspace替换掉了PermGen的永久代。Metaspace与PermGen之间最大的区别在于:Metaspace并不在虚拟机中,而是使用本地内存。替换的目的一方面是可以提升对元数据的管理同时提升GC效率,另一方面是方便后续HotSpot与JRockit合并。
在1.9、1.10版本中的主要特性是增加了模块系统,将G1设为默认垃圾回收器、支持局部变量推断等功能。这些功能都已经包含在1.11版本中。
1.11版本是Java最新的长期支持版本,也将会是未来一段时间的主要版本,1.11版本中提供的最激动人心的功能是ZGC这个新的垃圾回收器,ZGC为大内存堆设计,有着非常强悍的性能,能够实现10ms以下的GC暂停时间。关于ZGC会在下一课中进一步介绍。除此之外,1.11版本对字符串处理API进行了增强,提供了字符复制等功能。1.11版本还内置了HttpClient。
考察点和加分项
面试考察点
从面试官角度出发,总结本课时对于计算机基础和Java语言特性的考察点如下。
1.第一考察点就是对基本概念和基本原理的考察。要求对这两项的理解必须是正确的,清晰的。例如网络协议的4/7层模型的概念,TCP协议流量控制的实现原理等。
2.第二个考察点是常用工具、模型的实现方式和使用姿势,例如HashMap在JDK1.8中的实现方式是怎样的?单例模式有几种实现方式?什么场景下该使用懒汉式单例实现,什么场景下该使用饿汉式单例实现等。
3.第三个考察点是经常使用到的一些知识点,例如你常用的Linux命令有哪些,都用来解决什么问题?
4.第四个考察点是实际应用中容易犯错的点,例如==与squals的区别,例如对象的强引用使用不当可能导致内存泄漏,主要考察候选人对于不同对象引用方式的作用和理解。
5.第五个考察点是与面试相关的知识点。例如面试的岗位是中间件研发,面试时可能会涉及更多的存储、网络相关的知识的考察。
加分项
前面提到的考察点是面试通过的必要条件,回答出问题不一定能保证通过面试,所以如何做到比其他竞争者更优秀,给面试官留下更好的印象,是成功的关键。你需要一些buff。这些加分能力不仅仅针对这一课的内容,后续课程也有一定的通用性。
1.能将面试考察点与实际业务场景结合,或者与实际使用经验结合。
这样能够更好的体现对知识点的理解,突出实践能力。例如,在回答:“你知道哪几种设计模式”这个问题时,不但能说出几种设计模式,以及适合哪类场景,而且还能指出哪些著名的框架在处理什么问题时使用了哪种设计模式,或者自己在处理某个项目的什么场景时,使用了哪种设计模式,取得了什么效果,这样肯定会给面试官留下非常好的印象。
2.用反例来描述在实际场景中,误用某些功能会带来的问题。
例如,介绍反射机制时,除了介绍反射机制的实现方式、应用场景外,还可以提到大量使用反射会对性能产生影响,应避免滥用。
3.知道与考察知识点相关的优化点。
例如在介绍TCP建连与断连时,最好能够指出线上如果出现大量time_wait时,可以通过调整系统参数加快连接的回收与复用。
4.了解与知识点相关的最新技术趋势。
例如介绍ConcurrentHashMap的实现时,能够知道1.8版本的改进细节。或者在介绍HTTP时能够说出HTTP2和QUIC的特点与实现等。
5.回答面试问题时,在比较了解的前提下,尽量增加回答内容的深度。例如在介绍TCP的滑动窗口时,能讲到流量和拥塞控制,近一步能指出不同的解决拥塞的算法等。
这里要注意,面试官一般会沿着候选人的问题继续追问,如果对细节不太了解可能会适得其反。
真题汇总
真题汇总如下图所示。
解题思路如下。
第一题:线程、进程的区别和联系,主要从资源占用、切换效率、通信方式等方面进行解答;
第二题:线程的切换过程主要考察上下文切换,需要保存寄存器、栈等现场,需要由用户态切换到内核态。最后通过vmstat命令查看上下文切换的情况;
第三题:常用的Linux命令可以参考前面操作系统汇总提到的命令;
第四题、第五题,知识点详解中已经介绍过了,务必要掌握;
第六题:大致包括DNS解析、TCP建连、HTTP请求、HTTP相应等,实际回答时,可以画个简单的交互图来说明。
再汇总一些真题,包括基础概念,以及前面介绍过的知识点,如下图所示。