此为部分面试题包含答案,更多面试题见微信小程序 “Java精选面试题”,3000+道面试题。内容持续更新中包含基础、集合、并发、JVM、Spring、Spring MVC、Spring Boot、Spring Cloud、Dubbo、MySQL、Redis、MyBaits、Zookeeper、Linux、数据结构与算法、项目管理工具、消息队列、设计模式、Nginx、常见 BUG 问题、网络编程等。
————————————————
面向对象编程有哪些特征?
一、抽象和封装
类和对象体现了抽象和封装
抽象就是解释类与对象之间关系的词。类与对象之间的关系就是抽象的关系。一句话来说明:类是对象的抽象,而对象则是类得特例,即类的具体表现形式。
封装两个方面的含义:一是将有关数据和操作代码封装在对象当中,形成一个基本单位,各个对象之间相对独立互不干扰。二是将对象中某些属性和操作私有化,已达到数据和操作信息隐蔽,有利于数据安全,防止无关人员修改。把一部分或全部属性和部分功能(函数)对外界屏蔽,就是从外界(类的大括号之外)看不到,不可知,这就是封装的意义。
二、继承
面向对象的继承是为了软件重用,简单理解就是代码复用,把重复使用的代码精简掉的一种手段。如何精简,当一个类中已经有了相应的属性和操作的代码,而另一个类当中也需要写重复的代码,那么就用继承方法,把前面的类当成父类,后面的类当成子类,子类继承父类,理所当然。就用一个关键字extends就完成了代码的复用。
三、多态
没有继承就没有多态,继承是多态的前提。虽然继承自同一父类,但是相应的操作却各不相同,这叫多态。由继承而产生的不同的派生类,其对象对同一消息会做出不同的响应。
JDK、JRE、JVM 之间有什么关系?
1、JDK
JDK(Java development Toolkit),JDK是整个Java的核心,包括了Java的运行环境(Java Runtime Environment),一堆的Java工具(Javac,java,jdb等)和Java基础的类库(即Java API 包括rt.jar).
Java API 是Java的应用程序的接口,里面有很多写好的Java class,包括一些重要的结构语言以及基本图形,网络和文件I/O等等。
2、JRE
JRE(Java Runtime Environment),Java运行环境。在Java平台下,所有的Java程序都需要在JRE下才能运行。只有JVM还不能进行class的执行,因为解释class的时候,JVM需调用解释所需要的类库lib。JRE里面有两个文件夹bin和lib,这里可以认为bin就是JVM,lib就是JVM所需要的类库,而JVM和lib合起来就称JRE。
JRE包括JVM和JAVA核心类库与支持文件。与JDK不同,它不包含开发工具-----编译器,调试器,和其他工具。
3、JVM
JVM:Java Virtual Machine(Java 虚拟机)JVM是JRE的一部分,它是虚拟出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件构架,入处理器,堆栈,寄存器等,还有相应的指令系统。
JVM是Java实现跨平台最核心的部分,所有的Java程序会首先被编译为class的类文件,JVM的主要工作是解释自己的指令集(即字节码)并映射到本地的CPU的指令集或OS的系统调用。Java面对不同操作系统使用不同的虚拟机,一次实现了跨平台。JVM对上层的Java源文件是不关心的,它关心的只是由源文件生成的类文件
如何使用命令行编译和运行 Java 文件?
编译和运行Java文件,需了解两个命令:
1)javac命令:编译java文件;使用方法: javac Hello.java ,如果不出错的话,在与Hello.java 同一目录下会生成一个Hello.class文件,这个class文件是操作系统能够使用和运行的文件。
2)java命令: 作用:运行.class文件;使用方法:java Hello,如果不出错的话,会执行Hello.class文件。注意:这里的Hello后面不需要扩展名。
新建文件,编写代码如下:
public class Hello{ public static void main(String[] args){ System.out.println("Hello world,欢迎关注微信公众号“Java精选”!"); } }
文件命名为Hello.java,注意后缀为“java”。
打开cmd,切换至当前文件所在位置,执行javac Hello.java,该文件夹下面生成了一个Hello.class文件
输入java Hello命令,cmd控制台打印出代码的内容Hello world,欢迎关注微信公众号“Java精选”!
说说常用的集合有哪些?
Map接口和Collection接口是所有集合框架的父接口
Collection 接口的子接口包括:Set接口和List接口。
Set中不能包含重复的元素。List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。
Map 接口的实现类主要有:HashMap、Hashtable、ConcurrentHashMap以及TreeMap等。Map不能包含重复的key,但是可以包含相同的value。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。此为部分面试题包含答案,更多面试题见微信小程序 “Java精选面试题”,3000+道面试题。
Set 接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List 接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
Iterator,所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:
hasNext()是否还有下一个元素
next()返回下一个元素
remove()删除当前元素
进程与线程之间有什么区别?
进程是系统中正在运行的一个程序,程序一旦运行就是进程。
进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。
一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程与进程的一个主要区别是,统一进程内的一个主要区别是,同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。
线程是进程的一个实体,是进程的一条执行路径。
线程是进程的一个特定执行路径。当一个线程修改了进程的资源,它的兄弟线程可以立即看到这种变化。
什么是 JVM?
Java程序的跨平台特性主要是指字节码文件可以在任何具有Java虚拟机的计算机或者电子设备上运行,Java虚拟机中的Java解释器负责将字节码文件解释成为特定的机器码进行运行。
因此在运行时,Java源程序需要通过编译器编译成为.class文件。
众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是一个执行的外壳,它会装载jvm.dll(windows下,下皆以windows平台为例,linux下和solaris下其实类似,为:libjvm.so),这个动态连接库才是java虚拟机的实际操作处理所在。
JVM是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。
使用JVM就是为了支持与操作系统无关,实现跨平台。所以,JAVA虚拟机JVM是属于JRE的,而现在我们安装JDK时也附带安装了JRE(当然也可以单独安装JRE)。
什么是事务?
事务(transaction)是指数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)。
通俗的说就是事务可以作为一个单元的一组有序的数据库操作。如果组中的所有操作都成功,则认为事务成功,即使只有一个操作失败,事务也不成功。如果所有操作完成,事务则提交,其修改将作用于所有其他数据库进程。如果一个操作失败,则事务将回滚,该事务所有操作的影响都将取消。
MySQL 事务都有哪些特性?
事务的四大特性:
1 、原子性
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
2 、一致性
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
3 、隔离性
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
4 、持续性
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
MyBatis 是什么框架?
MyBatis框架是一个优秀的数据持久层框架,在实体类和SQL语句之间建立映射关系,是一种半自动化的ORM实现。其封装性要低于Hibernate,性能优秀,并且小巧。
ORM即对象/关系数据映射,也可以理解为一种数据持久化技术。
MyBatis的基本要素包括核心对象、核心配置文件、SQL映射文件。
数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。
什么是 Redis?
redis是一个高性能的key-value数据库,它是完全开源免费的,而且redis是一个NOSQL类型数据库,是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案,是一个非关系型的数据库。但是,它也是不能替代关系型数据库,只能作为特定环境下的扩充。
redis是一个以key-value存储的数据库结构型服务器,它支持的数据结构类型包括:字符串(String)、链表(lists)、哈希表(hash)、集合(set)、有序集合(Zset)等。为了保证读取的效率,redis把数据对象都存储在内存当中,它可以支持周期性的把更新的数据写入磁盘文件中。而且它还提供了交集和并集,以及一些不同方式排序的操作。
什么是 Spring 框架?
Spring中文翻译过来是春天的意思,被称为J2EE的春天,是一个开源的轻量级的Java开发框架, 具有控制反转(IoC)和面向切面(AOP)两大核心。Java Spring框架通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
Spring框架不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从Spring中受益。Spring框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
1)IOC 控制反转
对象创建责任的反转,在Spring中BeanFacotory是IOC容器的核心接口,负责实例化,定位,配置应用程序中的对象及建立这些对象间的依赖。XmlBeanFacotory实现BeanFactory接口,通过获取xml配置文件数据,组成应用对象及对象间的依赖关系。
Spring中有3中注入方式,一种是set注入,另一种是接口注入,另一种是构造方法注入。
2)AOP面向切面编程
AOP是指纵向的编程,比如两个业务,业务1和业务2都需要一个共同的操作,与其往每个业务中都添加同样的代码,通过写一遍代码,让两个业务共同使用这段代码。
Spring中面向切面编程的实现有两种方式,一种是动态代理,一种是CGLIB,动态代理必须要提供接口,而CGLIB实现是由=有继承。
什么是 Spring MVC 框架?
Spring MVC属于Spring FrameWork的后续产品,已经融合在Spring Web Flow中。
Spring框架提供了构建Web应用程序的全功能MVC模块。
使用Spring可插入MVC架构,从而在使用Spring进行WEB开发时,可以选择使用Spring中的Spring MVC框架或集成其他MVC开发框架,如Struts1(已基本淘汰),Struts2(老项目还在使用或已重构)等。
通过策略接口,Spring框架是高度可配置的且包含多种视图技术,如JavaServer Pages(JSP)技术、Velocity、Tiles、iText和POI等。
Spring MVC 框架并不清楚或限制使用哪种视图,所以不会强迫开发者只使用JSP技术。
Spring MVC分离了控制器、模型对象、过滤器以及处理程序对象的角色,这种分离让它们更容易进行定制。
什么是 Spring Boot 框架?
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。
Spring Boot框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
2014年4月发布第一个版本的全新开源的Spring Boot轻量级框架。它基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。
另外Spring Boot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。
什么是 Spring Cloud 框架?
Spring Cloud是一系列框架的有序集合,它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Spring Cloud的子项目,大致可分成两大类:
一类是对现有成熟框架“Spring Boot化”的封装和抽象,也是数量最多的项目;
第二类是开发一部分分布式系统的基础设施的实现,如Spring Cloud Stream扮演的是kafka, ActiveMQ这样的角色。
对于快速实践微服务的开发者来说,第一类子项目已经基本足够使用,如:
1)Spring Cloud Netflix是对Netflix开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等;
2)Spring Cloud Config将配置信息中央化保存, 配置Spring Cloud Bus可以实现动态修改配置文件;
3)Spring Cloud Bus分布式消息队列,是对Kafka, MQ的封装;
4)Spring Cloud Security对Spring Security的封装,并能配合Netflix使用;
5)Spring Cloud Zookeeper对Zookeeper的封装,使之能配置其它Spring Cloud的子项目使用;
6)Spring Cloud Eureka是Spring Cloud Netflix微服务套件中的一部分,它基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。注意的是从2.x起,官方不会继续开源,若需要使用2.x,风险还是有的。但是我觉得问题并不大,eureka目前的功能已经非常稳定,就算不升级,服务注册/发现这些功能已经够用。consul是个不错的替代品,还有其他替代组件,后续篇幅会有详细赘述或关注微信公众号“Java精选”,有详细替代方案源码分享。
Spring Cloud 框架有哪些优缺点?
Spring Cloud优点:
1)服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率,每个模块可以独立开发和部署、代码耦合度低;
2)可以更精准的制定优化服务方案,提高系统的可维护性,每个服务可以单独进行部署,升级某个模块的时候只需要单独部署对应的模块服务即可,效率更高;
3)微服务架构采用去中心化思想,服务之间采用Restful等轻量级通讯,比ESB更轻量,模块专一性提升,每个模块只需要关心自己模块所负责的功能即可,不需要关心其他模块业务,专一性更高,更便于功能模块开发和拓展;
4)技术选型不再单一,由于每个模块是单独开发并且部署,所以每个模块可以有更多的技术选型方案,如模块1数据库选择mysql,模块2选择用oracle也是可以的;
5)适于互联网时代,产品迭代周期更短。系统稳定性以及性能提升,由于微服务是几个服务共同组成的项目或者流程,因此相比传统单一项目的优点就在于某个模块提供的服务宕机过后不至于整个系统瘫痪掉,而且微服务里面的容灾和服务降级机制也能大大提高项目的稳定性;从性能而言,由于每个服务是单独部署,所以每个模块都可以有自己的一套运行环境,当某个服务性能低下的时候可以对单个服务进行配置或者代码上的升级,从而达到提升性能的目的。
Spring Cloud缺点:
1)微服务过多,治理成本高,不利于维护系统,服务之间接口调用成本增加,相比以往单项目的时候调用某个方法或者接口可以直接通过本地方法调用就能够完成,但是当切换成微服务的时候,调用方式就不能用以前的方式进行调试、目前主流采用的技术有http api接口调用、RPC、WebService等方式进行调用,调用成本比单个项目的时候有所增加;
2)分布式系统开发的成本高(容错,分布式事务等)对团队挑战大
2)独立的数据库,微服务产生事务一致性的问题,由于各个模块用的技术都各不相同、而且每个服务都会高并发进行调用,就会存在分布式事务一致性的问题;
3)分布式部署,造成运营的成本增加、相比较单个应用的时候,运营人员只需要对单个项目进行部署、负载均衡等操作,但是微服务的每个模块都需要这样的操作,增加了运行时的成本;
4)由于整个系统是通过各个模块组合而成的,因此当某个服务进行变更时需要对前后涉及的所有功能进行回归测试,测试功能不能仅限于当个模块,增加了测试难度和测试成本;
总体来说优点大过于缺点,目前看来Spring Cloud是一套非常完善的微服务框架,目前很多企业开始用微服务,Spring Cloud的优势是显而易见的。
什么是消息队列?
MQ全称为Message Queue 消息队列(MQ)是一种应用程序对应用程序的通信方法。
消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。
MQ是消费生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中的消息。
消息生产者只需要把消息发布到MQ中而不用管谁来获取,消息消费者只管从MQ中获取消息而不管是谁发布的消息,这样生产者和消费者双方都不用清楚对方的存在。
目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。
消息队列有哪些应用场景?
列举消息队列场景,异步处理,应用解耦,流量削锋,日志处理和消息通讯五个场景。
1、异步处理场景
用户注册后,需要发注册邮件和注册短信。传统的做法有两种
1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。
按照以上描述,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此采用消息队列的方式,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍。
2、应用解耦场景
用户购买物品下单后,订单系统需要通知库存系统。传统的做法是订单系统调用库存系统的接口。该模式的缺点:
1)假如库存系统无法访问,则订单减库存将失败,从而导致订单失败;
2)订单系统与库存系统耦合;
如何解决以上问题呢?
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后订单系统写入消息队列就不再关心其他的后续操作,从而实现订单系统与库存系统的应用解耦。
3、流量削锋场景
流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中被广泛应用。
秒杀活动,一般会因为流量并发过大,导致流量暴增,应用宕机,从而为解决该问题,一般在应用前端加入消息队列。
控制活动的人数,缓解短时间内高流量压垮应用。当用户的请求,服务器接收后,先写入消息队列。如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面,而其秒杀业务根据消息队列中的请求信息,再做后续处理。
4、日志处理场景
日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。日志采集客户端,负责日志数据采集,定时写受写入Kafka队列,而Kafka消息队列,负责日志数据的接收,存储和转发,日志处理应用订阅并消费kafka队列中的日志数据。
5、消息通讯
消息通讯是指消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。
什么是 Linux 操作系统?
Linux全称GNU/Linux,是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX的多用户、多任务、支持多线程和多CPU的操作系统。
伴随着互联网的发展,Linux得到了来自全世界软件爱好者、组织、公司的支持。它除了在服务器方面保持着强劲的发展势头以外,在个人电脑、嵌入式系统上都有着长足的进步。使用者不仅可以直观地获取该操作系统的实现机制,而且可以根据自身的需要来修改完善Linux,使其最大化地适应用户的需要。
Linux不仅系统性能稳定,而且是开源软件。其核心防火墙组件性能高效、配置简单,保证了系统的安全。
在很多企业网络中,为了追求速度和安全,Linux不仅仅是被网络运维人员当作服务器使用,甚至当作网络防火墙,这是Linux的一大亮点。
Linux具有开放源码、没有版权、技术社区用户多等特点,开放源码使得用户可以自由裁剪,灵活性高,功能强大,成本低。尤其系统中内嵌网络协议栈,经过适当的配置就可实现路由器的功能。这些特点使得Linux成为开发路由交换设备的理想开发平台。
什么是数据结构?
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
数据结构(data structure)是带有结构特性的数据元素的集合,它研究的是数据的逻辑结构和数据的物理结构以及它们之间的相互关系,并对这种结构定义相适应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。
简而言之,数据结构是相互之间存在一种或多种特定关系的数据元素的集合,即带“结构”的数据元素的集合。“结构”就是指数据元素之间存在的关系,分为逻辑结构和存储结构。
数据的逻辑结构和物理结构是数据结构的两个密切相关的方面,同一逻辑结构可以对应不同的存储结构。算法的设计取决于数据的逻辑结构,而算法的实现依赖于指定的存储结构。
数据结构的研究内容是构造复杂软件系统的基础,它的核心技术是分解与抽象。
通过分解可以划分出数据的3个层次;再通过抽象,舍弃数据元素的具体内容,就得到逻辑结构。类似地,通过分解将处理要求划分成各种功能,再通过抽象舍弃实现细节,就得到运算的定义。
上述两个方面的结合可以将问题变换为数据结构。这是一个从具体(即具体问题)到抽象(即数据结构)的过程。
然后,通过增加对实现细节的考虑进一步得到存储结构和实现运算,从而完成设计任务。这是一个从抽象(即数据结构)到具体(即具体实现)的过程。
什么是设计模式?
设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路,通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性好。使用设计模式最终的目的是实现代码的高内聚和低耦合。
高内聚低耦合是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。
目的是使程序模块的可重用性、移植性大大增强。
通常程序结构中各模块的内聚程度越高,模块间的耦合程度就越低。
内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事,它描述的是模块内的功能联系;耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。
什么是 Zookeeper?
ZooKeeper由雅虎研究院开发,ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,后来托管到Apache,是Hadoop和Hbase的重要组件。
ZooKeeper是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在$zookeeper_home\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。
于2010年11月正式成为Apache的顶级项目。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
分布式应用程序可以基于ZooKeeper实现数据发布与订阅、负载均衡、命名服务、分布式协调与通知、集群管理、Leader选举、分布式锁、分布式队列等功能。
应用服务 8080 端口被意外占用如何解决?
1)按键盘WIN+R键,打开后在运行框中输入“CMD”命令,点击确定。
2)在CMD窗口,输入“netstat -ano”命令,按回车键,即可查看所有的端口占用情况。
3)找到本地地址一览中类似“0.0.0.0:8080”信息,通过此列查看8080端口对应的程序PID。
4)打开任务管理器,详细信息找到对应的应用PID(若不存在通过设置可以调出来),右键结束任务即可。
什么是 Dubbo 框架?
Dubbo(读音[ˈdʌbəʊ])是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成。
Dubbo提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
核心组件
Remoting: 网络通信框架,实现了 sync-over-async 和request-response 消息机制;
RPC: 一个远程过程调用的抽象,支持负载均衡、容灾和集群功能;
Registry: 服务目录框架用于服务的注册和服务事件发布和订阅。
什么是 Maven?
Maven即为项目对象模型(POM),它可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具软件。
Maven 除了以程序构建能力为特色之外,还提供高级项目管理工具。
由于Maven的缺省构建规则有较高的可重用性,所以常常用两三行Maven构建脚本就可以构建简单的项目。
由于Maven面向项目的方法,许多Apache Jakarta项目发文时使用Maven,而且公司项目采用Maven的比例在持续增长,相比较Gradle,在之后的篇幅中会说明,欢迎大家关注微信公众号“Java精选”。
Maven这个单词来自于意第绪语(犹太语),意为知识的积累,最初在Jakata Turbine项目中用来简化构建过程。
当时有一些项目(有各自Ant build文件),仅有细微的差别,而JAR文件都由CVS来维护。于是希望有一种标准化的方式构建项目,一个清晰的方式定义项目的组成,一个容易的方式发布项目的信息,以及一种简单的方式在多个项目中共享JARs。
应用层中常见的协议都有哪些?
应用层协议(application layer protocol)定义了运行在不同端系统上的应用程序进程如何相互传递报文。
应用层协议
1)DNS:一种用以将域名转换为IP地址的Internet服务,域名系统DNS是因特网使用的命名系统,用来把便于人们使用的机器名字转换为IP地址。
现在顶级域名TLD分为三大类:国家顶级域名nTLD;通用顶级域名gTLD;基础结构域名。
域名服务器分为四种类型:根域名服务器;顶级域名服务器;本地域名服务器;权限域名服务器。
2)FTP:文件传输协议FTP是因特网上使用得最广泛的文件传送协议。FTP提供交互式的访问,允许客户指明文件类型与格式,并允许文件具有存取权限。
基于客户服务器模式,FTP协议包括两个组成部分,一是FTP服务器,二是FTP客户端,提供交互式的访问面向连接,使用TCP/IP可靠的运输服务,主要功能:减少/消除不同操作系统下文件的不兼容性 。
3)telnet远程终端协议:telnet是一个简单的远程终端协议,它也是因特网的正式标准。又称为终端仿真协议。
4)HTTP:超文本传送协议,是面向事务的应用层协议,它是万维网上能够可靠地交换文件的重要基础。http使用面向连接的TCP作为运输层协议,保证了数据的可靠传输。
5)电子邮件协议SMTP:即简单邮件传送协议。SMTP规定了在两个相互通信的SMTP进程之间应如何交换信息。SMTP通信的三个阶段:建立连接、邮件传送、连接释放。
6)POP3:邮件读取协议,POP3(Post Office Protocol 3)协议通常被用来接收电子邮件。
7)远程登录协议(Telnet):用于实现远程登录功能。
8)SNMP:简单网络管理协议。由三部分组成:SNMP本身、管理信息结构SMI和管理信息MIB。SNMP定义了管理站和代理之间所交换的分组格式。SMI定义了命名对象类型的通用规则,以及把对象和对象的值进行编码。MIB在被管理的实体中创建了命名对象,并规定类型。
Java 中的关键字都有哪些?
1)48个关键字:abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while。
2)2个保留字(目前未使用,以后可能用作为关键字):goto、const。
3)3个特殊直接量(直接量是指在程序中通过源代码直接给出的值):true、false、null。
Java 中基本类型都有哪些?
Java的类型分成两种,一种是基本类型,一种是引用类型。其中Java基本类型共有八种。
基本类型可以分为三大类:字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。
数值类型可以分为整数类型byte、short、int、long和浮点数类型float、double。
JAVA中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或操作系统的改变而改变。实际上《Thinking in Java》一书作者,提到Java中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,因为Void是不能new,也就是不能在堆里面分配空间存对应的值,所以将Void归成基本类型,也有一定的道理。
8种基本类型表示范围如下:
byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。
short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。
int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。
long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。
float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。
double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。
boolean:只有true和false两个取值。
char:16位,存储Unicode码,用单引号赋值。
为什么 Map 接口不继承 Collection 接口?
1)Map提供的是键值对映射(即Key和value的映射),而Collection提供的是一组数据并不是键值对映射。
2)若果Map继承了Collection接口,那么所实现的Map接口的类到底是用Map键值对映射数据还是用Collection的一组数据呢?比如平常所用的hashMap、hashTable、treeMap等都是键值对,所以它继承Collection是完全没意义,而且Map如果继承Collection接口的话,违反了面向对象的接口分离原则。
接口分离原则:客户端不应该依赖它不需要的接口。
另一种定义是类间的依赖关系应该建立在最小的接口上。
接口隔离原则将非常庞大、臃肿的接口拆分成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。
接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署,让客户端依赖的接口尽可能地小。
3)Map和List、Set不同,Map放的是键值对,List、Set存放的是一个个的对象。说到底是因为数据结构不同,数据结构不同,操作就不一样,所以接口是分开的,还是接口分离原则。
Collection 和 Collections 有什么区别?
java.util.Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。
Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。其直接继承接口有List与Set。
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
java.util.Collections是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
堆和栈的概念,它们有什么区别和联系?
在说堆和栈之前,我们先说一下JVM(虚拟机)内存的划分:
Java程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java虚拟机运行时也是要开辟空间的。JVM运行时在内存中开辟一片内存区域,启动时在自己的内存区域中进行更细致的划分,因为虚拟机中每一片内存处理的方式都不同,所以要单独进行管理。
JVM内存的划分有五片:
1)寄存器;
2)本地方法区;
3)方法区;
4)栈内存;
5)堆内存。
重点来说一下堆和栈:
栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。
比如主函数里的语句 int [] arr=new int [3];在内存中是怎么被定义的:
主函数先进栈,在栈中定义一个变量arr,接下来为arr赋值,但是右边不是一个具体值,是一个实体。实体创建在堆里,在堆里首先通过new关键字开辟一个空间,内存在存储数据的时候都是通过地址来体现的,地址是一块连续的二进制,然后给这个实体分配一个内存地址。数组都是有一个索引,数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化(这是堆内存的特点,未初始化的数据是不能用的,但在堆里是可以用的,因为初始化过了,但是在栈里没有),不同的类型初始化的值不一样。所以堆和栈里就创建了变量和实体:
那么堆和栈是怎么联系起来的呢?
刚刚说过给堆分配了一个地址,把堆的地址赋给arr,arr就通过地址指向了数组。所以arr想操纵数组时,就通过地址,而不是直接把实体都赋给它。这种我们不再叫他基本数据类型,而叫引用数据类型。称为arr引用了堆内存当中的实体。可以理解为c或“c++”的指针,Java成长自“c++”和“c++”很像,优化了“c++”
如果当int [] arr=null;
arr不做任何指向,null的作用就是取消引用数据类型的指向。
当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当做一个垃圾,在不定时的时间内自动回收,因为Java有一个自动回收机制,(而“c++”没有,需要程序员手动回收,如果不回收就越堆越多,直到撑满内存溢出,所以Java在内存管理上优于“c++”)。自动回收机制(程序)自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候收不一定。
所以堆与栈的区别很明显:
1)栈内存存储的是局部变量而堆内存存储的是实体;
2)栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
3)栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
Class.forName 和 ClassLoader 有什么区别?
在java中对类进行加载可以使用Class.forName()和ClassLoader。
ClassLoader遵循双亲委派模型,最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。
Class.forName()方法实际上也是调用的ClassLoader来实现的。
通过分析源码可以得出:最后调用的方法是forName()方法,方法中的第2个参数默认设置为true,该参数表示是否对加载的类进行初始化,设置为true时会对类进行初始化,这就意味着会执行类中的静态代码块以及对静态变量的赋值等操作。
也可以自行调用Class.forName(String name, boolean initialize,ClassLoader loader)方法手动选择在加载类的时候是否要对类进行初始化。
JDK源码中对参数initialize的描述是:if {@code true} the class will be initialized,大概意思是说:当值为true,则加载的类将会被初始化。
为什么要使用设计模式?
1)设计模式是前人根据经验总结出来的,使用设计模式,就相当于是站在了前人的肩膀上。
2)设计模式使程序易读。熟悉设计模式的人应该能够很容易读懂运用设计模式编写的程序。
3)设计模式能使编写的程序具有良好的可扩展性,满足系统设计的开闭原则。比如策略模式,就是将不同的算法封装在子类中,在需要添加新的算法时,只需添加新的子类,实现规定的接口,即可在不改变现有系统源码的情况下加入新的系统行为。
4)设计模式能降低系统中类与类之间的耦合度。比如工厂模式,使依赖类只需知道被依赖类所实现的接口或继承的抽象类,使依赖类与被依赖类之间的耦合度降低。
5)设计模式能提高代码的重用度。比如适配器模式,就能将系统中已经存在的符合新需求的功能代码兼容新的需求提出的接口 。
6)设计模式能为常见的一些问题提供现成的解决方案。
7)设计模式增加了重用代码的方式。比如装饰器模式,在不使用继承的前提下重用系统中已存在的代码。
为什么 String 类型是被 final 修饰的?
1、为了实现字符串池
final修饰符的作用:final可以修饰类,方法和变量,并且被修饰的类或方法,被final修饰的类不能被继承,即它不能拥有自己的子类,被final修饰的方法不能被重写, final修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。
String为什么要被final修饰主要是为了”安全性“和”效率“的原因。
final修饰的String类型,代表了String不可被继承,final修饰的char[]代表了被存储的数据不可更改性。虽然final修饰的不可变,但仅仅是引用地址不可变,并不代表了数组本身不会改变。
为什么保证String不可变呢?
因为只有字符串是不可变,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现,反之变量改变它的值,那么其它指向这个值的变量值也会随之改变。
如果字符串是可变,会引起很严重的安全问题。如数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接或在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则改变字符串指向的对象值,将造成安全漏洞。
2、为了线程安全
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
3、为了实现String可创建HashCode不可变性
因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。使得字符串很适合作为Map键值对中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
final 关键字的基本用法?
在Java中final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面从这三个方面来了解一下final关键字的基本用法。
1、修饰类
当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
2、修饰方法
下面这段话摘自《Java编程思想》第四版第143页:
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的。即父类的final方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。
final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写(可以重载多个final修饰的方法)。此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。(注:类的private方法会隐式地被指定为final方法。)
3、修饰变量
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。
final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。
如何理解 final 关键字?
1)类的final变量和普通变量有什么区别?
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
2)被final修饰的引用变量指向的对象内容可变吗?
引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的
3)final参数的问题
在实际应用中,我们除了可以用final修饰成员变量、成员方法、类,还可以修饰参数、若某个参数被final修饰了,则代表了该参数是不可改变的。如果在方法中我们修改了该参数,则编译器会提示你:
The final local variable i cannot be assigned. It must be blank and not using a compound assignment。
java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。
ArrayList 和 LinkedList 有什么区别?
1)ArrayList是Array动态数组的数据结构,LinkedList是Link链表的数据结构,此外,它们两个都是对List接口的实现。前者是数组队列,相当于动态数组;后者为双向链表结构,也可当作堆栈、队列、双端队列。
2)当随机访问List时(get和set操作),ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
3)当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。
4)从利用效率来看,ArrayList自由性较低,因为它需要手动设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。
5)ArrayList主要控件开销在于需要在List列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。
HashMap 和 HashTable 有什么区别?
Hashtable是线程安全,而HashMap则非线程安全。
Hashtable所有实现方法添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合。
HashMap允许使用null作为key,不过建议还是尽量避免使用null作为key。HashMap以null作为key时,总是存储在table数组的第一个节点上。而Hashtable则不允许null作为key。
HashMap继承了AbstractMap,HashTable继承Dictionary抽象类,两者均实现Map接口。
HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
HashMap扩容时是当前容量翻倍即:capacity*2,Hashtable扩容时是容量翻倍+1即:capacity*2+1。
HashMap和Hashtable的底层实现都是数组+链表结构实现。
线程的生命周期包括哪几个阶段?
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态。当线程启动以后,它不能一直占用着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、死亡。
新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源
运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
Thread 类中的 start() 和 run() 方法有什么区别?
Thread类中通过start()方法来启动一个线程,此时线程处于就绪状态,可以被JVM来调度执行,在调度过程中,JVM通过调用Thread类的run()方法来完成实际的业务逻辑,当run()方法结束后,此线程就会终止,所以通过start()方法可以达到多线程的目的。
如果直接调用线程类的run()方法,会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,即start()方法呢能够异步的调用run()方法,但是直接调用run()方法确实同步的,无法达到多线程的目的。
notify 和 notifyAll 有什么区别?
Java中提供了notify()和notifyAll()两个方法来唤醒在某些条件下等待的线程。
当调用notify()方法时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。
当调用notifyAll()方法时,等待该锁的所有线程都会被唤醒,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁。
如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的notifyAll()方法(唤醒所有 wait 线程)或notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争优先级高的线程竞争到对象锁的概率大,若某线程没有竞争到该对象锁,它将会留在锁池中,唯有线程再次调用wait()方法,才会重新回到等待池中。
而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,释放掉该对象锁,此时锁池中的线程会继续竞争该对象锁。
因此,notify()和notifyAll()之间的关键区别在于notify()只会唤醒一个线程,而notifyAll()方法将唤醒所有线程。
什么是乐观锁,什么是悲观锁?
乐观锁
乐观锁的意思是乐观思想,即认为读多写少,遇到并发写的可能性低,每次获取数据时都认为不会被修改,因此不会上锁,但是在更新操作时会判断有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读比较写的操作。
Java中的乐观锁基本上都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,反之失败。
悲观锁
悲观锁的意思是悲观思想,即认为写多,遇到并发写的可能性高,每次获取数据时都认为会被修改,因此每次在读写数据时都会上锁,在读写数据时就会block直到拿到锁。
Java中的悲观锁就是Synchronized、AQS框架下的锁则是先尝试CAS乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock。
Java 中 volatile 关键字有什么作用?
Java语言提供了弱同步机制,即volatile变量,以确保变量的更新通知其他线程。
volatile变量具备变量可见性、禁止重排序两种特性。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
volatile变量的两种特性:
变量可见性
保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的值对于其他线程是可以立即获取的。
禁止重排序
volatile禁止了指令重排。比sychronized更轻量级的同步锁。在访问volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。
volatile适合场景:一个变量被多个线程共享,线程直接给这个变量赋值。
当对非volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同CPU cache中。而声明变量是volatile的,JVM 保证了每次读变量都从内存中读,跳过CPU cache这一步。
适用场景
值得说明的是对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证“i++”这种操作的原子性,因为本质上i++是读、写两次操作。在某些场景下可以代替Synchronized。但是,volatile的不能完全取代Synchronized的位置,只有在一些特殊的场景下,才能适用volatile。
总体来说,需要必须同时满足下面两个条件时才能保证并发环境的线程安全:
1)对变量的写操作不依赖于当前值(比如 “i++”),或者说是单纯的变量赋值(boolean flag = true)。
2)该变量没有包含在具有其他变量的不变式中,也就是说,不同的volatile变量之间,不 能互相依赖。只有在状态真正独立于程序内其他内容时才能使用volatile。
Spring 中常用的注解包含哪些?
1)声明bean的注解
@Component 组件,没有明确的角色
@Service 在业务逻辑层使用(service层)
@Repository 在数据访问层使用(dao层)
@Controller 在展现层使用,控制器的声明(C*上使用)
2)注入bean的注解
@Autowired:由Spring提供
@Inject:由JSR-330提供
@Resource:由JSR-250提供
都可以注解在set方法和属性上,推荐注解在属性上(一目了然,少写代码)。
3)java配置类相关注解
@Configuration 声明当前类为配置类,相当于xml形式的Spring配置(类上使用)
@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上使用)
@Configuration 声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上使用)
@ComponentScan 用于对Component进行扫描,相当于xml中的(类上使用)
@WishlyConfiguration 为@Configuration与@ComponentScan的组合注解,可以替代这两个注解
4)切面(AOP)相关注解
Spring支持AspectJ的注解式切面编程。
@Aspect 声明一个切面(类上使用)
使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
@After 在方法执行之后执行(方法上使用)
@Before 在方法执行之前执行(方法上使用)
@Around 在方法执行之前与之后执行(方法上使用)
@PointCut 声明切点
在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上使用)
5)@Bean的属性支持
@Scope 设置Spring容器如何新建Bean实例(方法上使用,得有@Bean)
其设置类型包括:
Singleton (单例,一个Spring容器中只有一个bean实例,默认模式),
Protetype (每次调用新建一个bean),
Request (web项目中,给每个http request新建一个bean),
Session (web项目中,给每个http session新建一个bean),
GlobalSession(给每一个 global http session新建一个Bean实例)
@StepScope 在Spring Batch中还有涉及
@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod
@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod
6)@Value注解
@Value 为属性注入值(属性上使用)
7)环境切换
@Profile 通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。(类或方法上使用)
@Conditional Spring4中可以使用此注解定义条件话的bean,通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。(方法上使用)
8)异步相关
@EnableAsync 配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上使用)
@Async 在实际执行的bean方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)
9)定时任务相关
@EnableScheduling 在配置类上使用,开启计划任务的支持(类上使用)
@Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型(方法上,需先开启计划任务的支持)
10)@Enable*注解说明
注解主要用来开启对xxx的支持。
@EnableAspectJAutoProxy 开启对AspectJ自动代理的支持
@EnableAsync 开启异步方法的支持
@EnableScheduling 开启计划任务的支持
@EnableWebMvc 开启Web MVC的配置支持
@EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持
@EnableJpaRepositories 开启对SpringData JPA Repository的支持
@EnableTransactionManagement 开启注解式事务的支持
@EnableTransactionManagement 开启注解式事务的支持
@EnableCaching 开启注解式的缓存支持
11)测试相关注解
@RunWith 运行器,Spring中通常用于对JUnit的支持
@ContextConfiguration 用来加载配置ApplicationContext,其中classes属性用来加载配置类
Spring MVC 中常用的注解包含哪些?
@EnableWebMvc 在配置类中开启Web MVC的配置支持,如一些ViewResolver或者MessageConverter等,若无此句,重写WebMvcConfigurerAdapter方法(用于对SpringMVC的配置)。
@Controller 声明该类为SpringMVC中的Controller
@RequestMapping 用于映射Web请求,包括访问路径和参数(类或方法上)
@ResponseBody 支持将返回值放在response内,而不是一个页面,通常用户返回json数据(返回值旁或方法上)
@RequestBody 允许request的参数在request体中,而不是在直接连接在地址后面。(放在参数前)
@PathVariable 用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
@RestController 该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
@ControllerAdvice 通过该注解,我们可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,
这对所有注解了 @RequestMapping的控制器内的方法有效。
@ExceptionHandler 用于全局处理控制器里的异常
@InitBinder 用来设置WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中。
@ModelAttribute 本来的作用是绑定键值对到Model里,在@ControllerAdvice中是让全局的@RequestMapping都能获得在此处设置的键值对。
为什么说 MyBatis 是半自动 ORM 映射?
ORM是Object和Relation之间的映射,包括Object->Relation和Relation->Object两方面。Hibernate是个完整的ORM框架,而MyBatis完成的是Relation->Object,也就是其所说的Data Mapper Framework。
JPA是ORM映射标准,主流的ORM映射都实现了这个标准。MyBatis没有实现JPA,它和ORM框架的设计思路不完全一样。MyBatis是拥抱SQL,而ORM则更靠近面向对象,不建议写SQL,实在要写需用框架自带的类SQL代替。MyBatis是SQL映射而不是ORMORM映射,当然ORM和MyBatis都是持久层框架。
最典型的ORM映射是Hibernate,它是全自动ORM映射,而MyBatis是半自动的ORM映射。Hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成SQL。而MyBatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写SQL来实现和管理。
Hibernate数据库移植性远大于MyBatis。Hibernate通过它强大的映射结构和HQL语言,大大降低了对象与数据库(oracle、mySQL等)的耦合性,而MyBatis由于需要手写SQL,因此与数据库的耦合性直接取决于程序员写SQL的方法,如果SQL不具通用性而用了很多某数据库特性的SQL语句的话,移植性也会随之降低很多,成本很高。
main 方法中 args 参数是什么含义?
java中args即为arguments的缩写,是指字符串变量名,属于引用变量,属于命名,可以自定义名称也可以采用默认值,一般习惯性照写。
String[] args是main函数的形式参数,可以用来获取命令行用户输入进去的参数。
1)字符串变量名(args)属于引用变量,属于命名,可以自定义名称。
2)可以理解成用于存放字符串数组,若去掉无法知晓"args"声明的变量是什么类型。
3)假设public static void main方法,代表当启动程序时会启动这部分;
4)String[] args是main函数的形式参数,可以用来获取命令行用户输入进去的参数。
5)java本身不存在不带String args[]的main函数,java程序中去掉String args[]会出现错误。
什么是高内聚、低耦合?
内聚关注模块内部的元素结合程度,耦合关注模块之间的依赖程度。
1)内聚性
又称块内联系。指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一个模块内各元素(语名之间、程序段之间)联系的越紧密,则它的内聚性就越高。
所谓高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
2)耦合性
也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。
对于低耦合,粗浅的理解是:一个完整的系统,模块与模块之间,尽可能的使其独立存在。也就是说,让每个模块,尽可能的独立完成某个特定的子功能。模块与模块之间的接口,尽量的少而简单。如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。这样有利于修改和组合。
Spring Boot 框架的优缺点?
Spring Boot优点
1)创建独立的Spring应用程序
Spring Boot以jar包的形式独立运行,使用java -jar xx.jar命令运行项目或在项目的主程序中运行main方法。
2)Spring Boot内嵌入Tomcat,Jetty或者Undertow,无序部署WAR包文件
Spring项目部署时需要在服务器上部署tomcat,然后把项目打成war包放到tomcat中webapps目录。
Spring Boot项目不需要单独下载Tomcat等传统服务器,内嵌容器,使得可以执行运行项目的主程序main函数,让项目快速运行,另外,也降低对运行环境的基本要求,环境变量中有JDK即可。
3)Spring Boot允许通过maven工具根据需要获取starter
Spring Boot提供了一系列的starter pom用来简化我们的Maven依赖,通过这些starter项目就能以Java Application的形式运行Spring Boot项目,而无需其他服务器配置。
starter pom:
https://docs.spring.io/spring-boot/docs/1.4.1.RELEASE/reference/htmlsingle/#using-boot-starter
4)Spring Boot尽可能自动配置Spring框架
Spring Boot提供Spring框架的最大自动化配置,使用大量自动配置,使得开发者对Spring的配置减少。
Spring Boot更多的是采用Java Config的方式,对Spring进行配置。
5)提供生产就绪型功能,如指标、健康检查和外部配置
Spring Boot提供了基于http、ssh、telnet对运行时的项目进行监控;可以引入spring-boot-start-actuator依赖,直接使用REST方式来获取进程的运行期性能参数,从而达到监控的目的,比较方便。
但是Spring Boot只是微框架,没有提供相应的服务发现与注册的配套功能、监控集成方案以及安全管理方案,因此在微服务架构中,还需要Spring Cloud来配合一起使用,可关注微信公众号“Java精选”,后续篇幅会针对Spring Cloud面试题补充说明。
5)绝对没有代码生成,对XML没有要求配置
Spring Boot优点
1)依赖包太多,一个spring Boot项目就需要很多Maven引入所需的jar包
2)缺少服务的注册和发现等解决方案
3)缺少监控集成、安全管理方案
Spring Boot 核心注解都有哪些?
1)@SpringBootApplication*
用于Spring主类上最最最核心的注解,自动化配置文件,表示这是一个SpringBoot项目,用于开启SpringBoot的各项能力。
相当于@SpringBootConfigryation、@EnableAutoConfiguration、@ComponentScan三个注解的组合。
2)@EnableAutoConfiguration
允许SpringBoot自动配置注解,开启这个注解之后,SpringBoot就能根据当前类路径下的包或者类来配置Spring Bean。
如当前路径下有MyBatis这个Jar包,MyBatisAutoConfiguration 注解就能根据相关参数来配置Mybatis的各个Spring Bean。
3)@Configuration
Spring 3.0添加的一个注解,用来代替applicationContext.xml配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在的类来进行注册。
4)@SpringBootConfiguration
@Configuration注解的变体,只是用来修饰Spring Boot的配置而已。
5)@ComponentScan
Spring 3.1添加的一个注解,用来代替配置文件中的component-scan配置,开启组件扫描,自动扫描包路径下的@Component注解进行注册bean实例放到context(容器)中。
6)@Conditional
Spring 4.0添加的一个注解,用来标识一个Spring Bean或者Configuration配置文件,当满足指定条件才开启配置
7)@ConditionalOnBean
组合@Conditional注解,当容器中有指定Bean才开启配置。
8)@ConditionalOnMissingBean
组合@Conditional注解,当容器中没有值当Bean才可开启配置。
9)@ConditionalOnClass
组合@Conditional注解,当容器中有指定Class才可开启配置。
10)@ConditionalOnMissingClass
组合@Conditional注解,当容器中没有指定Class才可开启配置。
11)@ConditionOnWebApplication
组合@Conditional注解,当前项目类型是WEB项目才可开启配置。
项目有以下三种类型:
① ANY:任意一个Web项目
② SERVLET: Servlet的Web项目
③ REACTIVE :基于reactive-base的Web项目
12) @ConditionOnNotWebApplication
组合@Conditional注解,当前项目类型不是WEB项目才可开启配置。
13)@ConditionalOnProperty
组合@Conditional注解,当指定的属性有指定的值时才可开启配置。
14)@ConditionalOnExpression
组合@Conditional注解,当SpEl表达式为true时才可开启配置。
15)@ConditionOnJava
组合@Conditional注解,当运行的Java JVM在指定的版本范围时才开启配置。
16)@ConditionalResource
组合@Conditional注解,当类路径下有指定的资源才开启配置。
17)@ConditionOnJndi
组合@Conditional注解,当指定的JNDI存在时才开启配置。
18)@ConditionalOnCloudPlatform
组合@Conditional注解,当指定的云平台激活时才可开启配置。
19)@ConditiomalOnSingleCandidate
组合@Conditional注解,当制定的Class在容器中只有一个Bean,或者同时有多个但为首选时才开启配置。
20)@ConfigurationProperties
用来加载额外的配置(如.properties文件),可用在@Configuration注解类或者@Bean注解方法上面。可看一看Spring Boot读取配置文件的几种方式。
21)@EnableConfigurationProperties
一般要配合@ConfigurationProperties注解使用,用来开启@ConfigurationProperties注解配置Bean的支持。
22)@AntoConfigureAfter
用在自动配置类上面,便是该自动配置类需要在另外指定的自动配置类配置完之后。如Mybatis的自动配置类,需要在数据源自动配置类之后。
23)@AutoConfigureBefore
用在自动配置类上面,便是该自动配置类需要在另外指定的自动配置类配置完之前。
24)@Import
Spring 3.0添加注解,用来导入一个或者多个@Configuration注解修饰的配置类。
25)@IMportReSource
Spring 3.0添加注解,用来导入一个或者多个Spring配置文件,这对Spring Boot兼容老项目非常有用,一位内有些配置文件无法通过java config的形式来配置
Spring Boot 的目录结构是怎样的?
1、代码层的结构
根目录:com.springboot
1)工程启动类(ApplicationServer.java)置于com.springboot.build包下
2)实体类(domain)置于com.springboot.domain
3)数据访问层(Dao)置于com.springboot.repository
4)数据服务层(Service)置于com,springboot.service,数据服务的实现接口(serviceImpl)至于com.springboot.service.impl
5)前端控制器(Controller)置于com.springboot.controller
6)工具类(utils)置于com.springboot.utils
7)常量接口类(constant)置于com.springboot.constant
8)配置信息类(config)置于com.springboot.config
9)数据传输类(vo)置于com.springboot.vo
2、资源文件的结构
根目录:src/main/resources
1)配置文件(.properties/.json等)置于config文件夹下
2)国际化(i18n)置于i18n文件夹下
3)spring.xml置于META-INF/spring文件夹下
4)页面以及js/css/image等置于static文件夹下的各自文件下
Spring Boot 需要独立的容器运行吗?
Spring Boot项目可以不需要,内置了Tomcat/Jetty等容器,默认Tomcat。
Spring Boot不需要独立的容器就可以运行,因为在Spring Boot工程发布的jar文件里已经包含了tomcat插件的jar文件。
Spring Boot运行时创建tomcat对象实现web服务功能,另外也可以将Spring Boot编译成war包文件放到tomcat中运行。
Spring Boot 运行方式有哪几种?
1)直接执行main方法运行,通过IDE工具运行Application这个类的main方法
2)使用Maven插件spring-boot-plugin方式启动,在Spring Boot应用的根目录下运行mvn spring-boot:run
3)使用mvn install生成jar后通过java -jar命令运行
Spring Boot 自动配置原理是什么?
Spring Boot的启动类中使用了@SpringBootApplication注解,里面的@EnableAutoConfiguration注解是自动配置的核心,注解内部使用@Import(AutoConfigurationImportSelector.class)(class文件用来哪些加载配置类)注解来加载配置类,并不是所有的bean都会被加载,在配置类或bean中使用@Condition来加载满足条件的bean。
@EnableAutoConfiguration给容器导入META-INF/spring.factories中定义的自动配置类,筛选有效的自动配置类。每一个自动配置类结合对应的xxxProperties.java读取配置文件进行自动配置功能
Spring Boot 热部署有几种方式?
1)spring-boot-devtools
通过Springboot提供的开发者工具spring-boot-devtools来实现,在pom.xml引用其依赖。
然后在Settings→Build→Compiler中将Build project automatically勾选上,最后按ctrl+shift+alt+/ 选择registy,将compiler.automake.allow.when.app.running勾选。
2)Spring Loaded
Spring官方提供的热部署程序,实现修改类文件的热部署
下载Spring Loaded(项目地址https://github.com/spring-projects/spring-loaded)
添加运行时参数:-javaagent:C:/springloaded-1.2.5.RELEASE.jar –noverify
3)JRebel
收费的一个热部署软件,安装插件使用即可。
MyBatis 中 $ 和 # 传参有什么区别?
1)“#”符号将传入的数据当成一个字符串并将传入的数据加上双引号。
如:order by #{userId},如果传入的值是1,那么解析成sql时的值为order by “1”,如果传入的值是userId,则解析成的sql为order by “userId”。
2)“$”符号将传入的数据直接显示生成在sql语句中。
如:order by ${userId},如果传入的值是1,那么解析成sql时的值为order by 1, 如果传入的值是userId,则解析成的sql为order by userId。
3)“#”符号能够很大程度防止sql注入,而“$”符号无法防止sql注入。
4)“$”符号方式一般用于传入数据库对象,例如传入表名。
5)一般能用“#”符号的就别用“$”符号
6)MyBatis排序时使用order by动态参数时需要注意使用“$”符号而不是“#”符号。
MyBatis 如何实现分页?
1)相对原始方法,使用limit分页,需要处理分页逻辑:
MySQL数据库使用limit,如:
select * from table limit 0,10; --返回0-10行
Oracle数据库使用rownum,如:
从表Sys_option(主键为sys_id)中从第10条记录开始检索20条记录,语句如下:
SELECT * FROM (SELECT ROWNUM R,t1.* From Sys_option where rownum < 30 ) t2 Where t2.R >= 10
2)拦截StatementHandler,其实质还是在最后生成limit语句。
3)使用PageHelper插件,目前比较常见的方法。
MyBatis 如何获取自动生成的主键id?
数据插入时获得主键值分为两种情况:支持主键自增数据库和不支持主键自增。
1)对于支持自动生成主键的数据库,如Mysql、sqlServer,可以通过Mybatis元素useGeneratedKeys返回当前插入数据主键值到输入类中。
<insert id="insertTest" useGeneratedKeys="true" keyProperty="id" parameterType="com.kq.domain.IdentityTest"> insert into identity_test(name) values(#{name,jdbcType=VARCHAR}) </insert>
当执行此条插入语句以后,实体类IdentityTest中的Id会被当前插入数据的主键自动填充。
2)对于不支持自动生成主键的数据库。Oracle、DB2等,可以用元素selectKey 回当前插入数据主键值到输入类中。(同时生成一个自定义的随机主键)
<insert id="insertTest" useGeneratedKeys="true" keyProperty="id" parameterType="com.kq.domain.IdentityTest"> <selectKey keyProperty="id" resultType="String" order="BEFORE"> SELECT REPLACE(UUID(),'-','') </selectKey> insert into identity_test(name) values(#{name,jdbcType=VARCHAR}) </insert>
当执行此条插入语句以后,实体类IdentityTest中的Id也会被当前插入数据的主键自动填充。
TCP 和 UDP 协议有什么区别?
1)基于连接
TCP是面向连接的协议,而UDP是无连接的协议。即TCP面向连接;UDP是无连接的,即发送数据之前不需要建立连接。
2)可靠性和有序性
TCP 提供交付保证(Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输),无差错,不丢失,不重复,且按序到达,也保证了消息的有序性。该消息将以从服务器端发出的同样的顺序发送到客户端,尽管这些消息到网络的另一端时可能是无序的。TCP协议将会为你排好序。
UDP不提供任何有序性或序列性的保证。UDP尽最大努力交付,数据包将以任何可能的顺序到达。
TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
3)实时性
UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4)协议首部大小
TCP首部开销20字节;UDP的首部开销小,只有8个字节。
5)运行速度
TCP速度比较慢,而UDP速度比较快,因为TCP必须创建连接,以保证消息的可靠交付和有序性,毕竟TCP协议比UDP复杂。
6)拥塞机制
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低,对实时应用很有用,如IP电话,实时视频会议等。
7)流模式(TCP)与数据报模式(UDP)
TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 。
8)资源占用
TCP对系统资源要求较多,UDP对系统资源要求较少。
TCP被认为是重量级的协议,而与之相比,UDP协议则是一个轻量级的协议。因为UDP传输的信息中不承担任何间接创造连接,保证交货或秩序的的信息。这也反映在用于承载元数据的头的大小。
9)应用
每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 。基于UDP不需要建立连接,所以且适合多播的环境,UDP是大量使用在游戏和娱乐场所。
Integer 类型值是 0 ,为什么 != ‘’ 无法执行?
开发微信小程序“Java精选面试题”后台管理系统时,遇到根据状态判断是或否发布。
MySQL数据库中设计数据库表,其中某字段status使用tinyint数据类型,当修改状态的时候,赋值status属性的值为0,用于改变状态记录试题库中发布情况。
但是通过Debug模式查看Controller控制层明显已经获取到status等于0,但是在执行到MyBatis中xml文件SQL语句时,总是无法赋值成功,xml配置如下:
<update id="updateWarehouse" parameterType="Warehouse"> update t_warehouse <set> <if test="title != null and title != ''">title = #{title},</if> <if test="code != null and code != ''">code = #{code},</if> <if test="content != null and content != ''">content = #{content},</if> <if test="status != null and status != ''">status = #{status},</if> <if test="parentId != null and parentId != ''">parentId = #{parentId}</if> </set> where id = #{id} </update>
分析:
通过分析表面上没有任何传参问题,通过上网查询MyBatis相关资料,终于弄明白什么原因。
此行代码中
<if test="status != null and status != ''">status = #{status},</if>
and status != ‘’,MyBatis中传参status的值为0时,因为数据类型为Integer类型,判断为false值。
MyBatis认定 0 = ''的,因此判断status != ''为false,这导致修改试题信息时状态值无法改变为0。
正确写法:
<update id="updateWarehouse" parameterType="Warehouse"> update t_warehouse <set> <if test="title != null and title != ''">title = #{title},</if> <if test="code != null and code != ''">code = #{code},</if> <if test="content != null and content != ''">content = #{content},</if> <if test="status != null">status = #{status},</if> <if test="parentId != null and parentId != ''">parentId = #{parentId}</if> </set> where id = #{id} </update>
MySQL 的索引有哪些设计原则?
选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录
作为查询条件的字段建立索引
某个字段用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为该字段建立索引,可以提高整个表的查询速度
限制索引的数量
索引的数量并不是越多越好,每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
修改表时,索引过多会使得更新表速度变慢
尽量使用字段数据量少的索引
若索引的字段数据量过长,会导致查询的速度变慢
如:对一个char(200)类型的字段进行全文检索需要的时间肯定比对char(10)类型的字段需要的时间更多
排序、分组和联合字段建立索引
使用order by、group by、distinct和union等操作的字段进行排序操作会浪费很多时间。若为其建立索引,可以有效的避免排序操作
尽量使用前缀索引
索引字段的值很长,最好使用值的前缀来索引
如:text和blog类型的字段,进行全文检索会浪费时间。若只检索字段的部分若干个字符,可以提高检索速度
删除不使用或者很少使用的索引
表中数据被批量更新或数据的使用方式被改变后,原有的一些索引可能不再需要。应当定期清理这些索引
小表不创建索引(超过200w数据的表,创建索引)
包含很多列且不需要搜索非空值时可以考虑不建索引
被用来过滤记录的字段创建索引
primary key 字段,系统自动创建主键的索引
unique key 字段,系统自动创建对应的索引
foreign key 约束所定义的作为外键的字段
在查询中用来连接表的字段
用作为排序(order by的字段)的字段
创建索引必须考虑数据的操作方式,原则是内容变动少,经常被用于查询的字段创建索引,对于经常性变动的表而言,则需要谨慎创建必要的索引
为什么要使用自增 ID 作为主键?
1、若定义主键(PRIMARY KEY),InnoDB会选择主键作为聚集索引,反之未定义主键,则InnoDB会选择第一个不包含NULL值的唯一索引作为主键索引。
没有唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐含的聚集索引(ROWID随着行记录的写入而主键递增,这个ROWID不像ORACLE的ROWID那样可引用,是隐含的)。
2、数据记录本身被存于主索引(一颗B+Tree)的叶子节点上,这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放。
每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,若页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。
3、若表使用自增主键,则每次插入新的记录就会顺序添加到当前索引节点的后续位置,当写满一页就会自动开辟一个新的页。
4、若使用非自增主键,因为每次插入主键的值都近似于随机,所以每次新纪录都要被插到现有索引页得中间某个位置,此时MySQL将为新记录插到合适位置而移动数据,甚至可能被回写到磁盘上而从缓存中清除掉,此时又要从磁盘上读回来,这将增大了开销。同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续需通过OPTIMIZE TABLE来重建表并优化填充页面。
Linux 如何切换用户?
Linux系统切换用户的命令是su
su的含义是(switch user)切换用户的缩写。
通过su命令,可以从普通用户切换到root用户,也可以从root用户切换到普通用户。
注:从普通用户切换到root用户需要密码,从root用户切换到普通用户不需要密码。
使用SecureCRT工具连接终端,由普通用户切换到root用户。
在终端输入su命令然后回车,要求输入密码(linux终端输入的密码不显示)输入密码后回车进入root用户。
在终端输入su root然后回车,也可以进入root用户或su - root回车,也可以切换root用户。针对su root与su - root区别之后的篇幅会阐述,欢迎关注微信公众号“Java精选”,送免费资料。
su root 和 su - root 有什么区别?
su后面不加用户是默认切到root,su是不改变当前变量;su -是改变为切换到用户的变量。
简单来说就是su只能获得root的执行权限,不能获得环境变量,而su -是切换到root并获得root的环境变量及执行权限。
语法:
$ su [user_name]
su命令可以用来更改你的用户ID和组ID。su是switch user或set user id的一个缩写。
此命令是用于开启一个子进程,成为新的用户ID和赋予存取与这个用户ID关联所有文件的存取权限。
出于安全的考虑,在实际转换身份时,会被要求输入这个用户帐号的密码。
如果没有参数,su命令将转换为root(系统管理员)。root帐号有时也被称为超级用户,因为这个用户可以存取系统中的任何文件。
当必须要提供root密码,想要回到原先的用户身份,可以不使用su命令,只需使用exit命令退出使用su命令而生成的新的对话进程。
$ su – username
当使用命令su username时,对话特征和原始的登录身份一样。
如果需要对话进程拥有转换后的用户ID一致的特征,使用短斜杠命令: su – username。
Linux 怎么切换目录?
1)使用pwd命令查看一下当前所在的目录
2)切换文件目录使用的cd命令。
切换到根目录命令如下:
cd /
3)根目录下使用ls命令查看该目录下有哪些文件,使用用绝对路径的方式进入所需目录,比如进入usr目录,命令如下:
cd /usr
4)若进入文件下一层级目录,可以使用相对路径的方式切换目录。比如目录结构如下:
usr/local/other
在当前usr目录下使用命令
cd ./local
进入usr目录下的local目录。此命令和使用绝对路径的方式区别在于,前面多了个 “.”,这个"."代表的是当前目录。
5)若要回到上一级目录,则可以使用命令
cd ../
Dubbo 支持哪些协议,推荐用哪种?
dubbo协议(推荐使用)
单一TCP长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及服务消费者远大于提供者的情况。
缺点是Hessian二进制序列化,不适合传送大数据包的服务
rmi协议
采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口。
使用java标准序列化机制,使用阻塞式短连接,传输数据包不限,消费者和提供者个数相当。
多个短连接,TCP协议传输,同步传输,适用常规的远程服务调用和rmi互操作。
缺点是在依赖低版本的Common-Collections包,java反序列化存在安全漏洞,需升级commons-collections3 到3.2.2版本或commons-collections4到4.1版本。
webservice协议
基于WebService的远程调用协议(Apache CXF的frontend-simple和transports-http)实现,提供和原生WebService的互操作。
多个短连接,基于HTTP传输,同步传输,适用系统集成和跨语言调用。
http协议
基于Http表单提交的远程调用协议,使用Spring的HttpInvoke实现,对传输数据包不限,传入参数大小混合,提供者个数多于消费者。
缺点是不支持传文件,只适用于同时给应用程序和浏览器JS调用。
hessian协议
集成Hessian服务,基于底层Http通讯,采用Servlet暴露服务,Dubbo内嵌Jetty作为服务器实现,可与Hession服务互操作。
通讯效率高于WebService和Java自带的序列化。
适用于传输大数据包(可传文件),提供者比消费者个数多,提供者压力较大。
缺点是参数及返回值需实现Serializable接口,自定义实现List、Map、Number、Date、Calendar等接口
thrift协议
协议:对thrift原生协议的扩展添加了额外的头信息,使用较少,不支持传null值。
memcache协议
基于memcached实现的RPC协议。
redis协议
基于redis实现的RPC协议。
Dubbo 默认使用什么注册中心,还有别的选择吗?
推荐使用Zookeeper作为注册中心,还有Redis、Multicast、Simple注册中心,但不推荐。
为什么 Redis 需把数据放到内存中?
若不将数据读到内存中,磁盘I/O速度会严重影响Redis的性能。
Redis具有快速和数据持久化的特征。
Redis为了提高读写时的速度将数据读到内存中,并通过异步的方式将数据写入磁盘。
注:若设置最大使用内存,则数据已有记录数达到内存限值后不能继续插入新值。
为什么内存读取比磁盘读取数据速度快?
1)内存是电器元件,利用高低电平存储数据,而磁盘是机械元件,电气原件速度超快,而磁盘由于在每个磁盘块切换时磁头会消耗很多时间,说白了就是IO时间长,两者性能没发比较。
2)磁盘的数据进行操作时也是读取到内存中交由CPU进行处理操作,因此直接放在内存中的数据,读取速度肯定快很多。
Zookeeper 怎么保证主从节点的状态同步?
Zookeeper的核心是原子广播机制,这个机制保证了各个server之间的同步。实现这个机制的协议叫做Zab协议。
Zab协议有两种模式,分别是恢复模式和广播模式。
恢复模式
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数server完成了和leader的状态同步以后,恢复模式就结束了。
状态同步保证了leader和server具有相同的系统状态。
广播模式
一旦leader已经和多数的follower进行了状态同步后,它就可以开始广播消息了,即进入广播状态。
此时当一个server加入ZooKeeper服务中,它会在恢复模式下启动,发现leader,并和leader进行状态同步。
待到同步结束,它也参与消息广播。
ZooKeeper服务一直维持在Broadcast状态,直到leader崩溃了或者leader失去了大部分的followers支持。
Dubbo 停止更新了吗?
Dubbo是阿里巴巴内部使用的分布式业务框架,于2012年由阿里巴巴开源。
由于Dubbo在阿里巴巴内部经过广泛的业务验证,在很短时间内,Dubbo就被许多互联网公司所采用,并产生了许多衍生版本,如网易,京东,新浪,当当等等。
由于阿里巴巴内部策略的调整变化,在2014年10月Dubbo停止维护。随后部分互联网公司公开了自行维护的Dubbo版本,比较著名的如当当DubboX,新浪Motan等。
在2017年9月,阿里宣布重启Dubbo项目,并决策在未来对开源进行长期持续的投入。随后Dubbo开始了密集的更新,并将搁置三年以来大量分支上的特性及缺陷快速修正整合。
在2018年2月,阿里巴巴将Dubbo捐献给Apache基金会,Dubbo成为Apache孵化器项目。
为什么选用 Maven 进行构建?
1)Maven是一个优秀的项目构建工具。
使用Maven可以比较方便的对项目进行分模块构建,这样在开发或测试打包部署时,会大大的提高效率。
2)Maven可以进行依赖的管理。
使用Maven 可以将不同系统的依赖进行统一管理,并且可以进行依赖之间的传递和继承。
Maven可以解决jar包的依赖问题,根据JAR包的坐标去自动依赖/下载相关jar,通过仓库统一管理jar包。
多个项目JAR包冗余,使用Maven解决一致性问题。
4)屏蔽开发工具之间的差异,例如:IDE,Eclipse,maven项目可以无损导入其他编辑器。
Maven 规约是什么?
src/main/java 存放项目的类文件(后缀.java文件,开发源代码)
src/main/resources 存放项目配置文件,若没有配置文件该目录可无,如Spring、Hibernate、MyBatis等框架配置文件
src/main/webapp 存放web项目资源文件(web项目需要)
src/test/java 存放所有测试类文件(后缀.java文件,测试源代码)
src/test/resources 测试配置文件,若没有配置文件该目录可无
target 文件编译过程中生成的后缀.class文件、jar、war等
pom.xml maven项目核心配置文件,管理项目构建和依赖的Jar包
Maven负责项目的自动化构建,以编译为例,Maven若果自动进行编译,需要知道Java的源文件保存位置,通过这些规约,不用开发者手动指定位置,Maven就可以清晰的知道相关文件所在位置,从而完成自动编译。
遵循**“约定>>>配置>>>编码”**。即能进行配置的不要去编码指定,能事先约定规则的不要去进行配置。这样既减轻了工作量,也能防止编译出错。
Maven 常用命令有哪些?
1)mvn clean
清理输出目录默认target/
2)mvn clean compline
编译项目主代码,默认编译至target/classes目录下
3)mvn clean install
maven安装,将生成的JAR包文件复制到本地maven仓库中,其他项目可以直接使用这个JAR包
4)mvn clean test
maven测试,但实际执行的命令有:
clean:clean resource:resources compiler:compile resources:testResources compiler:testCompile
maven执行test前,先自动执行项目主资源处理,主代码编译,测试资源处理,测试代码编译等工作
测试代码编译通过之后默认在target/test-calsses目录下生成二进制文件,随后执行surefile:test任务运行测试,并输出测试报告,显示运行多少次测试,失败成功等。
5)mvn celan package
maven打包,maven会在打包之前默认执行编译,测试等操作,打包成功之后默认输出在target/目录中
6)mvn help:system
打印出java系统属性和环境变量。
7)echo %MAVEN_HOME%:
查看maven安装路径。
8)mvn deploy
在整合或发布环境下执行,将终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享。
9)mvn
检查是否安装了maven。
10)mvn dependency:list
查看当前项目中的已解析依赖
11)mvn dependency:tree
查看当前项目的依赖树
12)mvn dependency:analyse
查看当前项目中使用未声明的依赖和已声明但未使用的依赖
什么是链式存储结构?
链接存储结构的含义是在计算机中用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),它不要求逻辑上相邻的元素在物理位置上也相邻。
链式存储结构的优点主要是插入和删除简单,前提条件是知道操作位置,时间复杂度是O(1),如果不知道操作位置则要定位元素,时间复杂度为O(n),没有容量的限制,可以使用过程中动态分配的内存空间,不用担心溢出问题,但是它并不能实现随机读取,同时空间利用率不高。
说说几种常见的排序算法和复杂度?
1)快速排序
原理:快速排序采用的是一种分治的思想,通过依次排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
选定一个合适的值(理想情况下选择中间值最好,但实际中一般使用数组第一个值),称为“枢轴”(pivot)。
基于这个值将数组分为两部分,较小的分在左边,较大的分在右边,如此一轮下来,这个枢轴的位置一定在最终位置上。
对两个子数组分别重复上述过程,直到每个数组只有一个元素,排序完成。
复杂度:O(n)
特点:快速排序是平时最常使用的一种排序算法,因它速度快,效率高的一种排序算法。
2)冒泡排序
原理:冒泡排序采用的事两个相邻的值进行比较,将值大的交换到右侧。就是逐一比较交换,进行内外两次循环,外层循环为遍历所有数值,逐个确定每个位置,内层循环为确定位置后遍历所有后续没有确定位置的数字,与该位置的值进行比较,只要比该位置的值小,就位置交换。
复杂度:O(n^2),最佳时间复杂度为O(n)
特点:冒泡排序在实际开发中使用比较少,更适合数据量比较少的场景,因其效率比较低,但逻辑简单,方便记忆。
3)直接插入排序
原理:直接插⼊排序是从第二个数字开始,逐个取出,插入到之前排好序的数组中。
复杂度:O(n^2),最佳时间复杂度为O(n)
4)直接选择排序
原理:直接选择排序是从第一个位置开始遍历位置,找到剩余未排序的数组中最小值,将最小值做交换位置。
复杂度:O(n^2)
特点:类似冒泡排序其逻辑简单,但效率低,适合少量数据排序。
Java 递归遍历目录下的所有文件?
import java.io.File; public class ListFiles { public static void listAll(File directory) { if(!(directory.exists() && directory.isDirectory())) { throw new RuntimeException("目录不存在"); } File[] files = directory.listFiles(); for (File file : files) { System.out.println(file.getPath() + file.getName()); if(file.isDirectory()) { listAll(file); } } } public static void main(String[] args) { File directory = new File("E:\\Program Files (x86)"); listAll(directory); } }
JSP 获取 ModelAndView 传参数据问题?
Idea开发工具自动创建的web.xml约束太低,导致无法正常获取数据,需要把web.xml约束的信息调整一下,参考如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
Linux 运行 SQL 语句文件报错?
原因分析:Linux下MySQL版本不兼容导致的。
解决办法:把文件中所有的utf8mb4_0900_ai_ci替换为utf8_general_ci以及utf8mb4替换为utf8类型。
如何解决 Linux 显示中文乱码问题?
在Linux中通过locale来设置程序运行的不同语言环境,locale由 ANSI C提供支持。locale的命名规则为_.,如zh_CN.GBK,zh代表中文,CN代表大陆地区,GBK表示字符集。
修改 /etc/locale.conf文件的内容
LANG="zh_CN.UTF-8"
执行命令,使修改文件立刻生效
source /etc/locale.conf
IDEA 中 Maven 项目无法自动识别 pom.xml?
方式一
File->Settings->Build,Excecution,Deployment->Build Tools->Maven->Ignored Files
查看是否存在maven pom被勾选,去掉勾选即可。
方式二
右键项目pom.xml文件,选择“add as maven project”,自动导入pom所依赖的jar包。
刷新Maven配置
右键单击项目,在弹出菜单中选择Maven->Reimport菜单项。IDEA将通过网络自动下载相关依赖,并存放在Maven的本地仓库中。
或者将Maven的刷新设置为自动,单击File|Setting菜单项,打开Settings选项卡,在左侧的目录树中展开Maven节点,勾选Import Maven projects automatically选择项。
面向过程与面向对象有什么区别?
面向过程
性能相比面向对象高,因其类调用时需要实例化,开销比较大,消耗资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
面向对象
易维护、复用以及扩展,由于面向对象有封装、继承、多态性等特征,可以设计出低耦合、高内聚的系统,使得更加灵活,易于维护。
Java 编程语言有哪些特点?
1)简单易学;
2)面向对象(封装,继承,多态);
3)平台无关性(Java虚拟机实现平台无关性);
4)可靠性;
5)安全性;
6)支持多线程;
7)支持网络编程并方便易用;
8)编译与解释并存。
重载和重写有什么区别?
重载(Overload) 是指让类以统一的方式处理不同类型数据的一种手段,实质表现就是多个具有不同的参数个数或者不同类型的同名函数,存在于同一个类中,返回值类型不同,是一个类中多态性的一种表现。
调用方法时通过传递不同参数个数和参数类型来决定具体使用哪个方法的多态性。
重写(Override) 是指父类与子类之间的多态性,实质就是对父类的函数进行重新定义。
如果子类中定义某方法与其父类有相同的名称和参数则该方法被重写,需注意的是子类函数的访问修饰权限不能低于父类的。
如果子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,如需父类中原有的方法则可使用super关键字。
静态方法和实例方法有什么不同?
静态方法和实例方法的区别主要体现在两个方面:
其一在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式而实例方法只能试用后面这种方式。也就是说,调用静态方法可以无需创建对象进行实例化。
其二静态方法在访问本类的成员时,只允许访问静态成员也就是静态成员变量和静态方法,而不允许访问实例成员变量和实例方法,实例方法是没有这个限制的。
== 和 equals 两者有什么区别?
使用==比较
用于对比基本数据类型的变量,是直接比较存储的 “值”是否相等;
用于对比引用类型的变量,是比较的所指向的对象地址。
使用equals比较
equals方法不能用于对比基本数据类型的变量;
如果没对Object中equals方法进行重写,则是比较的引用类型变量所指向的对象地址,反之则比较的是内容。
HashMap 是怎么扩容的?
当HashMap中元素个数超过数组大小*loadFactor时,需进行数组扩容。
loadFactor默认值为0.75,默认情况下,数组大小为16,HashMap中元素个数超过16 * 0.75=12的时,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以能够预选知道HashMap中元素的个数,应该预设数组的大小,可以有效的提高HashMap的性能。
假设有1000个元素new HashMap(1000),理论上来讲new HashMap(1024)更合适,不过上面已经提过即使是1000个元素,HashMap也会自动设置为1024。但是new HashMap(1024),而0.75*1024 <1000, 为了可以0.75 * size >1000,必须new HashMap(2048),避免了resize的问题。
总结:
添加元素时会检查容器当前元素个数。当HashMap的容量值超过临界值(默认16 * 0.75=12)时扩容。HashMap将会重新扩容到下一个2的指数幂(16->32->64)。调用resize方法,定义长度为新长度(32)的数组,然后对原数组数据进行再Hash。注意的是这个过程比较损耗性能。
JDK1.8 和 JDK1.7 中 ArrayList 的初始容量多少?
JDK1.7下ArrayList()初始化后的默认长度是10,源码如下:
//无参构造方法 public ArrayList() { this(10); } public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; }
通过上述源代码可以看出,默认的构造方法中直接指定数组长度为10,同时调用重载的构造方法,创建了长度为10的一个数组。
JDK1.8下ArrayList()初始化后的默认长度是0,源码如下:
//无参构造方法 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
构造方法中静态类型的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}是个空数组,数组长度为0,因此JDK1.8下的ArrayList()初始化后默认的数组长度为0。
Arrays.asList() 有什么使用限制?
1)Arrays.asList()方法不适用于基本数据类型
byte short int long float double boolean
2)Arrays.asList()方法把数组与列表链接起来,当更新其中之一时,另一个自动更新。
3)Arrays.asList()方法不支持add和remove方法。
Set 为什么是无序的?
Set系列集合添加元素无序的根本原因是底层采用哈希表存储元素。
JDK1.8以下版本:哈希表 = 数组 + 链表 + (哈希算法)
JDK1.8及以上版本:哈希表 = 数组 + 链表 + 红黑树 + (哈希算法)
当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
Comparable 和 Comparator有什么区别?
Comparable接口出自java.lang包,它有一个compareTo(Object obj)方法用来排序。
Comparator接口出自java.util包,它有一个compare(Object obj1, Object obj2)方
法用来排序。
一般对集合使用自定义排序时,需要重写compareTo()方法或compare()方法。
当需要对某一个集合实现两种排序方式,比如一个用户对象中的姓名和身份证分别采用一种排序方法。
方式一:重写compareTo()方法实现姓名、身份证排序
方式二:使用自定义的Comparator方法实现姓名、身份证排序
方法三:使用两个Comparator来实现姓名、身份证排序
其中方式二代表只能使用两个参数的形式Collections.sort()。
Collections是一个工具类,sort是其中的静态方法,是用来对List类型进行排序的,它有两种参数形式:
public static <T extends Comparable<? super T>> void sort(List<T> list) { list.sort(null); } public static <T> void sort(List<T> list, Comparator<? super T> c) { list.sort(c); }
HashMap 中如何实现同步?
HashMap可以使用如下代码实现:
Map map = Collections.synchronizedMap(new HashMap());
来达到同步的效果。
具体而言,该方法会返回一个同步的Map集合,这个Map封装了底层HashMap的所有方法,使得底层的HashMap可以在多线程的环境中也能够保证安全性。
List、Set、Map 三者有什么区别?
List
存储数据允许不唯一集合,可以有多个元素引用相同的对象且是有序的对象。
Set
不允许重复的集合,不存在多个元素引用相同的对象。
Map
使用键值对存储方式。Map维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,比如Key是String类型,但也可以是任何对象。
多线程实现的方式有几种?
1)继承Thread类创建线程
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。
启动线程的唯一方法是通过Thread类的start()实例方法。
start()方法将启动一个新线程,并执行run()方法。
这种方式实现多线程比较简单,通过自己的类直接继承Thread,并重写run()方法,就可以启动新线程并执行自己定义的run()方法。
2)实现Runnable接口创建线程
如果自己的类已经继承了两一个类,就无法再继承Thread,因此可以实现一个Runnable接口
3)实现Callable接口,通过FutureTask包装器来创建Thread线程
4)使用ExecutorService
、Callable
、Future
实现有返回结果的线程
ExecutorService
、Callable
、Future
三个接口实际上都是属于Executor框架。
在JDK1.5中引入的新特征,不需要为了得到返回值而大费周折。主要需要返回值的任务必须实现Callable接口;不需要返回值的任务必须实现Runnabel接口。
执行Callable任务后,可以获取一个Future对象,在该对象上调用get()方法可以获取到Callable任务返回的Object了。注意的是get()方法是阻塞的,线程没有返回结果时,该方法会一直等待。
什么是线程局部变量?
ThreadLocal并非是一个线程本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更合适。
线程局部变量(ThreadLocal)功能非常简单,就是为每一个使用该变量的线程都提供了一个变量值副本,是Java中一种较为特殊的线程绑定机制,使得每一个线程都独立地改变所具有的副本,而不会和其他线程的副本冲突。
Java 中常见的阻塞队列有哪些?
ArrayBlockingQueue
最典型的有界队列,其内部是用数组存储元素的,利用ReentrantLock实现线程安全,使用Condition来阻塞和唤醒线程。
LinkedBlockingQueue
内部用链表实现BlockingQueue。如果不指定它的初始容量,那么它容量默认就为整型的最大值Integer.MAX_VALUE,由于这个数非常大,通常不可能放入这么多的数据,所以LinkedBlockingQueue 也被称作***队列,代表它几乎没有界限。
SynchronousQueue
相比较其他,最大的不同之处在于它的容量为0,所以没有地方暂存元素,导致每次存储或获取数据都要先阻塞,直至有数据或有消费者获取数据。
PriorityBlockingQueue
支持优先级的***阻塞队列,可以通过自定义类实现compareTo()方法来指定元素排序规则或初始化时通过构造器参数Comparator来指定排序规则。需主要的是插入队列的对象必须是可以比较大小的值,否则会抛出ClassCastException异常。
DelayQueue
具有“延迟”的功能。队列中的可以设定任务延迟多久之后执行,比如“30分钟后未付款自动取消订单”等需要执行的场景。
创建线程池的有几种方式?
newCachedThreadPool
创建一个可缓存的线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,如果没有可回收线程,则新建线程。
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor
创建一个单线程化的线程池,它只会唯一的工作线程来执行任务,保证所有任务按照指定执行。
查看文件内容有哪些命令?
vi 文件名
编辑方式查看,可以修改文件
cat 文件名
显示全部文件内容
tail 文件名
仅查看文件尾部信息,可以指定查看行数
head 文件名
仅查看头部,可以指定查看行数
more 文件名
分页显示文件内容
less 文件名
与more相似,可以往前翻页
命令中可以使用哪几种通配符?
“?”
可以替代任意单个字符。
“*”
可以替代任意多个字符。
方括号“[charset]”
可以替代charset集中的任何单个字符,比如[a-z],[abABC]
根据文件名搜索文件有哪些命令?
find <指定目录> <指定条件> <指定动作>,
whereis 加参数与文件名
locate 只加文件名
bash shell 中 hash 命令有什么作用?
Linux中hash命令管理着一个内置的哈希表,记录了已执行过命令的完整路径, 用该命令可以打印出所使用过的命令以及执行的次数。
Linux 中进程有哪几种状态?
1)不可中断状态:进程处于睡眠状态,此时的进程不可中断,进程不响应异步信号。
2)暂停状态/跟踪状态:向进程发送一个SIGSTOP信号,它就会因响应信号而进入TASK_STOPPED状态;当进程正在被跟踪时,它处于TASK_TRACED这个特殊的状态。正在被跟踪指的是进程暂停下来,等待跟踪它的进程对它进行操作。
3)就绪状态:在run_queue队列中的状态
4)运行状态:在run_queue队列中的状态
5)可中断睡眠状态:处于这个状态的进程因为等待某事件的发生,比如等待socket连接等,而被挂起
6)zombie状态(僵尸):父进程没有通过wait系列的系统调用,直接将子进程的task_struct也释放掉。
7)退出状态
Integer 和 int 两者有什么区别?
Integer是int的包装类,默认值是null;int是基本数据类型,默认值是0;
Integer变量必须实例化后才能使用;int变量不需要;
Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值。
分析总结
1)Integer与new Integer不相等。new出来的对象被存放在堆,而非new的Integer常量则在常量池,两者内存地址不同,因此判断是false。
2)两个值都是非new Integer,如果值在-128,127区间,则是true,反之为false。
这是因为java在编译Integer i2 = 128时,被翻译成:
Integer i2 = Integer.valueOf(128);
而valueOf()函数会对-128到127之间的数进行缓存。
3)两个都是new Integer,两者判断为false,内存地址不同。
4)int和Integer对比不管是否new对象,两者判断都是true,因为会把Integer自动拆箱为int再去比。
什么是 Java 内部类?
内部类是指把A类定义在另一个B类的内部。
例如:把类User定义在类Role中,类User就被称为内部类。
class Role { class User { } }
1、内部类的访问规则
1)可以直接访问外部类的成员,包括私有
2)外部类要想访问内部类成员,必须创建对象
2、内部类的分类
1)成员内部类
2)局部内部类
3)静态内部类
4)匿名内部类
常用的垃圾收集器有哪些?
Serial 收集器(新生代)
Serial即串行,以串行的方式执行,是单线程的收集器。它只会使用一个线程进行垃圾收集工作,GC线程工作时,其它所有线程都将停止工作。
作为新生代垃圾收集器的方式:-XX:+UseSerialGC
ParNew 收集器(新生代)
Serial收集器的多线程版本,需注意的是ParNew在单核环境下性能比Serial差,在多核条件下有优势。
作为新生代垃圾收集器的方式:-XX:+UseParNewGC
Parallel Scavenge 收集器(新生代)
多线程的收集器,目标是提高吞吐量(吞吐量 = 运行用户程序的时间 / (运行用户程序的时间 + 垃圾收集的时间))。
作为新生代垃圾收集器的方式:-XX:+UseParallelGC
Serial Old 收集器(老年代)
Serial收集器的老年代版本。
作为老年代垃圾收集器的方式:-XX:+UseSerialOldGC
Parallel Old 收集器(老年代)
Parallel Scavenge收集器的老年代版本。
作为老年代垃圾收集器的方式:-XX:+UseParallelOldGC
CMS 收集器(老年代)
CMS(Concurrent Mark Sweep),收集器几乎占据着JVM老年代收集器的半壁江山,它划时代的意义就在于垃圾回收线程几乎能做到与用户线程同时工作。
作为老年代垃圾收集器的方式:-XX:+UseConcMarkSweepGC
G1 收集器(新生代 + 老年代)
面向服务端应用的垃圾收集器,在多CPU和大内存的场景下有很好的性能。HotSpot开发团队赋予它的使命是未来可以替换掉CMS收集器。关注微信公众号Java精选,内涵视频资料、开源项目、源码分析等等。
作为老年代垃圾收集器的方式:-XX:+UseG1GC
生产环境中应用的 JVM 参数有哪些?
-server
-Xms6000M
-Xmx6000M
-Xmn500M
-XX:PermSize=500M
-XX:MaxPermSize=500M
-XX:SurvivorRatio=65536
-XX:MaxTenuringThreshold=0
-Xnoclassgc
-XX:+DisableExplicitGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled
-XX:-CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=90
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log
什么情况下会发生栈内存溢出?
栈溢出(StackOverflowError)
栈是线程私有的,他的生命周期与线程相同,每个方法在执行时会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口灯信息。局部变量表又包含基本数据类型,对象引用类型。
若线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
若虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
常用的 JVM 调优配置参数有哪些?
假设参数为-Xms20m -Xmx20m -Xss256k
XX比X的稳定性更差,并且版本更新不会进行通知和说明。
1、-Xms
s为strating,表示堆内存起始大小
2、-Xmx
x为max,表示最大的堆内存,一般来说-Xms和-Xmx的设置为相同大小,当heap自动扩容时,会发生内存抖动,影响程序的稳定性
3、-Xmn
n为new,表示新生代大小,-Xss规定了每个线程虚拟机栈(堆栈)的大小。
4、-XX:SurvivorRator=8
表示堆内存中新生代、老年代和永久代的比为8:1:1
5、-XX:PretenureSizeThreshold=3145728
表示当创建(new)的对象大于3M的时候直接进入老年代
6、-XX:MaxTenuringThreshold=15
表示当对象的存活的年龄(minor gc一次加1)大于多少时,进入老年代
7、-XX:-DisableExplicirGC
表示是否(+表示是,-表示否)打开GC日志
什么是类加载器?
类加载器是指把类文件加载到虚拟机中,通过一个类的全限定名(包名+类型)来获取描述该类的二进制字节流。
类加载器是Java语言的一项创新,最开始是为了满足Java Applet的需求而设计的。
类加载器目前在层次划分、程序热部署和代码加密等领域被广泛使用。
类加载器分为哪几类?
JVM默认提供了系统类加载器(JDK1.8),包括如下:
Bootstrap ClassLoader(系统类加载器)
Application ClassLoader(应用程序类加载器)
Extension ClassLoader(扩展类加载器)
Customer ClassLoader(自定义加载器)
可以自定义一个 java.lang.String 吗?
答案是可以的,但是不能被加载使用。
这个主要是因为加载器的委托机制,在类加载器的结构图中,BootStrap是顶层父类,ExtClassLoader是BootStrap类的子类,ExtClassLoader是AppClassLoader的父类。当使用java.lang.String类时,Java虚拟机会将java.lang.String类的字节码加载到内存中。
加载某个类时,优先使用父类加载器加载需要使用的类。加载自定义java.lang.String类,其使用的加载器是AppClassLoader,根据优先使用父类加载器原理,AppClassLoader加载器的父类为ExtClassLoader,这时加载String使用的类加载器是ExtClassLoader,但类加载器ExtClassLoader在jre/lib/ext目录下并不能找到自定义java.lang.String类。
然后使用ExtClassLoader父类的加载器BootStrap,父类加载器BootStrap在JRE/lib目录的rt.jar找到了String.class,将其加载到内存中。
MyBatis 实现批量插入数据的方式有几种?
MyBatis 实现批量插入数据的方式有几种?
1、MyBatis foreach标签
foreach主要用在构建in条件,在SQL语句中进行迭代一个集合。
foreach元素的属性主要有item,index,collection,open,separator,close。
item表示集合中每一个元素进行迭代时的别名
index指定一个名字,用于表示在迭代过程中,每次迭代到的位置
open表示该语句以什么开始
separator表示在每次进行迭代之间以什么符号作为分隔符
close表示以什么结束
collection必须指定该属性,在不同情况下,值是不同的,主要体现3种情况:
若传入单参数且参数类型是List时,collection属性值为list
若传入单参数且参数类型是array数组时,collection的属性值为array
若传入参数是多个时,需要封装成Map
具体用法如下:
<insert id="insertForeach" parameterType="java.util.List" useGeneratedKeys="false"> insert into t_userinfo (name, age, sex) values <foreach collection="list" item="item" index="index" separator=","> (#{item.name},#{item.age},#{item.sex}) </foreach> </insert>
2、MyBatis ExecutorType.BATCH
Mybatis内置ExecutorType,默认是simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql。关注微信公众号Java精选,内涵视频资料、开源项目、源码分析等等。
batch模式会重复使用已经预处理的语句,并批量执行所有更新语句。但batch模式Insert操作时,在事务没有提交前,是无法获取到自增的id。
什么是自动装箱?什么是自动拆箱?
自动装箱是指将基本数据类型重新转化为对象。
public class Test { public static void main(String[] args) { Integer num = 9; } }
num = 9的值是属于基本数据类型,原则上不能直接赋值给对象Integer。但是在JDK1.5版本后就可以进行这样的声明自动将基本数据类型转化为对应的封装类型,成为对象后可以调用对象所声明的方法。
自动拆箱是指将对象重新转化为基本数据类型。
public class Test { public static void main(String[] args) { // 声明Integer对象 Integer num = 9; // 隐含自动拆箱 System.out.print(num--); } }
由于对象不能直接进行运算,而是需要转化为基本数据类型后才能进行加减乘除。
// 装箱 Integer num = 10; // 拆箱 int num1 = num;
一级缓存和二级缓存有什么区别?
一级缓存
Mybatis一级缓存是指SQLSession
一级缓存的作用域是SQlSession
Mabits默认开启一级缓存
同一个SqlSession中,执行相同的SQL查询时第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。
当执行SQL时两次查询中间发生了增删改的操作,则SQLSession的缓存会被清空。
每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中。
Mybatis的内部缓存使用一个HashMap,其中key为hashcode+statementId+sql语句,Value为查询出来的结果集映射成的java对象。
SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存。
二级缓存
二级缓存是mapper级别的,Mybatis默认是没有开启二级缓存的。
第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放到mapper对应的二级缓存区域。
第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。
Redis 支持那些数据类型?
String字符串
命令: set key value
string类型是二进制安全的,它可以包含任何数据,如图片或序列化对象等。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
Hash(哈希)
命令: hmset name key1 value1 key2 value2
Redis hash是一个键值(key=>value)对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)
命令: lpush name value
key对应list的头部添加字符串元素
命令: rpush name value
key对应list的尾部添加字符串元素
命令: lrem name index
key对应list中删除count个和value相同的元素
命令: llen name
返回key对应list的长度
Set(集合)
命令: sadd name value
Redis的Set是string类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
zset(sorted set:有序集合)
命令: zadd name score value
Redis zset和set一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
什么是 Redis 持久化?Redis 有哪几种持久化方式?
Redis持久化是指把内存的数据写到磁盘中去,防止服务因宕机导致内存数据丢失。
Redis提供了两种持久化方式:RDB(默认)和AOF。
RDB是Redis DataBase缩写,功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数
AOF是Append-only file缩写,每当执行服务器(定时)任务或者函数时flushAppendOnlyFile函数都会被调用,这个函数执行以下两个工作。
AOF写入与保存
WRITE:根据条件,将aof_buf中的缓存写入到AOF文件
SAVE:根据条件,调用fsync或fdatasync函数,将AOF文件保存到磁盘中。
什么是缓存穿透?如何避免?
缓存穿透是指一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。
一些恶意的请求会故意查询不存在的key,导致请求量大,造成后端系统压力大,这就是做缓存穿透。
如何避免?
1)对查询结果为空的情况也进行缓存,缓存时间设置短一点或key对应的数据insert后清理缓存。
2)对一定不存在的key进行过滤,可以把所有可能存在的key放到一个大的Bitmap中,查询时通过bitmap过滤。
什么是缓存雪崩?何如避免?
缓存雪崩是指当缓存服务器重启或大量缓存集中在某一个时间段内失效,这样在失效时,会给后端系统带来很大压力,导致系统崩溃。
如何避免?
1)在缓存失效后,使用加锁或队列的方式来控制读数据库写缓存的线程数量。如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2)实现二级缓存方式,A1为原始缓存,A2为拷贝缓存,A1失效时,切换访问A2,A1缓存失效时间设置为短期,A2设置为长期。
3)不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
MyBatis 是否支持延迟加载?其原理是什么?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载。
association指的就是一对一
collection指的就是一对多查询
在Mybatis配置文件中,启用延迟加载配置参数
lazyLoadingEnabled=true。
原理:使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法。
比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,就会单独发送事先保存好的查询关联B对象的SQL语句,先查询出B,然后再调用a.setB(b)赋值,最后再调用a.getB().getName()方法就有值了。几乎所有的包括Hibernate、Mybatis,支持延迟加载的原理都是一样的。
如何解决 MyBatis 转义字符的问题?
xml配置文件使用转义字符
SELECT * FROM test WHERE crate_time <= #{crate_time} AND end_date >= #{crate_time}
xml转义字符关系表
字符 | 转义 | 备注 |
---|---|---|
< | < | 小于号 |
> | > | 大于号 |
& | & | 和 |
’ | ' | 单引号 |
" | " | 双引号 |
注意:XML中只有”<”和”&”是非法的,其它三个都是合法存在的,使用时都可以把它们转义了,养成一个良好的习惯。
转义前后的字符都会被xml解析器解析,为了方便起见,使用
<![CDATA[...]]>
来包含不被xml解析器解析的内容。标记所包含的内容将表示为纯文本,其中…表示文本内容。
但要注意的是:
1)此部分不能再包含”]]>”;
2)不允许嵌套使用;
3)”]]>”这部分不能包含空格或者换行。
Zookeeper 是如何保证事务的顺序一致性的?
Zookeeper采用了全局递增的事务Id来标识,所有的proposal(提议)都在被提出的时候加上了zxid。
zxid实际上是一个64位的数字,高32位是epoch用来标识leader的周期。
如果有新的leader产生出来,epoch会自增,低32位用来递增计数。
当新产生proposal的时候,会依据数据库的两阶段过程,首先会向其他的server发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。
Zookeeper 有哪几种部署模式?
部署模式:单机模式、伪集群模式、集群模式。
Zookeeper 集群最少要几台服务器,什么规则?
集群最少要3台服务器,集群规则为2N+1台,N>0,即3台。
Zookeeper 有哪些典型应用场景?
Zookeeper是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发者可以使用它来进行分布式数据的发布和订阅。
通过对Zookeeper中丰富的数据节点进行交叉使用,配合Watcher事件通知机制,可以非常方便的构建一系列分布式应用中年都会涉及的核心功能。应用场景如下:
1)数据发布/订阅
2)负载均衡
3)命名服务
4)分布式协调/通知
5)集群管理
6)Master选举
7)分布式锁
8)分布式队列
Paxos 和 ZAB 算法有什么区别和联系?
相同点
两者都存在一个类似于Leader进程的角色,由其负责协调多个Follower进程的运行;
Leader进程都会等待超过半数的Follower做出正确的反馈后,才会将一个提案进行提交;
ZAB协议中每个Proposal中都包含一个epoch值来代表当前的Leader周期,而Paxos中名字为Ballot。
不同点
Paxos是用来构建分布式一致性状态机系统,而ZAB用来构建高可用的分布式数据主备系统(Zookeeper)。
Zookeeper 中 Java 客户端都有哪些?
Java客户端:
1)zk自带的zkclient
2)Apache开源的Curator
Zookeeper 集群支持动态添加服务器吗?
动态添加服务器等同于水平扩容,而Zookeeper在这方面的表现并不是太好。两种方式:
1)全部重启
关闭所有Zookeeper服务,修改配置之后启动。不影响之前客户端的会话。
2)逐一重启
在过半存活即可用的原则下,一台机器重启不影响整个集群对外提供服务。这是比较常用的方式,在Zookeeper 3.5版本开始支持动态扩容。
Zookeeper 和 Nginx 的负载均衡有什么区别?
Zookeeper的负载均衡是可以调控,而Nginx的负载均衡只是能调整权重,其他可控的都需要自己写Lua插件;但是Nginx的吞吐量比Zookeeper大很多,具体按业务应用场景选择用哪种方式。
Zookeeper 节点宕机如何处理?
Zookeeper本身是集群模式,官方推荐不少于3台服务器配置。Zookeeper自身保证当一个节点宕机时,其他节点会继续提供服务。
假设其中一个Follower宕机,还有2台服务器提供访问,因为Zookeeper上的数据是有多个副本的,并不会丢失数据;
如果是一个Leader宕机,Zookeeper会选举出新的Leader。
Zookeeper集群的机制是只要超过半数的节点正常,集群就可以正常提供服务。只有Zookeeper节点挂得太多,只剩不到一半节点提供服务,才会导致Zookeeper集群失效。
Zookeeper集群节点配置原则
3个节点的cluster可以挂掉1个节点(leader可以得到2节 > 1.5)。
2个节点的cluster需保证不能挂掉任何1个节点(leader可以得到 1节 <=1)。
Socket 前后端通信是如何实现服务器集群?
假设有两台A服务器服务S1和B服务器服务S2,应用服务S0
首先你接收客户端(浏览器)请求的服务肯定是单点(一对一)连接,之后交给应用服务S0处理分配给两台服务器A和B上的服务S1和S2。
分配原则可以按一定的策略,如计数器等,按当前活跃连接数来决定分给哪台服务器。也可以更科学的按两台服务器实际处理的数据量来分配,因为有些连接可能一直空闲。
如果两台服务器上的服务通信正常且数据库能够承受压力,访问请求并不是太多的情况下,可以考虑使用数据库传递消息,反之可以考虑使用Redis缓存技术。
如果需要即时传递消息,在其中一个服务器上的服务S1查找不到,把消息发给另外一台服务器上的服务S2或把消息存储至数据库或缓存当中,然后通知其他服务有生产消息。类似MQ处理方式可参考ActiveMQ。
为什么要用 Redis 而不用 Map、Guava 做缓存?
缓存可以划分为本地缓存和分布式缓存。
以Java为例,使用自带Map或者Guava类实现的是本地缓存,主要特点是轻量以及快速,它们的生命周期会随着JVM的销毁而结束,且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
使用Redis或Memcached等被称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,具有一致性,但是需要保持Redis或Memcached服务的高可用。
Redis 是单线程的吗?为什么这么快?
Redis是单线程的,至于为什么这么快主要是因如下几方面:
1)Redis是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快。
2)Redis使用的是非阻塞IO、IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
3)Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
4)Redis避免了多线程的锁的消耗。
5)Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大
为什么使用消息队列?
消息队列中间件有很多,使用的场景广泛。主要解决的核心问题是削峰填谷、异步处理、模块解耦。
RabbitMQ 有几种广播类型?
direct(默认方式):最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,默认采取轮询的方式进行消息发送。
headers:与direct类似,只是性能很差,此类型几乎用不到。
fanout:分发模式,把消费分发给所有订阅者。
topic:匹配订阅模式,使用正则匹配到消息队列,能匹配到的都能接收到。
Kafka 的分区策略有哪些?
ProducerRecord内部数据结构:
– Topic (名字)
– PartitionID ( 可选)
– Key[( 可选 )
– Value
提供三种构造函数形参:
ProducerRecord(topic, partition, key, value) ProducerRecord(topic, key, value) ProducerRecord(topic, value)
第一种分区策略:指定分区号,直接将数据发送到指定的分区中。
第二种分区策略:没有指定分区号,指定数据的key值,通过key取上hashCode进行分区。
第三种分区策略:没有指定分区号,也没有指定key值,直接轮循进行分区。
第四种分区策略:自定义分区。
RabbitMQ 有哪些重要组件?
ConnectionFactory(连接管理器):应用程序与RabbitMQ之间建立连接的管理器
Channel(信道):消息推送使用的通道
Exchange(交换器):用于接受、分配消息
Queue(队列):用于存储生产者的消息
RoutingKey(路由键):用于把生产者的数据分配到交换器上
BindKey(绑定键):用于把交换器的消息绑定到队列上
RabbitMQ 有哪些重要角色?
生产者:
消息的生产者,负责创建和推送数据到消息服务器。
消费者:
消息的接收者,用于处理数据和确认消息。
代理:
RabbitMQ本身,用于扮演传递消息的角色,本身并不生产消息。
RabbitMQ 如何保证消息顺序性?
RabbitMQ保证消息的顺序性方式有两种:
1)拆分多个queue,每个queue对应一个consumer(消费者),就是多一些queue。
2)一个queue,对应一个consumer(消费者),然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理。
如何保证消息消费的幂等性?
1、利用数据库的唯一约束实现幂等
比如将订单表中的订单编号设置为唯一索引,创建订单时,根据订单编号就可以保证幂等
2、去重表
根据数据库的唯一性约束类似的方式来实现。其实现大体思路是:首先在去重表上建唯一索引,其次操作时把业务表和去重表放在同个本地事务中,如果出现重现重复消费,数据库会抛唯一约束异常,操作就会回滚。
3、利用redis的原子性
每次操作都直接set到redis里面,然后将redis数据定时同步到数据库中。
4、多版本(乐观锁)控制
此方式多用于更新的场景下。其实现的大体思路是:给业务数据增加一个版本号属性,每次更新数据前,比较当前数据的版本号是否和消息中的版本一致,如果不一致则拒绝更新数据,更新数据的同时将版本号+1。
5、状态机机制
此方式多用于更新且业务场景存在多种状态流转的场景。
6、token机制
生产者发送每条数据时,增加一个全局唯一ID,这个ID通常是业务的唯一标识。在消费者消费时,需要验证这个ID是否被消费过,如果没消费过,则进行业务处理。处理结束后,在把这个ID存入Redis,同时设置状态为已消费。如果已经消费过,则清除Redis缓存的这个ID。
Kafka 消费者如何取消订阅?
在KafkaConsumer类中使用unsubscribe()方法来取消主题的订阅。该方法可以取消如下的订阅方法:
subscribe(Collection);方式实现的订阅
subscribe(Pattern);方式实现的订阅
assign() 方式实现的订阅
取消订阅使用代码如下:
consumer.unsubscribe();
将subscribe(Collection)或assign()中的集合参数设置为空集合,也可以实现取消订阅,以下三种方式都可以取消订阅:
consumer.unsubscribe(); consumer.subscribe(new ArrayList<String>()); consumer.assign(new ArrayList<TopicPartition>());
设计模式有多少种,都有哪些设计模式?
Java有23种设计模式
设计模式总体分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。
设计模式的六大原则是什么?
1)开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2)里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3)依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
4)接口隔离原则(Interface Segregation Principle)
大概意思是指使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
5)迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6)合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
什么是单例模式?
单例模式的定义就是确保某一个类仅有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式分为:懒汉式单例、饿汉式单例、登记式单例三种。
单例模式特点:
1)单例类只能有一个实例。
2)单例类必须自己创建自己的唯一实例。
3)单例类必须给所有其他对象提供这一实例。
单例模式的优点是内存中只有一个对象,节省内存空间;避免频繁的创建销毁对象,可以提高性能;避免对资源的多重占用,简化访问;为整个系统提供一个全局访问点。
单例模式的缺点是不适用于变化频繁的对象;滥用单利将带来一些问题,如为了节省资源将数据库连接池对象设计为的单利类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失。
从名字上来说饿汉和懒汉,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候单例是已经被初始化,而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
1、懒汉式单例,具体代码如下:
package com.yoodb; //懒汉式单例类.在第一次调用的时候实例化自己 public class Singleton { //私有的无参构造方法 private Singleton(){ } //注意,这里没有final private static Singleton single = null; //静态工厂方法 public synchronized static Singleton getInstance(){ if(single == null){ single = new Singleton(); } return single; } }
2、饿汉式单例,具体代码如下:
package com.yoodb; public class Singleton { //私有的无参构造方法 private Singleton(){ } //自动实例化 private final static Singleton single = new Singleton(); //静态工厂方法 public static synchronized Singleton getInstance(){ return single; } }
3、登记式单例,具体代码如下:
package com.yoodb; import java.util.HashMap; import java.util.Map; public class Singleton { public static Map<String,Singleton> map = new HashMap<String,Singleton>(); static{ //将类名注入下次直接获取 Singleton single = new Singleton(); map.put(single.getClass().getName(),single); } //保护式无参构造方法 protected Singleton(){} public static Singleton getInstance(String name){ if(name == null){ name = Singleton.class.getName(); } if(map.get(name) == null){ try { map.put(name, Singleton.class.newInstance()); } catch (InstantiationException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return map.get(name); } public String create() { return "创建一个单例!"; } public static void main(String[] args) { Singleton single = Singleton.getInstance(null); System.out.println(single.create()); } }
单例模式中饿汉式和懒汉式有什么区别?
1、线程安全
饿汉式是线程安全的,可以直接用于多线程而不会出现问题,懒汉式就不行,它是线程不安全的,如果用于多线程可能会被实例化多次,失去单例的作用。
2、资源加载
饿汉式在类创建的同时就实例化一个静态对象,不管之后会不会使用这个单例,都会占据一定的内存资源,相应的在调用时速度也会更快。
懒汉式顾名思义,会延迟加载,在第一次使用该单例时才会实例化对象出来,第一次掉用时要初始化,如果要做的工作比较多,性能上会有些延迟,第一次调用之后就和饿汉式。
单例模式都有哪些应用场景?
1)需要生成唯一序列;
2)需要频繁实例化然后销毁代码的对象;
3)有状态的工具类对象;
4)频繁访问数据库或文件的对象。
例如:项目中读取配置文件、数据库连接池参数、spring中的bean默认是单例、线程池(threadpool)、缓存(cache)、日志参数等程序的对象。
需要注意的事这些场景只能有一个实例,如果生成多个实例,就会导致许多问题产生,如:程序行为异常、资源使用过量或数据不一致等。
什么是线程安全?
如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的且其他变量的值也和预期的是一样的,就是线程安全的。
或者也可以理解成一个类或程序所提供的接口对于线程来说是原子操作,多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说不用考虑同步的问题,那就是线程安全的。
Spring 框架中使用了哪些设计模式?
Spring框架中使用大量的设计模式,下面列举比较有代表性的:
代理模式
AOP能够将那些与业务无关(事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度有利于可拓展性和可维护性。
单例模式
Spring中bean的默认作用域是单例模式,在Spring配置文件中定义bean默认为单例模式。
模板方法模式
模板方法模式是一种行为设计模式,用来解决代码重复的问题,如RestTemplate、JmsTemplate、JpaTemplate。
包装器设计模式
Spring根据不同的业务访问不同的数据库,能够动态切换不同的数据源。
观察者模式
Spring事件驱动模型就是观察者模式很经典的一个应用。
工厂模式
Spring使用工厂模式通过BeanFactory、ApplicationContext创建bean对象。
Spring MVC 执行流程是什么?
1)客户端发送请求到前端控制器DispatcherServlet。
2)DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3)处理器映射器找到具体的处理器,根据xml配置、注解进行查找,生成处理器对象及处理器拦截器,并返回给DispatcherServlet。
4)DispatcherServlet调用HandlerAdapter处理器适配器。
5)HandlerAdapter经过适配调用具体的后端控制器Controller。
6)Controller执行完成返回ModelAndView。
7)HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8)DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9)ViewReslover解析后返回具体View。
10)DispatcherServlet根据View进行渲染视图,将模型数据填充至视图中。
11)DispatcherServlet响应客户端。
组件说明
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
Spring MVC 如何解决请求中文乱码问题?
解决GET请求中文乱码问题
方式一:每次请求前使用encodeURI对URL进行编码。
方式二:在应用服务器上配置URL编码格式,在Tomcat配置文件server.xml中增加URIEncoding=“UTF-8”,然后重启Tomcat即可。
<ConnectorURIEncoding="UTF-8" port="8080" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75"connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="UTF-8" />
解决POST请求中文乱码问题
方式一:在request解析数据时设置编码格式:
request.setCharacterEncoding("UTF-8");
方式二:使用Spring提供的编码过滤器
在web.xml文件中配置字符编码过滤器,增加如下配置:
<!--编码过滤器--> <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <!-- 开启异步支持--> <async-supported>true</async-supported> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
该过滤器的作用就是强制所有请求和响应设置编码格式:
request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");
Spring MVC 请求转发和重定向有什么区别?
请求转发是浏览器发出一次请求,获取一次响应,而重定向是浏览器发出2次请求,获取2次请求。
请求转发是浏览器地址栏未发生变化,是第1次发出的请求,而重定向是浏览器地址栏发生变化,是第2次发出的请求。
请求转发称为服务器内跳转,重定向称为服务器外跳转。
请求转发是可以获取到用户提交请求中的数据,而重定向是不可以获取到用户提交请求中的数据,但可以获取到第2次由浏览器自动发出的请求中携带的数据。
请求转发是可以将请求转发到WEB-INF目录下的(内部)资源,而重定向是不可以将请求转发到WEB-INF目录下的资源。
Spring MVC 中系统是如何分层?
表现层(UI):数据的展现、操作页面、请求转发。
业务层(服务层):封装业务处理逻辑。
持久层(数据访问层):封装数据访问逻辑。
各层之间的关系:MVC是一种表现层的架构,表现层通过接口调用业务层,业务层通过接口调用持久层,当下一层发生改变,不影响上一层的数据。
如何开启注解处理器和适配器?
在配置文件中(一般命名为springmvc.xml 文件)通过开启配置:
<mvc:annotation-driven>
来实现注解处理器和适配器的开启。
Spring MVC 如何设置重定向和转发?
在返回值前面加“forward:”参数,可以实现转发。
"forward:getName.do?name=Java精选"
在返回值前面加“redirect:”参数,可以实现重定向。
"redirect:https://blog.yoodb.com" Spring MVC 中函数的返回值是什么? Spring MVC的返回值可以有很多类型,如String、ModelAndView等,但事一般使用String比较友好。 @RequestMapping 注解用在类上有什么作用? @RequestMapping注解是一个用来处理请求地址映射的注解,可用于类或方法上。 用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 比如 ```java @Controller @RequestMapping("/test/") public class TestController{ }
启动的是本地服务,默认端口是8080,通过浏览器访问的路径就是如下地址:
http://localhost:8080/test/
Spring MVC 控制器是单例的吗?
默认情况下是单例模式,在多线程进行访问时存在线程安全的问题。
解决方法可以在控制器中不要写成员变量,这是因为单例模式下定义成员变量是线程不安全的。此为部分面试题包含答案,更多面试题见微信小程序 “Java精选面试题”,3000+道面试题。
通过@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)或@Scope(“prototype”)可以实现多例模式。但是不建议使用同步,因为会影响性能。
使用单例模式是为了性能,无需频繁进行初始化操作,同时也没有必要使用多例模式。
RequestMethod 可以同时支持POST和GET请求访问吗?
POST请求访问
@RequestMapping(value="/wx/getUserById",method = RequestMethod.POST)
GET请求访问
@RequestMapping(value="/wx/getUserById/{userId}",method = RequestMethod.GET)
同时支持POST和GET请求访问
@RequestMapping(value="/subscribe/getSubscribeList/{customerCode}/{sid}/{userId}")
RequestMethod常用的参数
GET(SELECT):从服务器查询,在服务器通过请求参数区分查询的方式。
POST(CREATE):在服务器新建一个资源,调用insert操作。
PUT(UPDATE):在服务器更新资源,调用update操作。
DELETE(DELETE):从服务器删除资源,调用delete语句。
Spring 依赖注入有几种实现方式?
1)Constructor构造器注入:通过将@Autowired注解放在构造器上来完成构造器注入,默认构造器参数通过类型自动装配。
public class Test { private UserInterface user; @Autowired private Test(UserInterface user) { this.user = user; } }
2)Field接口注入:通过将@Autowired注解放在构造器上来完成接口注入。
@Autowired //接口注入 private IUserService userService;
3)Setter方法参数注入:通过将@Autowired注解放在方法上来完成方法参数注入。
public class Test { private User user; @Autowired public void setUser(User user) { this.user = user; } public String getuser() { return user; } }
Spring 可以注入null或空字符串吗?
完全可以
Spring 支持哪几种 bean 作用域?
1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
2)prototype:为每一个bean请求提供一个实例。
3)request:为每一个网络请求创建一个实例,在请求完成后,bean会失效并被垃圾回收器回收。
4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
5)global-session:全局作用域,global-session和Portlet应用相关。
当应用部署在Portlet容器中工作时,它包含很多portlet。
如果想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。
全局作用域与Servlet中的session作用域效果相同。
JDK1.8 中 ConcurrentHashMap 不支持空键值吗?
首先明确一点HashMap是支持空键值对的,也就是null键和null值,而ConcurrentHashMap是不支持空键值对的。
查看一下JDK1.8源码,HashMap类部分源码,代码如下:
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
HashMap在调用put()方法存储数据时会调用hash()方法来计算key的hashcode值,可以从hash()方法上得出当key==null时返回值是0,这意思就是key值是null时,hash()方法返回值是0,不会再调用key.hashcode()方法。
ConcurrentHashMap类部分源码,代码如下:
public V put(K key, V value) { return putVal(key, value, false); }
/** Implementation for put and putIfAbsent */ final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; }
ConcurrentHashmap在调用put()方法时调用了putVal()方法,而在该方法中判断key为null或value为null时抛出空指针异常NullPointerException。
ConcurrentHashmap是支持并发的,当通过get()方法获取对应的value值时,如果指定的键为null,则为NullPointerException,这主要是因为获取到的是null值,无法分辨是key没找到null还是有key值为null。
Spring 有哪些不同的通知类型?
通知(advice)是在程序中想要应用在其他模块中横切关注点的实现。
Advice主要有5种类型:
@Before注解使用Advice
前置通知(BeforeAdvice):在连接点之前执行的通知(advice),除非它抛出异常,否则没有能力中断执行流。
@AfterReturning注解使用Advice
返回之后通知(AfterRetuningAdvice):如果一个方法没有抛出异常正常返回,在连接点正常结束之后执行的通知(advice)。
@AfterThrowing注解使用Advice
抛出(异常)后执行通知(AfterThrowingAdvice):若果一个方法抛出异常来退出的话,这个通知(advice)就会被执行。
@After注解使用Advice
后置通知(AfterAdvice):无论连接点是通过什么方式退出的正常返回或者抛出异常都会执行在结束后执行这些通知(advice)。
注解使用Advice
围绕通知(AroundAdvice):围绕连接点执行的通知(advice),只有这一个方法调用。这是最强大的通知(advice)。
Spring AOP 连接点和切入点是什么?
连接点(Joint Point)
连接点是指一个应用执行过程中能够插入一个切面的点,可以理解成一个方法的执行或者一个异常的处理等。
连接点可以是调用方法、抛出异常、修改字段等时,切面代码可以利用这些点插入到应用的正规流程中。使得程序执行过程中能够应用通知的所有点。
在Spring AOP中一个连接点总是代表一个方法执行。如果在这些方法上使用横切的话,所有定义在EmpoyeeManager接口中的方法都可以被认为是一个连接点。
切入点(Point cut)
切入点是一个匹配连接点的断言或者表达式,如果通知定义了“什么”和“何时”,那么切点就定义了“何处”。
通知(Advice)与切入点表达式相关联,切入点用于准确定位,确定在什么地方应用切面通知。
例如表达式
execution(* EmployeeManager.getEmployeeById(...))
可以匹配EmployeeManager接口的getEmployeeById()。
Spring默认使用AspectJ切入点表达式,由切入点表达式匹配的连接点概念是AOP的核心。此为部分面试题包含答案,更多面试题见微信小程序 “Java精选面试题”,3000+道面试题。
Spring AOP 代理模式是什么?
代理模式是使用非常广泛的设计模式之一。
代理模式是指给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是生活中常见的房产中介。
Spring AOP是基于代理实现的。
Spring AOP代理是一个由AOP框架创建的用于在运行时实现切面协议的对象。
Spring AOP默认为AOP代理使用标准的JDK动态代理,这使得任何接口(或者接口的集合)可以被代理。
Spring AOP也可以使用CGLIB代理,如果业务对象没有实现任何接口那么默认使用CGLIB。
Spring 框架有哪些特点?
1)方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象之间的依赖关系交由Spring进行控制,避免编码所造成的过度耦合。使用Spring可以使用户不必再为单实例模式类、属性文件解析等底层的需求编码,可以更专注于上层的业务逻辑应用。
2)支持AOP面向切面编程
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松完成。
3)支持声明事物
通过Spring可以从单调繁琐的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
4)方便程序测试
使用非容器依赖的编程方式进行几乎所有的测试工作,通过Spring使得测试不再是高成本的操作,而是随手可做的事情。Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5)方便便捷集成各种中间件框架
Spring可以降低集成各种中间件框架的难度,Spring提供对各种框架如Struts,Hibernate、Hessian、Quartz等的支持。
6)降低Java EE API使用难度
Spring对很多Java EE API如JDBC、JavaMail、远程调用等提供了一个简易的封装层,通过Spring的简易封装,轻松实现这些Java EE API的调用。
Spring 是由哪些模块组成的?
Core module
Bean module
Context module
Expression Language module
JDBC module
ORM module
OXM module
Java Messaging Service(JMS) module
Transaction module
Web module
Web-Servlet module
Web-Struts module
Web-Portlet module
Spring 提供几种配置方式设置元数据?
Spring配置元数据可以采用三种方式,可以混合使用。
1)基于XML的配置元数据
使用XML文件标签化配置Bean的相关属性。
属性 | 描述 | 对应注解 |
---|---|---|
class | 此项必填,指定要创建Bean的类(全路径) | 无 |
id | 全局唯一 指定bean的唯一标示符 | 无 |
name | 全局唯一 指定bean的唯一标示符 | @Bean的name属性 |
scope | 创建bean的作用域 | @Scope |
singleton | 是否单例 | @Scope(value=SCOPE_SINGLETON) |
depends-on | 用来表明依赖关系 | @DependsOn |
depends-check | 依赖检查 | 无 |
autowire | 自动装配 默认NO | @Bean的autowire属性 |
init-method | 对象初始化后调用的方法 | @Bean 的initMethod属性 |
destroy-method | 对象销毁前调用的方法 | @Bean 的destroyMethod |
lazy-init | 容器启动时不会初始化,只有使用时初始化 | @Lazy |
primary | 容器中有多个相同类型的bean时,autowired时优先使用primary=true | @Primary |
factory-method | 工厂创建对象的方法 | 无 |
factory-bean | 工厂bean | 无 |
2)基于注解的配置元数据
@Component 标识一个被Spring管理的对象 @Respository 标识持久层对象 @Service 标识业务层对象 @Controller 标识表现层对象
Spring Boot项目中Contrller层、Service层、Dao层均采用注解的方式,在对应类上加相对应的@Controller,@Service,@Repository,@Component等注解。
需要注意的是使用@ComponentScan注解,若是Spring Boot框架项目,扫描组件的默认路径与Application.class同级包。
3)基于Java的配置元数据
默认情况下,方法名即为Bean名称。Java使用Configuration类配置bean,对应Spring注解@Bean。
目前常用的方式是第二种和第三种,也经常结合使用。
HTTP1.0 和 HTTP1.1 有什么区别?
1)长连接(Persistent Connection)
HTTP1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。
HTTP1.1支持长连接,在请求头中有Connection:Keep-Alive。在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。
2)节省带宽
HTTP1.0中存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象传输过去,并且不支持断点续传功能。
HTTP1.1支持只发送header信息,不携带其他任何body信息,如果服务器认为客户端有权限请求服务器,则返回100状态码,客户端接收到100状态码后把请求body发送到服务器;如果返回401状态码,客户端无需发送请求body节省带宽。
3)HOST域
HTTP1.0没有host域,HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。
HTTP1.1的请求消息和响应消息都支持host域,且请求消息中若是host域会报告400 Bad Request错误。一台物理服务器上可以同时存在多个虚拟主机(Multi-homed Web Servers),并且它们可以共享一个IP地址。
4)缓存处理
HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准。
HTTP1.1引入了更多的缓存控制策略如Entity tag、If-Unmodified-Since、If-Match、If-None-Match等更多可供选择的缓存头来控制缓存策略。
5)错误通知管理
HTTP1.1中新增24个错误状态响应码,比如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
HTTP1.1 和 HTTP2.0 有什么区别?
1)多路复用
HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。
HTTP1.1可以建立多个TCP连接来支持处理更多并发的请求,但是创建TCP连接本身也是有开销的。
2)头部数据压缩
HTTP1.1中HTTP请求和响应都是由状态行、请求/响应头部、消息主体三部分组成。
一般而言,消息主体都会经过gzip压缩或本身传输的就是压缩过后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输。
随着Web功能越来越复杂,每个页面产生的请求数也越来越多,导致消耗在头部的流量越来越多,尤其是每次都要传输UserAgent、Cookie等不会频繁变动的内容,完全是一种浪费资源的体现。
HTTP1.1不支持header数据的压缩,而HTTP2.0使用HPACK算法对header的数据进行压缩,压缩的数据体积小,在网络上传输更快。
3)服务器推送
服务端推送是一种在客户端请求前发送数据的机制。
网页中使用了许多资源:HTML、样式表、脚本、图片等,在HTTP1.1中这些资源每一个都必须明确地请求,这是一个很慢的过程。
浏览器从获取HTML开始,然后在它解析和评估页面时获取更多的资源,因为服务器必须等待浏览器做每一个请求,网络经常是空闲和未充分使用的。
HTTP2.0引入了server push,允许服务端推送资源给浏览器,在浏览器明确请求前,不用客户端再次创建连接发送请求到服务器端获取,客户端可以直接从本地加载这些资源,不用再通过网络。
Spring Boot 支持哪几种内嵌容器?
Spring Boot支持的内嵌容器有Tomcat(默认)、Jetty、Undertow和Reactor Netty(v2.0+),借助可插拔(SPI)机制的实现,开发者可以轻松进行容器间的切换。
什么是 Spring Boot Stater?
Spring Boot在配置上相比Spring要简单许多,其核心在于Spring Boot Stater。
Spring Boot内嵌容器支持Tomcat、Jetty、Undertow等应用服务的starter启动器,在应用启动时被加载,可以快速的处理应用所需要的一些基础环境配置。
starter解决的是依赖管理配置复杂的问题,可以理解成通过pom.xml文件配置很多jar包组合的maven项目,用来简化maven依赖配置,starter可以被继承也可以依赖于别的starter。
比如spring-boot-starter-web包含以下依赖:
org.springframework.boot:spring-boot-starter org.springframework.boot:spring-boot-starter-tomcat org.springframework.boot:spring-boot-starter-validation com.fasterxml.jackson.core:jackson-databind org.springframework:spring-web org.springframework:spring-webmvc
starter负责配与Sping整合相关的配置依赖等,使用者无需关心框架整合带来的问题。
比如使用Sping和JPA访问数据库,只需要项目包含spring-boot-starter-data-jpa依赖就可以完美执行。
Spring Boot Stater 有什么命名规范?
1)Spring Boot官方项目命名方式
前缀:spring-boot-starter-,其中是指定类型的应用程序名称。
格式:spring-boot-starter-{模块名}
举例:spring-boot-starter-web、spring-boot-starter-jdbc
2)自定义命名方式
后缀:*-spring-boot-starter
格式:{模块名}-spring-boot-starter
举例:mybatis-spring-boot-starter
Spring Boot 启动器都有哪些?
Spring Boot在org.springframework.boot组下提供了以下应用程序启动器:
名称 | 描述 |
---|---|
spring-boot-starter | 核心启动器,包括自动配置支持,日志记录和YAML |
spring-boot-starter-activemq | 使用Apache ActiveMQ的JMS消息传递启动器 |
spring-boot-starter-amqp | 使用Spring AMQP和Rabbit MQ的启动器 |
spring-boot-starter-aop | 使用Spring AOP和AspectJ进行面向方面编程的启动器 |
spring-boot-starter-artemis | 使用Apache Artemis的JMS消息传递启动器 |
spring-boot-starter-batch | 使用Spring Batch的启动器 |
spring-boot-starter-cache | 使用Spring Framework的缓存支持的启动器 |
spring-boot-starter-data-cassandra | 使用Cassandra分布式数据库和Spring Data Cassandra的启动器 |
spring-boot-starter-data-cassandra-reactive | 使用Cassandra分布式数据库和Spring Data Cassandra Reactive的启动器 |
spring-boot-starter-data-couchbase | 使用Couchbase面向文档的数据库和Spring Data Couchbase的启动器 |
spring-boot-starter-data-couchbase-reactive | 使用Couchbase面向文档的数据库和Spring Data Couchbase Reactive的启动器 |
spring-boot-starter-data-elasticsearch | 使用Elasticsearch搜索和分析引擎以及Spring Data Elasticsearch的启动器 |
spring-boot-starter-data-jdbc | 使用Spring Data JDBC的启动器 |
spring-boot-starter-data-jpa | 将Spring Data JPA与Hibernate结合使用的启动器 |
spring-boot-starter-data-ldap | 使用Spring Data LDAP的启动器 |
spring-boot-starter-data-mongodb | 使用MongoDB面向文档的数据库和Spring Data MongoDB的启动器 |
spring-boot-starter-data-mongodb-reactive | 使用MongoDB面向文档的数据库和Spring Data MongoDB Reactive的启动器 |
spring-boot-starter-data-neo4j | 使用Neo4j图形数据库和Spring Data Neo4j的启动器工具 |
spring-boot-starter-data-r2dbc | 使用Spring Data R2DBC的启动器 |
spring-boot-starter-data-redis | 使用Redis键值数据存储与Spring Data Redis和Lettuce客户端的启动器 |
spring-boot-starter-data-redis-reactive | 将Redis键值数据存储与Spring Data Redis Reacting和Lettuce客户端一起使用的启动器 |
spring-boot-starter-data-rest | 使用Spring Data REST在REST上公开Spring数据存储库的启动器 |
spring-boot-starter-freemarker | 使用FreeMarker视图构建MVC Web应用程序的启动器 |
spring-boot-starter-groovy-templates | 使用Groovy模板视图构建MVC Web应用程序的启动器 |
spring-boot-starter-hateoas | 使用Spring MVC和Spring HATEOAS构建基于超媒体的RESTful Web应用程序的启动器 |
spring-boot-starter-integration | 使用Spring Integration的启动器 |
spring-boot-starter-jdbc | 结合使用JDBC和HikariCP连接池的启动器 |
spring-boot-starter-jersey | 使用JAX-RS和Jersey构建RESTful Web应用程序的启动器。的替代品spring-boot-starter-web |
spring-boot-starter-jooq | 使用jOOQ访问SQL数据库的启动器。替代spring-boot-starter-data-jpa或spring-boot-starter-jdbc |
spring-boot-starter-json | 读写JSON启动器 |
spring-boot-starter-jta-atomikos | 使用Atomikos的JTA交易启动器 |
spring-boot-starter-mail | 使用Java Mail和Spring Framework的电子邮件发送支持的启动器 |
spring-boot-starter-mustache | 使用Mustache视图构建Web应用程序的启动器 |
spring-boot-starter-oauth2-client | 使用Spring Security的OAuth2 / OpenID Connect客户端功能的启动器 |
spring-boot-starter-oauth2-resource-server | 使用Spring Security的OAuth2资源服务器功能的启动器 |
spring-boot-starter-quartz | 启动器使用Quartz Scheduler |
spring-boot-starter-rsocket | 用于构建RSocket客户端和服务器的启动器 |
spring-boot-starter-security | 使用Spring Security的启动器 |
spring-boot-starter-test | 用于使用包括JUnit Jupiter,Hamcrest和Mockito在内的库测试Spring Boot应用程序的启动器 |
spring-boot-starter-thymeleaf | 使用Thymeleaf视图构建MVC Web应用程序的启动器 |
spring-boot-starter-validation | 初学者,可将Java Bean验证与Hibernate Validator结合使用 |
spring-boot-starter-web | 使用Spring MVC构建Web(包括RESTful)应用程序的启动器。使用Tomcat作为默认的嵌入式容器 |
spring-boot-starter-web-services | 使用Spring Web Services的启动器 |
spring-boot-starter-webflux | 使用Spring Framework的反应式Web支持构建WebFlux应用程序的启动器 |
spring-boot-starter-websocket | 使用Spring Framework的WebSocket支持构建WebSocket应用程序的启动器 |
除应用程序启动器外,以下启动程序还可用于添加生产环境上线功能:
名称 | 描述 |
---|---|
spring-boot-starter-actuator | 使用Spring Boot Actuator的程序,该启动器提供了生产环境上线功能,可帮助您监视和管理应用程序 |
Spring Boot还包括以下启动程序,如果想排除或替换启动器,可以使用这些启动程序:
名称 | 描述 |
---|---|
spring-boot-starter-jetty | 使用Jetty作为嵌入式servlet容器的启动器。替代spring-boot-starter-tomcat |
spring-boot-starter-log4j2 | 使用Log4j2进行日志记录的启动器。替代spring-boot-starter-logging |
spring-boot-starter-logging | 使用Logback进行日志记录的启动器。默认记录启动器 |
spring-boot-starter-reactor-netty | 启动器,用于将Reactor Netty用作嵌入式反应式HTTP服务器。 |
spring-boot-starter-tomcat | 启动器,用于将Tomcat用作嵌入式servlet容器。默认使用的servlet容器启动器spring-boot-starter-web |
spring-boot-starter-undertow | 使用Undertow作为嵌入式servlet容器的启动器。替代spring-boot-starter-tomcat |
Spring Cloud 断路器的作用是什么?
分布式架构中断路器模式的作用基本类似的,当某个服务单元发生故障,类似家用电器发生短路后,通过断路器的故障监控,类似熔断保险丝,向调用方返回一个错误响应,不需要长时间的等待。这样就不会出现因被调用的服务故障,导致线程长时间占用而不释放,避免了在分布式系统中故障的蔓延。
Spring Cloud 核心组件有哪些?
Eureka:服务注册与发现,Eureka服务端称服务注册中心,Eureka客户端主要处理服务的注册与发现。
Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求url地址,发起请求。
Ribbon:负载均衡,服务间发起请求时基于Ribbon实现负载均衡,从一个服务的多台机器中选择一台。
Hystrix:提供服务隔离、熔断、降级机制,发起请求是通过Hystrix提供的线程池,实现不同服务调用之间的隔离,避免服务雪崩问题。
Zuul:服务网关,前端调用后端服务,统一由Zuul网关转发请求给对应的服务。
Spring Cloud 如何实现服务的注册?
1、当服务发布时,指定对应的服务名,将服务注册到注册中心,比如Eureka、Zookeeper等。
2、注册中心加@EnableEurekaServer注解,服务使用@EnableDiscoveryClient注解,然后使用Ribbon或Feign进行服务直接的调用发现。
服务发现组件的功能
1)服务注册表
服务注册表是一个记录当前可用服务实例的网络信息的数据库,是服务发现机制的核心。
服务注册表提供查询API和管理API,使用查询API获得可用的服务实例,使用管理API实现注册和注销;
2)服务注册
3)健康检查
什么是 Spring Cloud Config?
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持,可以方便的对微服务各个环境下的配置进行实时更新、集中式管理。
Spring Cloud Config分为Config Server和Config Client两部分。
Config Server负责读取配置文件,并且暴露Http API接口,Config Client通过调用Config Server的接口来读取配置文件。
Spring Cloud Eureka 自我保护机制是什么?
1)假设某一时间某个微服务宕机了,而Eureka不会自动清除,依然对微服务的信息进行保存。
2)在默认的情况系,Eureka Server在一定的时间内没有接受到微服务的实例心跳(默认为90秒),Eureka Server将会注销该实例。
3)但是在网络发生故障时微服务在Eureka Server之间是无法通信的,因为微服务本身实例是健康的,此刻本不应该注销这个微服务。那么Eureka自我保护模式就解决了这个问题。
4)当Eureka Server节点在短时间内丢失过客户端时包含发生的网络故障,那么节点就会进行自我保护。
5)一但进入自我保护模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据注销任何微服务。
6)当网络故障恢复后,这个Eureka Server节点会自动的退出自我保护机制。
7)在自我保护模式中Eureka Server会保护服务注册表中的信息,不在注销任何服务实例,当重新收到心跳恢复阀值以上时,这个Eureka Server节点就会自动的退出自我保护模式,这种设计模式是为了宁可保留错误的服务注册信息,也不盲目的注销删除任何可能健康的服务实例。
8)自我保护机制就是一种对网络异常的安全保护实施,它会保存所有的微服务,不管健康或不健康的微服务都会保存,而不会盲目的删除任何微服务,可以让Eureka集群更加的健壮、稳定。
9)在Eureka Server端可取消自我保护机制,但是不建议取消此功能。
常用的并发工具类有哪些?
CountDownLatch闭锁
CountDownLatch是一个同步计数器,初始化时传入需要计数线程的等待数,可能是等于或大于等待执行完的线程数。调用多个线程之间的同步或说起到线程之间的通信(不是互斥)一组线程等待其他线程完成工作后在执行,相当于加强的join。
CyclicBarrier栅栏
CyclicBarrier字面意思是栅栏,是多线程中重要的类,主要用于线程之间互相等待的问题,初始化时传入需要等待的线程数。
作用:让一组线程达到某个屏障被阻塞直到一组内最后一个线程达到屏蔽时,屏蔽开放,所有被阻塞的线程才会继续运行。
Semophore信号量
semaphore称为信号量是操作系统的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。
作用:semaphore管理一系列许可每个acquire()方法阻塞,直到有一个许可证可以获得,然后拿走许可证,每个release方法增加一个许可证,这可能会释放一个阻塞的acquire()方法,然而并没有实际的许可保证这个对象,semaphore只是维持了一个可获取许可的数量,主要控制同时访问某个特定资源的线程数量,多用在流量控制。
Exchanger交换器
Exchange类似于交换器可以在队中元素进行配对和交换线程的同步点,用于两个线程之间的交换。
具体来说,Exchanger类允许两个线程之间定义同步点,当两个线程达到同步点时,它们交换数据结构,因此第一个线程的数据结构进入到第二个线程当中,第二个线程的数据结构进入到第一个线程当中。
并发和并行有什么区别?
并行(parallellism)是指两个或者多个事件在同一时刻发生,而并发(parallellism)是指两个或多个事件在同一时间间隔发生。
并行是在不同实体上的多个事件,而并发是在同一实体上的多个事件。
并行是在一台处理器上同时处理多个任务(Hadoop分布式集群),而并发在多台处理器上同时处理多个任务。
JSP 模版引擎如何解析 ${} 表达式?
目前开发中已经很少使用JSP模版引擎,JSP虽然是一款功能比较强大的模板引擎,并被广大开发者熟悉,但它前后端耦合比较高。
其次是JSP页面的效率没有HTML高,因为JSP是同步加载。而且JSP需要Tomcat应用服务器部署,但不支持Nginx等,已经快被时代所淘汰。
JSP页面中使用${表达式}展示数据,但是页面上并没有显示出对应数据,而是把${表达式}当作纯文本显示。
原因分析:这是由于jsp模版引擎默认会无视EL表达式,需要手动设置igNoreEL为false。
<%@ page isELIgnored="false" %>
什么是服务熔断?什么是服务降级?
熔断机制
熔断机制是应对雪崩效应的一种微服务链路保护机制。
当某个微服务不可用或者响应时间过长时会进行服务降级,进而熔断该节点微服务的调用,快速返回“错误”的响应信息。
当检测到该节点微服务调用响应正常后恢复调用链路。
在Spring Cloud框架里熔断机制通过Hystrix实现,Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内调用20次,如果失败,就会启动熔断机制。
服务降级
服务降级一般是从整体负荷考虑,当某个服务熔断后,服务器将不再被调用,此时客户端可以准备一个本地fallback回调,返回一个缺省值。这样做目的是虽然水平下降,但是是可以使用,相比直接挂掉要强很多。
Spring Boot 和 Spring Cloud 之间有什么联系?
Spring Boot是Spring推出用于解决传统框架配置文件冗余,装配组件繁杂的基于Maven的解决方案,旨在快速搭建单个微服务。
Spring Cloud专注于解决各个微服务之间的协调与配置,整合并管理各个微服务,为各个微服务之间提供配置管理、服务发现、断路器、路由、事件总线等集成服务。
Spring Cloud是依赖于Spring Boot,而Spring Boot并不依赖与Spring Cloud。
Spring Boot专注于快速、方便的开发单个的微服务个体,Spring Cloud是关注全局的服务治理框架。
你都知道哪些微服务技术栈?
微服务描述 | 技术名称 |
---|---|
服务开发 | Springboot、Spring、SpringMVC |
服务配置与管理 | Netflix公司的Archaius、阿里的Diamond等 |
服务注册与发现 | Eureka、Consul、Zookeeper等 |
服务调用 | REST、RPC、gRPC |
服务熔断器 | Hystrix、Envoy等 |
负载均衡 | Ribbon、Nginx等 |
服务接口调用(客户端调用服务发简单工具) | Feign等 |
消息队列 | kafka、RabbitMQ、ActiveMQ等 |
服务配置中心管理 | SpringCloudConfig、Chef等 |
服务路由(API网关) | Zuul等 |
服务监控 | Zabbix、Nagios、Metrics、Spectator等 |
全链路追踪 | Zipkin、Brave、Dapper等 |
服务部署 | Docker、OpenStack、Kubernetes等 |
数据流操作开发包 | SpringCloud Stream(封装与Redis,Rabbit、Kafka等发送接收消息) |
事件消息总线 | Spring Cloud Bus |
接口和抽象类有什么区别?
比较方面 | 抽象类说明 | 接口说明 |
---|---|---|
默认方法 | 抽象类可以有默认的方法实现 | JDK1。8之前版本,接口中不存在方法的实现 |
实现方式 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现 | 子类使用implements来实现接口,需要提供接口中所有声明的实现。 |
构造器 | 抽象类中可以有构造器 | 接口中不能 |
和正常类区别 | 抽象类不能被实例化 | 接口则是完全不同的类型 |
访问修饰符 | 抽象方法可以有public、protected、default等修饰 | 接口默认是public,不能使用其他修饰符 |
多继承 | 一个子类只能存在一个父类 | 一个子类可以存在多个接口 |
添加新方法 | 抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 | 如果往接口中添加新方法,则子类中需要实现该方法 |
什么是线程死锁?
线程死锁是指多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
产生死锁必须具备以下四个条件:
互斥条件:该资源任意一个时刻只由一个线程占用;
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源持有不释放;
不剥夺条件:线程已获得的资源,在末使用完之前不能被其他线程强行剥夺,只有使用完毕后才释放资源;
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免线程死锁?
只需要破坏产生死锁的四个条件中任意一个就可以避免线程死锁,但是互斥条件是没有办法破坏的,因为锁的意义就是想让线程之间存在资源互斥访问。
1)破坏请求与保持条件,一次性申请所有的资源;
2)破坏不剥夺条件,占用部分资源的线程进一步申请其他资源时如果申请不到,使其主动释放占有的资源;
3)破坏循环等待条件,按序申请资源来预防线程死锁,按某一顺序申请资源,释放资源则反序释放。
父类中静态方法能否被子类重写?
父类中静态方法不能被子类重写。
重写只适用于实例方法,不能用于静态方法,而且子类当中含有和父类相同签名的静态方法,一般称之为隐藏。
public class A { public static String a = "这是父类静态属性"; public static String getA() { return "这是父类静态方法"; } }
public class B extends A{ public static String a = "这是子类静态属性"; public static String getA() { return "这是子类静态方法"; } public static void main(String[] args) { A a = new B(); System.out.println(a.getA()); } }
如上述代码所示,如果能够被重写,则输出的应该是“这是子类静态方法”。与此类似的是,静态变量也不能被重写。如果想要调用父类的静态方法,应该使用类来直接调用。
什么是不可变对象?有什么好处?
不可变对象是指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象。
比如String、Integer及其它包装类。
不可变对象最大的好处是线程安全。
静态变量和实例变量有什么区别?
静态变量:独立存在的变量,只是位置放在某个类下,可以直接类名加点调用静态变量名使用。并且是项目或程序一启动运行到该类时就直接常驻内存。不需要初始化类再调用该变量。用关键字static声明。静态方法也是同样,可以直接调用。
实例变量:相当于该类的属性,需要初始化这个类后才可以调用。如果这个类未被再次使用,垃圾回收器回收后这个实例也将不存在了,下次再使用时还需要初始化这个类才可以再次调用。
1)存储区域不同:静态变量存储在方法区属于类所有,实例变量存储在堆当中;
2)静态变量与类相关,普通变量则与实例相关;
3)内存分配方式不同。
4)生命周期不同。
需要注意的是从JDK1.8开始用于实现方法区的PermSpace被MetaSpace取代。
Object 类都有哪些公共方法?
1)equals(obj);
判断其他对象是否“等于”此对象。
2)toString();
表示返回对象的字符串。通常,ToString方法返回一个“以文本方式表示”此对象的字符串。结果应该是一个简洁但信息丰富的表达,很容易让人阅读。建议所有子类都重写此方法。
**3)getClass();
**
返回此对象运行时的类型。
4)wait();
表示当前线程进入等待状态。
5)finalize();
用于释放资源。
6)notify();
唤醒在该对象上等待的某个线程。
7)notifyAll();
唤醒在该对象上等待的所有线程。
8)hashCode();
返回对象的哈希代码值。用于哈希查找,可以减少在查找中使用equals的次数,重写equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
9)clone();
实现对象的浅复制,实现Cloneable接口才可以调用这个方法,否则抛出CloneNotSupportedException异常。
Java 创建对象有哪几种方式?
创建对象 | 构造方法说明 |
---|---|
使用new关键字 | 调用构造方法 |
使用Class类的newInstance方法 | 调用构造方法 |
使用Constructor类的newInstance方法 | 调用构造方法 |
使用clone方法 | 没有调用构造方法 |
使用反序列化 | 没有调用构造方法 |
a==b 与 a.equals(b) 有什么区别?
a==b 与 a.equals(b) 有什么区别?
假设a和b都是对象
a==b是比较两个对象内存地址,当a和b指向的是堆中的同一个对象才会返回true。
a.equals(b)是比较的两个值内容,其比较结果取决于equals()具体实现。
多数情况下需要重写这个方法,如String类重写equals()用于比较两个不同对象,但是包含的字母相同的比较:
public boolean equals(Object obj) { if (this == obj) {// 相同对象直接返回true return true; } if (obj instanceof String) { String anotherString = (String)obj; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
Object 中 equals() 和 hashcode() 有什么联系?
Java的基类Object提供了一些方法,其中equals()方法用于判断两个对象是否相等,hashCode()方法用于计算对象的哈希码。equals()和hashCode()都不是final方法,都可以被重写(overwrite)。
hashCode()方法是为对象产生整型的hash值,用作对象的唯一标识。
hashCode()方法常用于基于hash的集合类,如Hashtable、HashMap等,根据Java规范使用equal()方法来判断两个相等的对象,必须具有相同的hashcode。
将对象放入到集合中时,首先要判断放入对象的hashcode是否已经存在,不存在则直接放入集合。
如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的其他对象是否相等,使用equal()判断不相等,则直接将该元素放入集合中,反之不放入集合中。
hashcode() 中可以使用随机数字吗?
hashcode()中不可以使用随机数字不行,这是因为对象的hashcode值必须是相同的。
Java 中 & 和 && 有什么区别?
&是位操作
&&是逻辑运算符
逻辑运算符具有短路特性,而&不具备短路特性。
来看一下代码执行结果:
public class Test{ static String name; public static void main(String[] args){ if(name!=null & name.equals("")){ System.out.println("ok"); }else{ System.out.println("error"); } } }
执行结果:
Exception in thread "main" java.lang.NullPointerException at com.jingxuan.JingXuanApplication.main(JingXuanApplication.java:25)
上述代码执行时抛出空指针异常,若果&替换成&&,则输出日志是error。
一个 .java 类文件中可以有多少个非内部类?
一个.java类文件中只能出现一个public公共类,但是可以有多个default修饰的类。如果存在两个public修饰的类时,会报如下错误:
The public type Test must be defined in its own file
Java 中如何正确退出多层嵌套循环?
1)使用lable标签和break方式;
lable是跳出循环标签。
break lable;是跳出循环语句。
当执行跳出循环语句时会跳出循环标签下方循环的末尾后面。
lable: for(int i=0;i<3;i++){ for(int j=0;j<3;j++){ System.out.println(i); if(i == 2) { break lable; } } }
上述代码在执行过程中,当i=2时,执行跳出循环语句,控制台只输出i=0和i=1的结果,执行继续for循环后面的代码。
0 0 0 1 1 1 2 执行for后面的程序代码
2)通过在外层循环中添加标识符,比如定义布尔类型bo = false,当bo=true跳出循环语句。
浅拷贝和深拷贝有什么区别?
浅拷贝是指被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝是指被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,并且不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
Java 中 final关键字有哪些用法?
Java代码中被final修饰的类不可以被继承。
Java代码中被final修饰的方法不可以被重写。
Java代码中被final修饰的变量不可以被改变,如果修饰引用类型,那么表示引用类型不可变,引用类型指向的内容可变。
Java代码中被final修饰的方法,JVM会尝试将其内联,以提高运行效率。
Java代码中被final修饰的常量,在编译阶段会存入常量池中。
String s = new String(“abc”); 创建了几个String对象?
String s = new String(“abc”); 创建了2个String对象。
一个是字符串字面常数,在字符串常量池中。
一个是new出来的字符串对象,在堆中。
String 和 StringBuffer 有什么区别?
String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象。因此尽量避免对String进行大量拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能。
StringBuffer是对象本身操作,而不是产生新的对象。因此在有大量拼接的情况下,建议使用StringBuffer。
String是线程不安全的,而StringBuffer是线程安全的。
需要注意是Java从JDK5开始,在编译期间进行了优化。如果是无变量的字符串拼接时,那么在编译期间值都已经确定了的话,javac工具会直接把它编译成一个字符常量。比如:
String str = "关注微信公众号" + "“Java精选”" + ",面试经验、专业规划、技术文章等各类精品Java文章分享!";
在编译期间会直接被编译成如下:
String str = "关注微信公众号“Java精选”,面试经验、专业规划、技术文章等各类精品Java文章分享!";
Java 中 3*0.1 == 0.3 返回值是什么?
3*0.1==0.3返回值是false
这是由于在计算机中浮点数的表示是误差的。所以一般情况下不进行两个浮点数是否相同的比较。而是比较两个浮点数的差点绝对值,是否小于一个很小的正数。如果条件满足,就认为这两个浮点数是相同的。
System.out.println(3*0.1 == 0.3); System.out.println(3*0.1); System.out.println(4*0.1==0.4); System.out.println(4*0.1);
执行结果如下:
false 0.30000000000000004 true 0.4
分析:3*0.1的结果是浮点型,值是0.30000000000000004,但是4*0.1结果值是0.4。这个是二进制浮点数算法的计算原因。
a=a+b 和 a+=b 有什么区别吗?
+=操作符会进行隐式自动类型转换,a+=b隐式的将相加操作结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。
byte a = 127; byte b = 127; a = a + b; a += b;
a = a + b; 编译报错:
Type mismatch: cannot convert from int to byte
Java 中线程阻塞都有哪些原因?
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),Java提供了大量方法来支持阻塞。
方法名 | 方法说明 |
---|---|
sleep() | sleep()方法允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止 |
suspend()和resume() | 两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和resume()被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用resume()使其恢复。 |
yield() | yield()使当前线程放弃当前已经分得的CPU时间,但不使当前线程阻塞,即线程仍处于可执行状态,随时可能再次分得CPU时间。调用yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程 |
wait()和notify() | 两个方法配套使用,wait()使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的notify()被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的notify()被调用。 |