面试答案见微信小程序 “Java 精选面试题,3万 道面试题。内容持续更新包括基础、集合、并发、JVM、Spring、Spring MVC、Spring Boot、Spring Cloud、Dubbo、MySQL、Redis、MyBaits、Zookeeper、Linux、数据结构与算法、项目管理工具、消息队列、设计模式Nginx、常见 BUG 问题,网络编程等。
————————————————
面向对象编程有哪些特征?
类别和对象反映了抽象和包装
抽象是解释类与对象之间关系的词。类与对象之间的关系是抽象的关系。总之,类是对象的抽象,对象是特例,即特定的表达形式。
包装有两个含义:一是在对象中包装相关数据和操作代码,形成相对独立的基本单位,不干扰对象。二是将对象中的某些属性和操作私有化,隐藏数据和操作信息,有利于数据安全,防止无关人员修改。把一部分或全部属性和部分功能(函数)对外界屏蔽,就是从外界(类的大括号之外)看不到,不可知,这就是封装的意义。
对象的继承是软件的重用,简单的理解是重用代码,简化重用代码的手段。如何简化,当一个类有相应的属性和操作代码,另一个类也需要写重复代码,然后使用继承方法,以前的类作为父,以后的类作为子,子继承父,理所当然。用一个关键词 extends 完成代码的重用。
没有继承,就没有多态性。继承是多态性的前提。虽然继承来自同一个父亲,但相应的操作是不同的,这被称为多态性。由继承产生的不同衍生物对同一消息有不同的反应。 JDK、JRE、JVM 有什么关系?
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 等等。
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 不同的是,它不包含开发工具 ----- 编译器、调试器等工具。
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 收集遍历,得到相应的值。
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 事务的特点是什么?
事务的四大特点:
事务是数据库的逻辑工作单位。事务中包含的所有操作要么完成,要么不完成
事务执行的结果必须是将数据库从一个一致性状态转变为另一个一致性状态。因此,当数据库只包含成功事务提交的结果时,数据库处于一致性状态。若数据库系统 运行中出现故障,部分事务在完成前被迫中断,部分未完成的事务已写入物理数据库,数据库处于错误状态,或 状态不一致。
事务的执行不受其他事务的干扰。也就是说,事务的内部操作和使用的数据与其他并发事务隔离,并发事务不能相互干扰。
也称为永久性,是指一旦提交事务,数据库中数据的变化应该是永久性的。下一个操作或障不应该对其执行结果有任何影响。 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 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
对象创建责任的反转,在 Spring 中 BeanFacotory 是 IOC 容器的核心接口,负责实例化,定位,配置应用程序中的对象及建立这些对象间的依赖。XmlBeanFacotory 实现 BeanFactory 接口,通过获取 xml 配置文件数据,组成应用对象及对象间的依赖关系。
Spring 中有 3 中注入方式,一种是 set 注入,另一种是接口注入,另一种是构造方法注入。
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 框架有哪些优缺点?
1)服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率,每个模块可以独立开发和部署、代码耦合度低;
2)可以更精准的制定优化服务方案,提高系统的可维护性,每个服务可以单独进行部署,升级某个模块的时候只需要单独部署对应的模块服务即可,效率更高;
3)微服务架构采用去中心化思想,服务之间采用 Restful 等轻量级通讯,比 ESB 更轻量,模块专一性提升,每个模块只需要关心自己模块所负责的功能即可,不需要关心其他模块业务,专一性更高,更便于功能模块开发和拓展;
4)技术选型不再单一,由于每个模块是单独开发并且部署,所以每个模块可以有更多的技术选型方案,如模块 1 数据库选择 mysql,模块 2 选择用 oracle 也是可以的;
5)适于互联网时代,产品迭代周期更短。系统稳定性以及性能提升,由于微服务是几个服务共同组成的项目或者流程,因此相比传统单一项目的优点就在于某个模块提供的服务宕机过后不至于整个系统瘫痪掉,而且微服务里面的容灾和服务降级机制也能大大提高项目的稳定性;从性能而言,由于每个服务是单独部署,所以每个模块都可以有自己的一套运行环境,当某个服务性能低下的时候可以对单个服务进行配置或者代码上的升级,从而达到提升性能的目的。
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)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。
按照以上描述,用户的响应时间相当于是注册信息写入数据库的时间,也就是 50 毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是 50 毫秒。因此采用消息队列的方式,系统的吞吐量提高到每秒 20 QPS。比串行提高了 3 倍,比并行提高了两倍。
用户购买物品下单后,订单系统需要通知库存系统。传统的做法是订单系统调用库存系统的接口。该模式的缺点:
1)假如库存系统无法访问,则订单减库存将失败,从而导致订单失败;
2)订单系统与库存系统耦合;
如何解决以上问题呢?
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,采用拉 / 推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后订单系统写入消息队列就不再关心其他的后续操作,从而实现订单系统与库存系统的应用解耦。
流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中被广泛应用。
秒杀活动,一般会因为流量并发过大,导致流量暴增,应用宕机,从而为解决该问题,一般在应用前端加入消息队列。
控制活动的人数,缓解短时间内高流量压垮应用。当用户的请求,服务器接收后,先写入消息队列。如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面,而其秒杀业务根据消息队列中的请求信息,再做后续处理。
日志处理是指将消息队列用在日志处理中,比如 Kafka 的应用,解决大量日志传输的问题。日志采集客户端,负责日志数据采集,定时写受写入 Kafka 队列,而 Kafka 消息队列,负责日志数据的接收,存储和转发,日志处理应用订阅并消费 kafka 队列中的日志数据。
消息通讯是指消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。 什么是 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 修饰的?
final 修饰符的作用:final 可以修饰类,方法和变量,并且被修饰的类或方法,被 final 修饰的类不能被继承,即它不能拥有自己的子类,被 final 修饰的方法不能被重写, final 修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。
String 为什么要被 final 修饰主要是为了” 安全性 “和” 效率 “的原因。
final 修饰的 String 类型,代表了 String 不可被继承,final 修饰的 char [] 代表了被存储的数据不可更改性。虽然 final 修饰的不可变,但仅仅是引用地址不可变,并不代表了数组本身不会改变。
为什么保证 String 不可变呢?
因为只有字符串是不可变,字符串池才有可能实现。字符串池的实现可以在运行时节约很多 heap 空间,不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么 String interning 将不能实现,反之变量改变它的值,那么其它指向这个值的变量值也会随之改变。
如果字符串是可变,会引起很严重的安全问题。如数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接或在 socket 编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则改变字符串指向的对象值,将造成安全漏洞。
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
因为字符串是不可变的,所以在它创建的时候 HashCode 就被缓存了,不需要重新计算。使得字符串很适合作为 Map 键值对中的键,字符串的处理速度要快过其它的键对象。这就是 HashMap 中的键往往都使用字符串。 final 关键字的基本用法?
在 Java 中 final 关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面从这三个方面来了解一下 final 关键字的基本用法。
当用 final 修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用 final 进行修饰。final 类中的成员变量可以根据需要设为 final,但是要注意 final 类中的所有成员方法都会被隐式地指定为 final 方法。
在使用 final 修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为 final 类。
下面这段话摘自《Java 编程思想》第四版第 143 页:
“使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的 Java 版本中,不需要使用 final 方法进行这些优化了。“
因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为 final 的。即父类的 final 方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。
final 修饰的方法表示此方法已经是 “最后的、最终的” 含义,亦即此方法不能被重写(可以重载多个 final 修饰的方法)。此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中 final 修饰的方法同时访问控制权限为 private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与 final 的矛盾,而是在子类中重新定义了新的方法。(注:类的 private 方法会隐式地被指定为 final 方法。)
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 个阶段,包括:新建、就绪、运行、阻塞、死亡。
当创建 Thread 类的一个实例(对象)时,此线程进入新建状态(未被启动)。
线程已经被启动,正在等待被分配给 CPU 时间片,也就是说此时线程正在就绪队列中排队等候得到 CPU 资源
线程获得 CPU 资源正在执行任务(run () 方法),此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入,线程将一直运行到结束。
由于某种原因导致正在运行的线程让出 CPU 并暂停自己的执行,即进入堵塞状态。
正在睡眠:用 sleep (long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用 wait () 方法。(调用 motify () 方法回到就绪状态)
被另一个线程所阻塞:调用 suspend () 方法。(调用 resume () 方法恢复)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行 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 中常用的注解包含哪些?
@Component 组件,没有明确的角色
@Service 在业务逻辑层使用(service 层)
@Repository 在数据访问层使用(dao 层)
@Controller 在展现层使用,控制器的声明(C * 上使用)
@Autowired:由 Spring 提供
@Inject:由 JSR-330 提供
@Resource:由 JSR-250 提供
都可以注解在 set 方法和属性上,推荐注解在属性上(一目了然,少写代码)。