【精】各大厂问题汇总
创建时间: | 2022/6/26 14:34 |
---|---|
更新时间: | 2023/3/21 19:27 |
作者: | HelloXF |
标签: | 知识库, |
Java
基础
JAVA SE
$关键字
Java 语言目前定义了 51 个关键字,这些关键字不能作为变量名、类名和方法名来使用。以下对这些关键字进行了分类。
-
数据类型:boolean、int、long、short、byte、float、double、char、class、interface。
-
流程控制:if、else、do、while、for、switch、case、default、break、continue、return、try、catch、finally。
-
修饰符:public、protected、private、final、void、static、strict、abstract、transient、synchronized、volatile、native。
-
动作:package、import、throw、throws、extends、implements、this、supper、instanceof、new。
-
保留字:true、false、null、goto、const。
标识符
Java 中标识符是为方法、变量或其他用户定义项所定义的名称。标识符可以有一个或多个字符。在 Java 语言中,标识符的构成规则如下。
-
标识符由数字(09)和字母(AZ 和 a~z)、美元符号($)、下划线(_)以及 Unicode 字符集中符号大于 0xC0 的所有符号组合构成(各符号之间没有空格)。
-
标识符的第一个符号为字母、下划线和美元符号,后面可以是任何字母、数字、美元符号或下划线。
static和 final
final定义的变量可以看做一个常量,不能被改变; final定义的方法不能被覆盖; final定义的类不能被继承。 final static
就是再加上static的特性就可以了 static 和final是没有直接关系的 static
是在内存中分配一块区域,供整个类通用,所有的类的对象都享有它的共同的值
数据类型
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码,用单引号赋值。
$java6大原则
-
单一职责原则 :简单地说就是一个类只做一件事。如果你遵守了这个原则,那么你的类就会划分的很细,每个类都有比较单一的职责,这不就是高内聚、低耦合么!单一职责原则并不是一个类只能有一个函数,而是说这个类中的函数所做的工作是高度相关的,也就是高内聚。
-
依赖反转原则:设计和实现要依赖于抽象而非具体。
-
里氏替换原则 :继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。里氏替换原则通俗的来讲就是: 子类可以扩展父类的功能,但不能改变父类原有的功能。 它包含以下4层含义:1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。2、子类中可以增加自己特有的方法。3、当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。4、当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
-
接口隔离原则:接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构。 采用接口隔离原则对接口进行约束时,要注意以下几点:1、接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。2、为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。3、提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。4、运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。
-
迪米特原则 :一个类应该对自己需要耦合或者调用的类知道得最少,这有点类似于接口隔离原则中的最小接口的概念。被依赖者的内部如何实现、如何复杂都与调用者或者依赖者没有关系,调用者或者依赖者只需要知道它需要它需要的方法即可,其他的一概不关心。
-
开闭原则 :即在需要对软件进行升级、变化时应该通过扩展的形式来实现,而非修改原有代码。
$ConcurrentHashMap
我们熟知的缓存技术(比如redis、memcached)的核心其实就是在内存中维护一张巨大的哈希表,还有大家熟知的HashMap、CurrentHashMap等的应用。
我们知道HashMap是线程不安全的,在多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。
其实可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,
从JDK1.7版本的(两次hash定位值)ReentrantLock(可重入锁)+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。
- 数据结构 :取消了 Segment分段锁 的数据结构,取而代之的是数组+链表+红黑树的结构。 2. 保证线程安全机制 :JDK1.7采用segment的分段锁机制实现线程安全,其中 segment 继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。
其中value用volatile修饰,key和next用final修饰 3. 锁的粒度
:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。 4. 链表转化为红黑树
:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。 5. 查询时间复杂度
:从原来的遍历链表O(n),变成遍历红黑树O(logN)。
红黑树的规则:是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组,
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n
是树中元素的数目。
-
规则1: 每个节点不是黑色就是红色
-
规则2: 根节点为黑色
-
规则3:红色节点的父节点和子节点不能为红色
-
规则4:所有的叶子节点都是黑色(空节点视为叶子节点NIL)
-
规则5:每个节点到叶子节点的每个路径黑色节点的个数都相等。
$集合
java集合框架主要有collection和map两类。
Collection 接口又有 3 种子类型,List、Set 和 Queue,set是无序的并且不可重复。
再下面是一些抽象类,最后是具体实现类,常用的有
ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
来源于Java.util包,是非常实用常用的数据结构 字面意思就是容器。
在Java的util包中有两个所有集合的父接口Collection和Map,
![](【精】各大厂问题汇总_files/Image [1].png)
Collection和Collections
1、java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java
类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
List,Set,Queue接口都继承Collection。
直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。
2、java.util.Collections
是一个包装类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
$多线程
Java线程的底层实现
我们知道,线程是 CPU 独立调度的单位,通过引入线程,实现时分复用,利用并发思想使得我们的程序运行的更加迅速。
主流的操作系统都提供了线程的实现,注意这句话,谁实现的线程?是操作系统,尽管本文侧重于介绍 Java
线程的实现原理,但是请大家清楚一点,实际上实现线程的老大哥,是运行在内核态的操作系统。
Java 语言提供了不同硬件和操作系统平台下对线程操作的统一处理,每个已经执行 start() 且还未结束的 java.lang.Thread
类的实例就代表了一个线程。但是正如我们刚刚所强调的那样,线程可是由操作系统来实现的啊,那么 Java
是如何面向开发者提供的线程统一操作呢?我们来简单的看一下 Thread 类的几个关键方法都有native修饰。
因为在 Java 的 API 中,一个 native 方法往往意味着这个方法无法使用平台无关的手段来实现。所以,还是那句话,实际上线程的实现与 Java
无关,由平台所决定,Java 所做的是将 Thread
对象映射到操作系统所提供的线程上面去,对外提供统一的操作接口,向程序员隐藏了底层的细节,使程序员感觉在哪个平台上编写的有关于线程的代码都是一样的。这也是
Java 这门语言诞生之初的核心思想,一处编译,到处运行,只面向虚拟机,实现所谓的平台无关性,而这个平台无关性就是由虚拟机为我们提供的。
操作系统实现线程主要有 3 种方式
-
用户级线程
-
内核级线程
-
用户级线程 + 内核级线程,混合实现
Java 线程的实现
Java 线程在 JDK1.2之前,是基于称为“绿色线程”的用户线程实现的,而在 JDK 1.2
中,线程模型替换为基于操作系统原生线程模型来实现,因此,在目前的 JDK 版本中,操作系统支持怎样的线程模型,在很大程度上决定了 Java
虚拟机的线程是怎样映射的,这点在不同平台上没有办法达成一致,虚拟机规范中也并未限定 Java 线程需要使用哪种线程模型来实现。
举个例子,对于 Sun JDK 来说,它的 Windows 版与 Linux 版都是使用一对一的线程模型实现的,一条 Java
线程就是映射到一条轻量级进程之中,因为 Windows 和 Linux 系统提供的线程模型就是一对一的。
原文链接:https://blog.csdn.net/u013568373/article/details/93474642
线程池种类
Executors工具类提供静态工厂方法
newcachedThreadPool可缓存
newFixThreadPool定长,可控制最大并发数,超出进入队列
newScheduledThreadpool 定时周期执行
newSingleThreadExecutors 单线程化
线程状态
runnable sleep dead wait notifial blocked join running
$CAS
cas是compareandswap的简称,从字面上理解就是比较并更新,简单来说:从某一内存上取值V,和预期值A进行比较,如果内存值V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止。java中的
Atomic 系列就是使用cas实现的
volatile只用来保证变量可见性,但不保证原子性
Atomic:性能高,轻量,存在ABA问题可以通过添加版本号解决,线程安全
** VOLATILE**
是JAVA中一个极其重要关键字,它保证的内存的可见性,但是并不能够保证原子性。而CAS是采用一种无锁的方式,解决VOLATILE所不能带来的原子性等这类问题。接下来,就讲讲VOLATILE与CAS吧!
2.Atomic 原子操作*
关于atomic*原子操作,这里以AtomicInteger类为例
使用场景:
AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,
不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
ABA
A-> B ->A 过程有改变但是 compareandset不知道
$反射原理和作用
JAVA语言编译之后会生成一个.claSs 文件,反射就是通过字节码文件找到某一个类、类中
的方法以及属性等。反射的实现主要借助以下四个类:Class:类的对象,Constructor:类的构
造方法,Field:类中的属性对象,Method:类中的方法对象。
作用:反射机制指的是程序在运行时能够获取自身的信息。在JAVA中,只要给定类的名字,那么就可以通过反射机制来获取类的所有信息。
$JVM内存分配
(1)堆内存分配
JVM初始分配的内存由-Xms 指定,默认是物理内存的1/64;JVM 最大分配的内存由-Xmx 指
定,默认是物理内存的1/4。默认空余堆内存小于 40%时,JVM就会增大堆直到-Xmx 的最大限制;
空余堆内存大于 70%时,JVM会减少堆直到-Xms 的最小限制。因此服务器一般设置-XmS、-Xmx
相等以避免在每次GC后调整堆的大小。
(2)非堆内存分配
JVM使用-XX:PermSize 设置非堆内存初始值,默认是物理内存的 1/64;由 XX:MaxPermSize
设置最大非堆内存的大小,默认是物理内存的1/4。
$GC算法
①GC(GarbageCollection 垃圾收集),GC 的对象是堆空间和永久区
②GC 算法包含:引用计数法,标记清除,标记压缩,复制算法。
J2EE
过滤器(Filter)的工作原理
一、Filter简介
Filter也称之为过滤器,它是Servlet技术中最激动人心的技术之一,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp,
Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等
一些高级功能。
Servlet
API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter
技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,Filter接口源代码:
public abstract interface Filter{
public abstract void init(FilterConfig paramFilterConfig) throws
ServletException;
public abstract void doFilter(ServletRequest paramServletRequest,
ServletResponse paramServletResponse, FilterChain
paramFilterChain) throws IOException, ServletException;
public abstract void destroy();
}
二、Filter是如何实现拦截的?(Filter的工作原理)
Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,
都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
调用目标资源之前,让一段代码执行。
是否调用目标资源(即是否让用户访问web资源)。
调用目标资源之后,让一段代码执行。
web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个
doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,
否则web资源不会被访问。
3.2、 Filter链
在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。web服务器根据Filter在web.xml文件中的注册顺序,
决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter
方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,
如果没有,则调用目标资源。
四,S pring框架下,过滤器的配置
如果项目中使用了Spring框架,那么,很多过滤器都不用自己来写了,Spring为我们写好了一些常用的过滤器。下面我们就以字符编码的
过滤器CharacterEncodingFilter为例,只需要配置加上该过滤器就能使用。
如果我们不使用Spring的CharacterEncodingFilter类,可以自己来写。
GenericFilterBean类:
public abstract class GenericFilterBean implements Filter, BeanNameAware,
ServletContextAware, InitializingBean, DisposableBean
还没有过瘾,那就再看一个项目中使用过的一个过滤器:InvilidCharacterFilter(防止脚本攻击的过滤器)
五、Filter的生命周期
5.1、Filter的创建
Filter的创建和销毁由web服务器负责。 web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化
功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前
filter配置信息的FilterConfig对象。
5.2、Filter的销毁
web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。
5.3、FilterConfig接口
用户在配置filter时,可以使用
filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:
String getFilterName():得到filter的名称。
String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
public ServletContext getServletContext():返回Servlet上下文对象的引用。
六、Filter的部署时的一些参数的含义
Filter的部署分为两个步骤:
1、注册Filter
2、映射Filter
6.1、注册Filter
开发好Filter之后,需要在web.xml文件中进行注册,这样才能够被web服务器调用。在web.xml文件中注册Filter范例:
可以使用FilterConfig接口对象来访问初始化参数。如果过滤器不需要指定初始化参数,那么
6.2、映射Filter
在web.xml文件中注册了Filter之后,还要在web.xml文件中映射Filter
名称和资源访问的请求路径
容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个
子元素用来指定 Filter 对资源的多种调用方式进行拦截。如下:
REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问
时,那么该过滤器就不会被调用。
INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
Spring
$依赖注入 IOC方式
set 构造器 接口
IOC(控制反转)就是 依赖倒置原则 的一种代码设计思路。 就是把原先在代码里面需要实现的对象创建、对象之间的依赖,反转给容器来帮忙实现 。
Spring IOC容器通过xml,注解等其它方式配置类及类之间的依赖关系,完成了对象的创建和依赖的管理注入。实现IOC的主要设计模式是 工厂模式
。
使用IOC的好处
-
集中管理,实现类的可配置和易管理。
-
降低了类与类之间的耦合度。
a setter
原理 :
在目标对象中,定义需要注入的依赖对象对应的属性和setter方法;“让ioc容器调用该setter方法”,将ioc容器实例化的依赖对象通过setter注入给目标对象,封装在目标对象的属性中。
b 构造器
原理 :
为目标对象提供一个构造方法,在构造方法中添加一个依赖对象对应的参数。ioc容器解析时,实例化目标对象时会自动调用构造方法,ioc只需要为构造器中的参数进行赋值;将ioc实例化的依赖对象作为构造器的参数传入。
【接口注入
原理 : 为依赖对象提供一个接口实现,将接口注入给目标对象,实现将接口的实现类注入的效果。比如HttpServletRequest
HttpServletResponse接口
注意:这是ioc提供的方式,spring中的ioc技术并没有实现该种注入方式】
c 方法注入
原理 :
在目标对象中定义一个普通的方法,将方法的返回值设置为需要注入的依赖对象类型。通过ioc容器调用该方法,将其创建的依赖对象作为方法的返回值返回给目标对象。
$$spring core与context理解
Spring core是核心层,拥有这BeanFactory这个强大的工厂,是所有bean的管理器;
而spring context是上下文运行环境,基于spring core之上的一个架构, 它之上是spring
web,这下明白了吧,主要应用就是web的一个初始化上下文环境;
Spring框架由7个定义良好的模块(组件)组成,各个模块可以独立存在,也可以联合使用。
(1)Spring
Core:核心容器提供了Spring的基本功能。核心容器的核心功能是用Ioc容器来管理类的依赖关系.Spring采用的模式是调用者不理会被调用者的实例的创建,由Spring容器负责被调用者实例的创建和维护,需要时注入给调用者。这是目前最优秀的解耦模式。
(2)Spring AOP:Spring的AOP模块提供了面向切面编程的支持。SpringAOP采用的是纯Java实现。Spring
AOP采用基于代理的AOP实现方案,AOP代理由Ioc容器负责生成、管理,依赖关系也一并由Ioc容器管理,尽管如此,Spring
Ioc容器并不依赖于AOP,这样我们可以自由选择是否使用AOP。
AOP(面向切面)是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。
AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
使用AOP的好处
-
降低模块的耦合度
-
使系统容易扩展
-
提高代码复用性
(3)Spring ORM:提供了与多个第三方持久层框架的良好整合。
(4)Spring DAO:
Spring进一步简化DAO开发步骤,能以一致的方式使用数据库访问技术,用统一的方式调用事务管理,避免具体的实现侵入业务逻辑层的代码中。
(5)Spring
Context:它是一个配置文件,为Spring提供上下文信息,提供了框架式的对象访问方法。Context为Spring提供了一些服务支持,如对国际化(i18n)、电子邮件、校验和调度功能。
(6)Spring Web:提供了基础的针对Web开发的集成特性,例如多方文件上传,利用Servlet
listeners进行IoC容器初始化和针对Web的applicationContext.
(7)Spring
MVC:提供了Web应用的MVC实现。Spring的MVC框架并不是仅仅提供一种传统的实现,它提供了一种清晰的分离模型,在领域模型代码和web
form之间。并且,还可以借助Spring框架的其他特性
$Spring MVC原理
Dispatcher调度员
①客户端的所有请求都交给前端控制器DispatcherServlet 来处理,它会负责调用系统的其他模
块来真正处理用户的请求。
②DispatcherServlet 收到请求后,将根据请求的信息(包括URL、HTTP协议方法、请求头、
请求参数、Cookie 等)以及HandlerMapping 的配置找到处理该请求的 Handler(任何一个对象
都可以作为请求的Handler)。
③在这个地方 Spring 会通过HandlerAdapter 对该处理器进行封装。
④HandlerAdapter 是一个适配器,它用统一的接口对各种Handler 中的方法进行调用。
5Handler完成对用户请求的处理后,会返回一个ModelAndView对象给 DispatcherServlet,
ModelAndView顾名思义,包含了数据模型以及相应的视图的信息。
③ModelAndView 的视图是逻辑视图,DispatcherServlet 还要借助ViewResolver 完成从逻辑视
图到真实视图对象的解析工作。
当得到真正的视图对象后,DispatcherServlet会利用视图对象对模型数据进行染。
③客户端得到响应,可能是一个普通的 HTML 页面,也可以是 XML或 JSON 字符串,还可以是一
张图片或者一个PDF文件。
EJB
既然说了EJB 是为了"服务集群"和"企业级开发",那么,总得说说什么是所谓的"服务
集群"和"企业级开发"吧!
J2EE 对于这个问题的处理方法是将业务逻辑从客户端软件中抽取出来,封装在一个组
件中。这个组件运行在一个独立的服务器上,客户端软件通过网络调用组件提供的服务以实
现业务逻辑,而客户端软件的功能单纯到只负责发送调用请求和显示处理结果。在J2EE 中,
这个运行在一个独立的服务器上,并封装了业务逻辑的组件就是EJB(Enterprise Java
Bean)组件。
JSP内置对象及方法
JSP一共有9个内置对象:request、response、session、application、out、pagecontext、config、page、exception。
- request对象。
resquest对象是javax.servlet.http.HttpServletRequest类的一个实例。客户端的请求信息封装在resquest中发送给服务器端。request的作用域是一次请求。
请求方式:request.getMethod()
请求的资源:request.getRequestURI()
请求用的协议:request.getProtocol()
请求的文件名:request.getServletPath()
请求的服务器的IP:request.getServerName()
请求服务器的端口:request.getServerPort()
客户端IP地址:request.getRemoteAddr()
客户端主机名:request.getRemoteHost()
- response对象。
response对象是javax.servlet.http.HttpServletResponse的一个实例。服务端
的相应信息封装在response中返回。
重定向客户端请求 response.sendRedirect(index.jsp)
- session对象。
session对象是javax.servlet.http.HttpSession的一个实例。在第一个JSP页面被装载时自动创建,完成会话期管理。
session对象内部使用Map类来保存数据,因此保存数据的格式为 “Key/value”。
获取Session对象编号 session.getId()
添加obj到Session对象 session.setAttribute(String key,Object obj)
获取Session值 session.getAttribute(String key)
- application对象。
application对象是javax.servlet.ServletContext的一个实例。
实现了用户间数据的共享,可存放全局变量。它开始于服务器的启动,直到服务器的关闭,在此期间,此对象将一直存在。
添加obj到Application对象 application.setAttribute(String key,Object obj)
获取Application对象中的值 application.getAttribute(String key)
- out 对象。
out对象是javax.servlet.jsp.jspWriter的一个实例。用于浏览器输出数据。
输出各种类型数据 out.print()
输出一个换行符 out.newLine()
关闭流 out.close()
- pageContext 对象。
pageContext 对象是javax.servlet.jsp.PageContext的一个对象。作用是取得任何范围的参数,通过它可以获取
JSP页面的out、request、reponse、session、application 等对象。
- config 对象。
config 对象是javax.servlet.ServletConfig的一个对象。主要作用是取得服务器的配置信息。通过 pageConext对象的
getServletConfig() 方法可以获取一个config对象。
- cookie 对象。
cookie 对象是Web服务器保存在用户硬盘上的一段文本。唯一的记录了用户的访问信息。
将Cookie对象传送到客户端 Cookie c = new Cookie(username",john");
读取保存到客户端的Cookie response.addCookie(c)
- exception 对象。
exception 对象的作用是显示异常信息,只有在包含 isErrorPage="true" 的页面中才可以被使用。
forword与redirect的区别
1.从[地址栏](https://www.baidu.com/s?wd=地址栏&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-TLwGUv3En1R1nW6snj6z)显示来说
forward是服务器请求资源,服务器直接访问目标地址的[URL](https://www.baidu.com/s?wd=URL&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z),把那个[URL](https://www.baidu.com/s?wd=URL&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z)的响应内容读取过来,然后把这些内容再发给[浏览器](https://www.baidu.com/s?wd=浏览器&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z).[浏览器](https://www.baidu.com/s?wd=浏览器&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z)根本不知道服务器发送的内容从哪里来的,所以它的[地址栏](https://www.baidu.com/s?wd=地址栏&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-TLwGUv3En1R1nW6snj6z)还是原来的地址.
redirect是[服务端](https://www.baidu.com/s?wd=服务端&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z)根据逻辑,发送一个状态码,告诉[浏览器](https://www.baidu.com/s?wd=浏览器&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z)重新去请求那个地址.所以[地址栏](https://www.baidu.com/s?wd=地址栏&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z)显示的是新的[URL](https://www.baidu.com/s?wd=URL&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-TLwGUv3En1R1nW6snj6z).
2.从[数据共享](https://www.baidu.com/s?wd=数据共享&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-TLwGUv3En1R1nW6snj6z)来说 forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据.
3.从运用地方来说 forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等. 4.从效率来说 forward:高. redirect:低.
redis
快:利用内存 缓存。 单线程多路复用IO CPU核心数不是瓶颈
$redis的list实现消息队列
Redis 中list 的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列生产者/
消费者模型)。消息的生产者只需要通过lpush将消息放入list,消费者便可以通过 rpop取出
该消息,并且可以保证消息的有序性。如果需要实现带有优先级的消息队列也可以选择 sorted
set。而 pub/sub功能也可以用作发布者 /订阅者模型的消息。
redis的AOF和RDB区别
RDB 的优点:
这种文件非常适合用于进行备份: 比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。
这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。 RDB 非常适用于灾难恢复 (disaster recovery)。
RDB 的缺点:
如果你需要尽量避免在服务器故障时丢失数据 , 那么 RDB 不适合你 。 虽然 Redis 允许你设置不同的保存点(save
point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。 因此你可能会至少 5
分钟才保存一次 RDB 文件。 在这种情况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据 。
AOF 的优点
使用 AOF 持久化会让 Redis 变得非常耐久(much more durable): 你可以设置不同的 fsync 策略 ,比如无 fsync
,每秒钟一次 fsync ,或者每次执行写入命令时 fsync 。 AOF 的默认策略为每秒钟 fsync 一次 ,在这种配置下,Redis
仍然可以保持良好的性能,并且就算发生故障停机,也 最多只会丢失一秒钟的数据 ( fsync
会在后台线程执行,所以主线程可以继续努力地处理命令请求)。
AOF 的缺点
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。
在一般情况下, 每秒 fsync 的性能依然非常高 , 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。
不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。
二者的区别
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
RDB 和 AOF ,我应该用哪一个?
-
如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久。
-
AOF 将 Redis 执行的每一条命令追加到磁盘中,处理巨大的写入会降低 Redis 的性能,不知道你是否可以接受。
数据库备份和灾难恢复 :定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF
恢复的速度要快。
Redis 支持同时开启 RDB 和 AOF,系统重启后,Redis 会优先使用 AOF 来恢复数据,这样丢失的数据会最少。
JSTL
JSTL 是JSP的标准标签库
JSP的标签集合 ,按照类别包括
核心标签,格式化标签,JSTL函数,SQL标签和XML标签
,其中前三个用的概率较高。要想使用JSTL标签库我们首先要做的就是引入对应的Jar包【standard.jar和jstl.jar】。有时候我们在jsp页面上面要嵌套大量的Java代码,但是又要在页面上进行源码的编写,复杂且难以维护,所以我们就可以利用我们的JSTL标签库进行解决这个问题。
![](【精】各大厂问题汇总_files/Image [2].png)![](【精】各大厂问题汇总_files/Image
[3].png)![](【精】各大厂问题汇总_files/Image [4].png)
EL
EL是JSP的表达式语言
,EL表达式使我们在访问JavaBean中的数据非常简单,EL
表达式语法为【${expr}】,在jsp页面中,常用于获取后台传递的数据。通常情况下,我们将JSTL标签库与EL表达式进行结合使用,能很方便的进行数据的展示。
![](【精】各大厂问题汇总_files/Image [5].png)
对NuLL的判断
Empty 对于 null 和”” 都会返回true
== null 则是对null 返回true 而对”” 则是返回false
Not empty 不等于空,包括不等于null 和不等于””
<c:if test="${rdinfo.isProprietaryShop eq '0' or rdinfo.isProprietaryShop eq
null }">
<c:if test =”${empty arraylist}”> // 判断对象是否为空对象
$GET和POST
①get请求用来从服务器上获得资源,而post 是用来向服务器提交数据;
②get将表单中数据按照 name=value 的形式,添加到 action 所指向的 URL 后面,并且两者使
用"?"连接,而各个变量之间使用"&"连接;post 是将表单中的数据放在 HTTP 协议的请求头或消
息体中,传递到action所指向URL:
③get 传输的数据要受到URL长度限制(1024字节);而 post 可以传输大量的数据,上传文件
通常要使用 post方式;
④使用 get 时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用 get;对于敏
感数据还是应用使用post;
③get 使用 MIME类型 application/x-www-form-urlencoded 的 URL编码(也叫百分号编码)文
本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是"%20”。
Cookies和session
l、cookie 数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗
考虑到安全应当使用 session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,
考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。
$大型网站在架构上应该考虑问题
一分层 :分层是处理任何复杂系统最常见的手段之一,将系统横尚切分成若千个层面,每个
层面只承担单一的职责,然后通过下层为上层提供的基础设施和服务以及上层对下层的调用来形
成一个完整的复杂的系统。计算机网络的开放系统互联参考模型(OSI/RM)和Internet 的TCP/IF
模型都是分层结构,大型网站的软件系统也可以使用分层的理念将其分为持久层(提供数据存储
和访问服务)、业务层(处理业务逻辑,系统中最核心的部分)和表示层(系统交互、视图展示)。
需要指出的是:(1)分层是逻辑上的划分,在物理上可以位于同一设备上也可以在不同的设备
上部署不同的功能模块,这样可以使用更多的计算资源来应对用户的并发访问;(2)层与层之
间应当有清晰的边界,这样分层才有意义,才更利于软件的开发和维护。
一分割 :分割是对软件的纵尚切分。我们可以将天型网站的不同功能和服务分割开,形成高内聚
低耦合的功能模块(单元)。在设计初期可以做一个粗粒度的分割,将网站分割为若干个功能模
块,后期还可以进一步对每个模块进行细粒度的分割,这样一方面有助于软件的开发和维护,另
一方面有助于分布式的部署,提供网站的并发处理能力和功能的扩展。
分布式 :除了上面提到的内容,网站的静态资源(JavaScript、CsS、图片等)也可以采用独
立分布式部署并采用独立的域名,这样可以减轻应用服务器的负载压力,也使得浏览器对资源的
加载更快。数据的存取也应该是分布式的,传统的商业级关系型数据库产品基本上都支持分布式
部署,而新生的NoSQL产品儿乎都是分布式的。当然,网站后台的业务处理也要使用分布式技术,
例如查询索引的构建、数据分析等,这些业务计算规模庞大,可以使用Hadoop以及MapReduce
分布式计算架来处理。
一集群 :集群使得有更多的服务器提供相同的服务,可以更好的提供对并发的支持。
一缓存 :所谓缓存就是用空间换取时间的技术,将数据尽可能放在距离计算最近的位置。使用缓
存是网站优化的第一定律。我们通常说的CDN、反向代理、热点数据都是对缓存技术的使用。
一异步 :异步是实现软件实体之间解耦合的义一重要手段。异步架构是典型的生产者消费者模式
二者之间没有直接的调用关系,只要保持数据结构不变,彼此功能实现可以随意变化而不互相影
响,这对网站的扩展非常有利。使用异步处理还可以提高系统可用性,加快网站的响应速度(用
Ajax加载数据就是一种异步技术),同时还可以起到削峰作用(应对瞬时高并发)。";能
推迟处理的者都要推迟处理”是网站优化的第二定律,而异步是践行网站优化第二定律的重要手段。
一亢余 :各种服务器都要提供相应的亢余服务器以便在某台或某些服务器岩机时还能保证网站可
以正常工作,同时也提供了灾难恢复的可能性。亢余是网站高可用性的重要保证。
$J2EE中常用的名词解释
-
web容器 :给处于其中的应用程序组件(JSP,SERVLET)提供一个环境,使JSP,SERVLET直接和容器中的环境变量接接口互,不必关注其它系统问题。主要有WEB服务器来实现。例如:TOMCAT,WEBLOGIC,WEBSPHERE等。该容器提供的接口严格遵守J2EE规范中的WEB APPLICATION 标准。我们把遵守以上标准的WEB服务器就叫做J2EE中的WEB容器。
-
Web container :实现J2EE体系结构中Web组件协议的容器。这个协议规定了一个Web组件运行时的环境,包括安全,一致性,生命周期管理,事务,配置和其它的服务。一个提供和JSP和J2EE平台APIs界面相同服务的容器。一个Web container 由Web服务器或者J2EE服务器提供。
-
EJB容器 :Enterprise java bean 容器。更具有行业领域特色。他提供给运行在其中的组件EJB各种管理功能。只要满足J2EE规范的EJB放入该容器,马上就会被容器进行高效率的管理。并且可以通过现成的接口来获得系统级别的服务。例如邮件服务、事务管理。一个实现了J2EE体系结构中EJB组件规范的容器。 这个规范指定了一个Enterprise bean的运行时环境,包括安全,一致性,生命周期,事务, 配置,和其他的服务。
-
JNDI :(Java Naming & Directory Interface)JAVA命名目录服务。主要提供的功能是:提供一个目录系统,让其它各地的应用程序在其上面留下自己的索引,从而满足快速查找和定位分布式应用程序的功能。
-
JMS :(Java Message Service)JAVA消息服务。主要实现各个应用程序之间的通讯。包括点对点和广播。
-
JTA :(Java Transaction API)JAVA事务服务。提供各种分布式事务服务。应用程序只需调用其提供的接口即可。
JPA(java persistence API)
JPA 通过JDK5.0的注解或XML来描述 对象-关系表的映射关系,并 将运行期的实体对象持久化存储到数据库中
-
JAF :(Java Action FrameWork)JAVA安全认证框架。提供一些安全控制方面的框架。让开发者通过各种部署和自定义实现自己的个性安全控制策略。
-
RMI/IIOP :(Remote Method Invocation /internet对象请求中介协议)他们主要用于通过远程调用服务。例如,远程有一台计算机上运行一个程序,它提供股票分析服务,我们可以在本地计算机上实现对其直接调用。当然这是要通过一定的规范才能在异构的系统之间进行通信。RMI是JAVA特有的。RMI-IIOP出现以前,只有RMI和CORBA两种选择来进行分布式程序设计。RMI-IIOP综合了RMI和CORBA的优点,克服了他们的缺点,使得程序员能更方便的编写分布式程序设计,实现分布式计算。首先,RMI-IIOP综合了RMI的简单性和CORBA的多语言性(兼容性),其次RMI-IIOP克服了RMI只能用于Java的缺点和CORBA的复杂性(可以不用掌握IDL)。
数据库
Java数据库连接池实现原理
一般来说,Java应用程序访问数据库的过程是: ①装载数据库驱动程序; ②通过jdbc建立数据库连接; ③访问数据库,执行sql语句; ④断开数据库连接。
程序开发过程中,存在很多问题:首先,每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。
可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库
通过上面的分析,我们可以看出来,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。
数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接
创建数据库连接池大概有3个步骤:
① 创建ConnectionPool实例,并初始化创建10个连接,保存在Vector中(线程安全)
② 实现getConnection()从连接库中获取一个可用的连接
③ returnConnection(conn) 提供将连接放回连接池中方法
Innodb中的行锁与表锁
InnoDB,是MySQL的数据库引擎之一,现为MySQL的默认存储引擎
前面提到过,在Innodb引擎中既支持行锁也支持表锁,那么什么时候会锁住整张表,什么时候或只锁住一行呢?
InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!
在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。
行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。行级锁的缺点是:由于需要请求大量的锁资源,所以速度慢,内存消耗大。
$数据库索引底层实现,什么时候失效
B+树
没有遵循最左匹配原则
一些关键字会导致索引失效,例如or,
! = , not in, is null ,is not unll
like 查询是以%开头
隐式转换会导致索引失效。
对索引应用内部函数,索引字段进行了运算。
$数据库设计三范式
1NF:字段不可分;
2NF:有主键,非主键字段依赖主键;
3NF:非主键字段不能相互依赖;
解释:
1NF: 原子性 字段不可再分,否则就不是关系数据库;
2NF: 唯一性 一个表只说明一个事物;
3NF:每列都与主键有 直接关系 ,不存在传递依赖;
数据库事务ACID特性
ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性指事务前后数据的完整性必须保持一致。
隔离性指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。
持久性是指一人事务一旦提交,它对数据库中数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。
MYSQL数据库的主从复制
0、为什么需要主从复制?
1、在业务复杂的系统中,有这么一个情景,有一句sql语句需要锁表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作。
2、做数据的热备
3、架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
1、什么是mysql的主从复制?
MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL
默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
2、mysql复制原理
原理:
(1)master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中;
(2)slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/OThread请求master二进制事件
(3)同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQLThread将进入睡眠状态,等待下一次被唤醒。
也就是说:
-
从库会生成两个线程,一个I/O线程,一个SQL线程;
-
I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
-
主库会生成一个log dump线程,用来给从库I/O线程传binlog;
-
SQL线程,会读取relay log文件中的日志,并解析成sql语句逐一执行;
注意:
1--
master将操作语句记录到binlog日志中,然后授予slave远程连接的权限(master一定要开启binlog二进制日志功能;通常为了数据安全考虑,slave也开启binlog功能)。
2--slave开启两个线程:IO线程和SQL线程。其中:IO线程负责读取master的binlog内容到中继日志relay
log里;SQL线程负责从relay
log日志里读出binlog内容,并更新到slave的数据库里,这样就能保证slave数据和master数据保持一致了。 3--
Mysql复制至少需要两个Mysql的服务,当然Mysql服务可以分布在不同的服务器上,也可以在一台服务器上启动多个服务。 4--
Mysql复制最好确保master和slave服务器上的Mysql版本相同(如果不能满足版本一致,那么要保证master主节点的版本低于slave从节点的版本)
5--master和slave两节点间时间需同步
具体步骤:
1、从库通过手工执行change master to 语句连接主库,提供了连接的用户一切条件(user
、password、port、ip),并且让从库知道,二进制日志的起点位置(file名 position 号); start slave
2、从库的IO线程和主库的dump线程建立连接。
3、从库根据change master to 语句提供的file名和position号,IO线程向主库发起binlog的请求。
4、主库dump线程根据从库的请求,将本地binlog以events的方式发给从库IO线程。
5、从库IO线程接收binlog events,并存放到本地relay-
log中,传送过来的信息,会记录到http://master.info中
6、从库SQL线程应用relay-log,并且把应用过的记录到[http://relay-
log.info](https://link.zhihu.com/?target=http%3A//relay-
log.info)中,默认情况下,已经应用过的relay 会自动被清理purge
3、mysql主从形式
(一)一主一从
![](【精】各大厂问题汇总_files/Image [1].jpg)
(二)主主复制
![](【精】各大厂问题汇总_files/Image [2].jpg)
(三)一主多从
![](【精】各大厂问题汇总_files/Image [3].jpg)
(四)多主一从
![](【精】各大厂问题汇总_files/Image [4].jpg)
(五)联级复制
![](【精】各大厂问题汇总_files/Image [5].jpg)
4、mysql主从同步延时分析
mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高,slave的sql
thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作是随机的,不是顺序,所以成本要高很多,另一方面,由于sql
thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL
thread所能处理的速度,或者当slave中有大型query语句产生了锁等待,那么延时就产生了。
解决方案:
1.业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力。
2.单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。
3.服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力。
4.不同业务的mysql物理上放在不同机器,分散压力。
5.使用比主库更好的硬件设备作为slave,mysql压力小,延迟自然会变小。
6.使用更加强劲的硬件设备
mysql优化方法
1、选取最适用的字段属性(非null,字段长度合适,)
2、使用连接(JOIN)来代替子查询(Sub-Queries)
3、使用联合(UNION)来代替手动创建的临时表
4、事务
事物以BEGIN关键字开始,COMMIT关键字结束。在这之间的一条SQL操作失败,那么,ROLLBACK命令就可以把数据库恢复到BEGIN开始之前的状态。
BEGIN;
INSERT INTO salesinfo SET CustomerID=14;
UPDAT Einventory SET Quantity=11 WHERE item='book';
COMMIT;
事务的另一个重要作用是当多个用户同时使用相同的数据源时,它可以利用锁定数据库的方法来为用户提供一种安全的访问方式,这样可以保证用户的操作不被其它的用户所干扰。
5、锁定表
包含有WRITE关键字的LOCKTABLE语句可以保证在UNLOCKTABLES命令被执行之前,不会有其它的访问来对表进行插入、更新或者删除的操作。
6、使用外键
锁定表的方法可以维护数据的完整性,但是它却不能保证数据的关联性。这个时候我们就可以使用外键。
例如,外键可以保证每一条销售记录都指向某一个存在的客户。在这里,外键可以把customerinfo表中的CustomerID映射到salesinfo表中CustomerID,任何一条没有合法CustomerID的记录都不会被更新或插入到salesinfo中。
7、使用索引
索引是提高数据库性能的常用方法,它可以令数据库服务器以比没有索引快得多的速度检索特定的行,尤其是在查询语句当中包含有MAX(),MIN()和ORDERBY这些命令的时候,性能提高更为明显。
8、优化的查询语句
本文通过8个方法优化Mysql数据库:创建索引、复合索引、索引不会包含有NULL值的列、使用短索引、排序的索引问题、like语句操作、不要在列上进行运算、不使用NOT
IN和<>操作
数据库连接池的工作原理
J2EE服务器启动时会建立一定数量的池连接,并一直维持不少于此数目的池连接。客户端程序需要连接时,池驱动程序会返回一个未使用的池连接并将其表记为忙。如果当前没有空闲连接,池驱动程序就新建一定数量的连接,新建连接的数量由配置参数决定。当使用的池连接调用完成后,池驱动程序将此连接表记为空闲,其他调用就可以使用这个连接。
SOAP和REST
wsdl(网络服务描述语言)是Web
Service的描述语言,也就是说wsdl文件是soap的使用说明书。在学习soap之前,认识WSDL是非常有必要的,只有能看懂WSDL文件,我们才可以去调用soap类型的Web服务,下面是一个非常简单的wsdl文件。
-JAX-RS(JSR 311 & JSR 339 & JSR 37O: 是Java 针对 REST (Representation State Transfer)架构风格制定的一套 Web Service 规范。REST是一种软件架构模式,是一种风格,它不像 SOAP那样本身承载着一种消息协议,(两种风格的 Web Service 均采用了HTTP 做传输协议,因为HTTP协议能穿越防火墙,Java的远程方法调用(RMI)等是重量级协议,通常不能穿越防火墙),因此可以将 REST视为基于HTTP协议的软件架构。REST 中最重要的两个概念是资源定位和资源操作,而HTTP协议恰好完整的提供了这两人点。HTTP 协议中的 URI 可以完成资源定位,而 GET、POST、OPTION、DELETE方法可以完成资源操作。因此REST 完全依赖HTTP协议就可以完成 WeService,而不像 SOAP协议那样只利用了 HTTP 的传输特性,定位和操作都是由 SOAP 协议自身完成的,也正是由于SOAP消息的存在使得基于SOAP的web Service显得笨重而逐渐被淘汰。
网络
TCP应用(协议),可靠性
当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于些要求可靠的应用,比如 HTTP、HTTPS、FTP
等传输文件的协议,POP、SMTP 等邮件传输的协议三次握手超时重传,滑动窗口(避免接收双方网速不一致导致数据丢失,一种拥塞控制的协议),拥塞控制
windows和linux常用命令
win: 切换目录 cd, 列出所有任务及进程号 tasklist ,杀进程 taskkill netstat
查看网络连接状态
ping telnet ipconfig
shell常用
mkdir 和 rmdir
cp:复制命令
mv:移动命令
echo命令将输入的字符串送往标准输出,输出的字符串间以空白字符隔开, 并在最后加上换行符。
grep 命令用于从文件面搜索包含指定模式的行并打印出来,它是一种强大的文本搜索工具,支持使用正则表达式搜索文本。
rm删除命令
pwd:用于显示用户当前工作目录
cd: 用于切换用户当前工作目录
文件操作:
1、编辑文件 vi 文件名 (或者说是新建文件并用vi编辑)
2、复制文件 cp a文件 b文件 (将a文件复制一份,b就是复制文件(副本)。(两个文件都在当前路径,可以分别指定路径)
3、复制文件目录 cp a目录 b目录 -r 将a目录(包含里面的全部文件)内容 复制到b目录下,(-r 递归复制)
4、新建文件 touch 文件名 (文件不存在就新建,存在就更新新建的最新修改时间)
5、移动文件 mv a文件 b目录 (将a文件移动到b目录下)
6、重命名文件 mv a文件 b文件 (将a文件命名为b文件,注:都是在当前路径下)
7、删除文件 rm a文件 (删除a文件)
8、删除文件目录 rm a目录 -r (删除a目录,包括里面的文件)
服务器CPU和内存占用高
一、在排查问题的过程中针对CPU的问题,使用以下命令组合来排查问题
1、查看问题进程,得到进程PID:
top -c
2、查看进程里的线程明细,并手动记下CPU异常的线程PID:
top -p PID -H
3、使用jdk提供jstack命令打印出项目堆栈:
jstack pid > xxx.log
线程PID转成16进制,与堆栈中的nid对应,定位问题代码位置。
二、针对内存问题,使用以下命令组合来排查问题:
1、查看内存中的存活对象统计,找出业务相关的类名:
jmap -histo:live PID > xxx.log
2、通过简单的统计还是没法定位问题的话,就输出内存明细来分析。这个命令会将内存里的所有信息都输出,输出的文件大小和内存大小基本一致。而且会导致应用暂时挂起,所以谨慎使用。
jmap -dump:live,format=b,file=xxx.hprof PID
3、 最后对dump出来的文件进行分析。文件大小不是很大的话,使用jdk自带的jhat命令即可:
jhat -J-mx2G -port 7170
4、dump文件太大的话,可以使用jprofiler工具来分析。jprofiler工具的使用,这里不做详细介绍,有兴趣可以搜索一下。
三、需要分析GC情况,可以使用以下命令:
jstat -gc PID
这里简单介绍一下java8里面这个命令得出的列表各个列的含义:
S0C:第一个幸存区的大小 S1C:第二个幸存区的大小 S0U:第一个幸存区的使用大小 S1U:第二个幸存区的使用大小 EC:伊甸园区的大小
EU:伊甸园区的使用大小 OC:老年代大小 OU:老年代使用大小 MC:方法区大小 MU:方法区使用大小 CCSC:压缩类空间大小
CCSU:压缩类空间使用大小 YGC:年轻代垃圾回收次数 YGCT:年轻代垃圾回收消耗时间 FGC:老年代垃圾回收次数 FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
一般会比较关注YGC和FGC的次数。
内容补充
1、jstack输出的堆栈文件可以上传到下面这个网站,这个网站可以对堆栈内容进行统计汇总,方便我们做分析:
http://fastthread.io/index.jsp
2、排查过程小节中的第5步,jmap命令执行完后没有输出业务类,而第7步在却有。这个是因为第5步操作的时候只有1G多的内存,代码还没执行到业务对象的封装,内存就不够了,后续的代码无法被执行到。第7步操作的时候内存调整到2G,所以有部分业务对象已经被创建了。
$堆(heap)与栈(stack),静态区
堆:java对是运行是的数据区,类的对象从中分配空间,由GC负责
栈:数据项的插入和删除都只能在栈顶的一端完成,后进先出,存放一些基本的数据类型变量和对象句柄
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间;而通过
new关键字和构造器创建的对象放在堆空间;程序中的字面量(1iteral)如直接书写的100、"hello"和常量都是放在静态区中。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,理论上整个内存没有被其他进程使用的空间甚至硬盘上的虚拟内存者都可以被当成堆空间来使用。
String str = new String("hello");
上面的语句中变量 str放在栈上,用 new创建出来的字符串对象放在堆上,而"hello"这个字面
量放在静态区。
JVM
$java内存模型分为了几块区域?元空间里有些啥?
JVM
内存共分为虚拟机栈、堆、方法区、寄存器、本地方法栈五个部分,JDK1.8有一个元数据区替代方法区了,存储一些常量池,类信息,还有class的static变量。
方法区:常量池、变量等存储地方;(持久区)
堆:实例对象存储地方;GC重点关照位置;(新生代和老年代)
程序计数器:记录程序下一步指令;
Java方法栈:方法程序运行地方;Java栈总是与线程关联在一起的,每当创建一个线程,JVM就会为该线程创建对应的Java栈;
栈(stack)是存储任何基本数据值、对对象的引用和方法的位置
本地方法栈:java方法与本地相关联
$JVM的回收机制
-
那些内存需要回收?(对象是否可以被回收的两种经典算法: 引用计数法 和 可达性分析算法- 判断对象的引用链是否可达 )
-
- 引用计数法
给每个对象添加一个引用计数器,每当有地方引用它时,计数器 +1;引用失效时,计数器 -1。当计数器为0时对象就不再被引用。
但主流Java虚拟机没有采用这种算法,主要原因是:它难以解决对象之间循环引用的问题
- 可达性分析算法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连(即从 GC
Roots 到该对象不可达),则此对象是不可用的,会判断为可回收对象。
Java中,可作为 GC Roots 的对象包括:
1. 栈(栈帧中的本地变量表)中引用的对象
2. 方法区中类 static 静态属性引用的对象
3. 方法区中 final 常量引用的对象
4. 本地方法栈中 JNI 引用的对象
讲讲jvm的内存管理机制(对比着c和java讲了下,主要详细说了下对象的创建过程,说了下如何判别对象该被回收了(可达性分析算法)、垃圾回收算法、垃圾收集器)
在虚拟机遇到 new 指令时:
-
类加载:确保常量池中存放的是已解释的类,且对象所属类型已经初始化过,如果没有,则先执行类加载
-
为新生对象分配内存:对象所需内存大小在类加载时可以确定,将确定大小的内存从Java堆中划分出来
-
分配空闲内存方法:
-
指针碰撞:假如堆是规整的,用过的内存和空闲的内存各一边,中间使用指针作为分界点,分配内存时则将指针移动对象大小的距离
-
空闲列表:假如堆是不规整的,虚拟机需要维护哪些内存块是可用的列表,分配时候从列表中找出足够大的空闲内存划分,并更新列表记录
-
-
对象创建在并发情况下保证线程安全:例如,正在给对象A分配内存,指针还没修改,对象B同时使用了原来的指针来分配内存
-
CAS配上失败重试
-
本地线程分配缓冲TLAB(ThreadLocal Allocation Buffer):将内存分配动作按线程划分到不同空间中进行,即每个线程在Java堆中预先分配一小块内存
-
将分配的内存空间初始化为零值:保证对象的实例在Java代码中可以不赋值就可直接使用,能访问到这些字段的数据类型对应的零值(例如,int类型参数默认为0)
-
设置对象头:设置对象的类的元数据信息、哈希码、GC分代年龄等
-
执行
方法初始化:将对象按照程序员的意愿初始化
-
垃圾回收算法:
-
标记-清除算法、
-
效率问题;遍历了两次内存空间(第一次标记,第二次清除)。
-
空间问题:容易产生大量内存碎片,当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发GC。
-
-
复制算法(现在的商业虚拟机都采用复制算法来回收新生代)、
-
优点
-
相对于标记–清理算法解决了内存的碎片化问题。
-
效率更高(清理内存时,记住首尾地址,一次性抹掉)。
-
缺点
-
内存率不高,每次只能使用一半内存。
-
-
标记-整理算法、
- 因为前面的复制算法当对象的存活率比较高时,这样一直复制过来,复制过去,没啥意义,且浪费时间。所以针对老年代提出了“标记整理”算法。
执行步骤:
* 标记:对需要回收的进行标记
* 整理:让存活的对象,向内存的一端移动,然后直接清理掉没有用的内存。
-
分代收集算法
-
当前大多商用虚拟机都采用这种分代收集算法,这个算法并没有新的内容,只是根据对象的存活的时间的长短,将内存分为了新生代和老年代,这样就可以针对不同的区域,采取对应的算法。如
-
新生代,每次都有大量对象死亡,有老年代作为内存担保,采取复制算法。
-
老年代,对象存活时间长,采用标记整理,或者标记清理算法都可。
-
-
垃圾收集器:其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。
JMM模型(Java内存模型即Java Memory Model),内存可见性介绍下
内存泄漏是什么,怎么检测
1.什么是内存泄漏(Memory Leak)?
简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。
2、如何检测内存泄露
第一:良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉。
第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。
第三:Boost 中的smart pointer。
第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky等等。
介绍2个设计模式
面向对象原则
重载和重写。为什么要有重载,我随便命名一个别的函数名不行吗?谈谈你是怎么理解的。
1.重写(Override)
从字面上看,重写就是 重新写一遍的意思。其实就是 在子类中把父类本身有的方法
重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以
在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意
子类函数的访问修饰权限不能少于父类的。
重写 总结:
1.发生在父类与子类之间
2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
2.重载(Overload)
在一个类中,同名的方法如果有不同的参数列表( 参数类型不同、参数个数不同甚至是参数顺序不同
)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但 不能通过返回类型是否相同来判断重载 。
重载 总结:
1.重载Overload是一个类中多态性的一种表现
2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
抽象类和接口的区别,什么时候用抽象类什么时候用接口
-
抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:
-
抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
-
抽象类不能用来创建对象;
-
如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
-
在其他方面,抽象类和普通的类并没有区别。
-
接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
-
区别
-
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
-
抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象
-
设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
-
[详细讲一下JUC(java.util.concurrent
)包有哪些组件,用过哪些?](https://app.yinxiang.com/shard/s64/nl/13973867/30d36207-f6d3-455d-b47d-c81383c9d2e1)
- JUC 简介
- 在 Java 5.0 提供了
java.util.concurrent
(简称JUC)包,在此包中增加了在并发编程中很常用的工具类, 用于定义类似于线程的自定义子系统,包括线程池,异步 IO
和轻量级任务框架;还提供了设计用于多线程上下文中 的 Collection 实现等;
atom,retranLock,线程池,blokingqueue
三次握手
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack (number
)=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
四次握手
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
强引用,弱引用,软引用 ,虚引用
虚引用有哪些应用场景
虚引用
也称为幻影引用:一个对象是都有虚引用的存在都不会对生存时间都构成影响,也无法通过虚引用来获取对一个对象的真实引用。唯一的用处:能在对象被GC时收到系统通知,JAVA中用PhantomReference来实现虚引用。
适用场景
使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。这个虚引用对于对象而言完全是无感知的,有没有完全一样,但是对于虚引用的使用者而言,就像是待观察的对象的把脉线,可以通过它来观察对象是否已经被回收,从而进行相应的处理。
事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。
小结
-
虚引用是最弱的引用
-
虚引用对对象而言是无感知的,对象有虚引用跟没有是完全一样的
-
虚引用不会影响对象的生命周期
-
虚引用可以用来做为对象是否存活的监控
$static final修饰的一个int 进行修改后是否需要进行重新编译
需要。一句话总结,在静态类里定义的静态属性,坚决不用引用类型,而需要用对象类型。
结论:
解决这种问题总共有三种方法:
1、将全部的CLASS文件删除掉再编译
2、将其变成GET、SET方法
3、设置成对象引用的方式处理!
Spring
知道spring AOP是如何实现的么,动态代理和CGlib分别是如何实现的
aop的简介:AOP(Aspect-OrientedProgramming,面向方面编程)(“横切”),可以说是OOP(Object-Oriented
Programing,面向对象编程)的补充和完善(为分散的对象引入公共行为)。将多个类的公共行为封装到一个可重用模块(“Aspect”-方面)减少系统的重复代码,降低模块间的耦合度。
使用场景:权限、缓存、内容传递、错误处理、懒加载、调试
概念:方面(Aspect)、连接点(Joinpoint)、通知(Advice)、切入点(Pointcut)、引入(Introduction)、目标对象、AOP代理、织入(Weaving)
主流程可以简述为:获取可以应用到此方法上的通知链拦截器链(Interceptor Chain),如果有,则应用通知,并执行joinpoint;
如果没有,则直接反射执行joinpoint。
Spring AOP的底层实现有两种方式:一种是JDK动态代理,另一种是CGLib的方式。
JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑贬值在一起。
-
JDK动态代理的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例哪?答案就是CGLib。
-
CGLib采用底层的字节码技术,全称是:Code Generation Library,CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
三、JDK 和 CGLib动态代理区别
1、JDK动态代理具体实现原理:
1. 通过实现InvocationHandler接口创建自己的调用处理器;
2. 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
3. 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
5. JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
2、CGLib动态代理:
CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过
CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。
3、两者对比:
JDK动态代理是面向接口的。
CGLib动态代理是通过字节码底层继承要代理类来实现 (如果被代理类被final关键字所修饰,那么抱歉会失败)。
4、使用注意:
如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理 。
说一说Spring中Bean的加载过程,BeanFactory和FactoryBean有什么区别?
3、区别
BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
BeanFactory定义了IOC容器的最基本形式,并提供了IOC容器应遵守的的最基本的接口,也就是Spring
IOC所遵守的最底层和最基本的编程规范。在Spring代码中,BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如
DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,都是附加了某种功能的实现。
Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。
一个 Bean 加载会经历这么几个阶段(用绿色标记):
-
获取 BeanName ,对传入的 name 进行解析,转化为可以从 Map 中获取到 BeanDefinition 的 bean name。
-
合并 Bean 定义 ,对父类的定义进行合并和覆盖,如果父类还有父类,会进行递归合并,以获取完整的 Bean 定义信息。
-
实例化 ,使用构造或者工厂方法创建 Bean 实例。
-
属性填充 ,寻找并且注入依赖,依赖的 Bean 还会递归调用 getBean 方法获取。
-
初始化 ,调用自定义的初始化方法。
-
获取最终的 Bean ,如果是 FactoryBean 需要调用 getObject 方法,如果需要类型转换调用 TypeConverter 进行转化。
消息
$ActiveMQ(Message Queue, 消息队列)是由哪些东西组成的?
JMS(java message service)组成的四大元素
* JMS provider : 实现JMS接口和规范的消息中间件,也就是我们的MQ服务器
* JMS producer :消息生产者,创建和发送JMS消息的客户端应用
* JMS consumer :消息消费者,接收和处理JMS消息的客户端应用
* JMS message :传送的消息。
-
activeMQ 是什么? 是 Apache 公司旗下的一个消息总线 ActiveMQ 是一个开源兼容 Java Message Service (JMS) 1.1 面向消息的中件间. 来自 Apache Software Foundation. ActiveMQ 提供松耦合的应用程序架构.
-
activeMQ 能干什么? 用来在服务与服务之间进行异步通信的
-
activeMQ 优势 1. 流量削锋 2. 任务异步处理
-
特点:可以解耦合
-
通信模式:
- 点对点 (queue)
》一个消息只能被一个服务接收》消息一旦被消费,就会消失》如果没有被消费,就会一直等待,直到被消费》多个服务监听同一个消费空间,先到先得
- 发布 / 订阅模式 (topic)
》一个消息可以被多个服务接收 》订阅一个主题的消费者,只能消费自它订阅之后发布的消息。
》消费端如果在生产端发送消息之后启动,是接收不到消息的,除非生产端对消息进行了持久化 (例如广播,只有当时听到的人能听到信息)
生产者-
消费者问题,也称有界缓冲问题,属于操作系统进程同步的经典问题,在多线程并发编程中也是经典案例,值得去学习。有一个生产者线程和一个消费者线程,生产者生产资源到资源缓冲区,消费者从资源缓冲区消费资源。资源缓冲区有界,空则不能取,满则不能产。
实现方式:1 Object.wait() / notify()方法实现;2 Lock和Condition实现
JDK 1.5 以后新增的 java.util.concurrent包新增了 BlockingQueue 接口。并提供了如下几种阻塞队列实现:
/** * 生产者消费者模式:使用{@link java.util.concurrent.BlockingQueue}实现 */ public class
ProducerConsumerByBQ{ private static final int CAPACITY = 5; public static
void main(String args[]){ LinkedBlockingDeque
LinkedBlockingDeque
blockingQueue, CAPACITY); Thread producer2 = new Producer("P-2",
blockingQueue, CAPACITY); Thread consumer1 = new Consumer("C1", blockingQueue,
CAPACITY); Thread consumer2 = new Consumer("C2", blockingQueue, CAPACITY);
Thread consumer3 = new Consumer("C3", blockingQueue, CAPACITY);
producer1.start(); producer2.start(); consumer1.start(); consumer2.start();
consumer3.start(); }
IO和NIO
$NIO知道么? nio底层调用了啥?啥是非阻塞IO?
完整的IO读请求操作包括两个阶段:1)查看数据是否就绪;2)进行数据拷贝(内核将数据拷贝到用户线程)。
阻塞(blocking IO)和非阻塞(non-blocking
IO)的区别就在于第一个阶段,如果数据没有就绪,再查看数据是否就绪的过程中是一直等待,还是直接返回一个标志信息。Java中传统的IO都是阻塞IO,比如通过socket来读数据,调用read()方法之后,如果数据没有就绪,当前线程就会一直阻塞在read方法调用那里,直到有数据才返回;而如果是非阻塞IO的话,当数据没有就绪,read()方法应该返回一个标志信息,告知当前线程数据没有就绪,而不是一直在那里等待。
用户程序进行IO的读写,基本上会用到read&write两大系统调用。
read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。
- 四种主要的IO模型:同步阻塞IO(Blocking IO),同步非阻塞IO(Non-blocking IO),IO多路复用(IO Multiplexing),异步IO(Asynchronous IO)四种IO模型,理论上越往后,阻塞越少,效率也是最优。
发起一个non-blocking socket的read读操作系统调用,流程是这个样子:
(1)在内核数据没有准备好的阶段,用户线程发起IO请求时,立即返回。用户线程需要不断地发起IO系统调用。
(2)内核数据到达后,用户线程发起系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。
(3)用户线程才解除block的状态,重新运行起来。经过多次的尝试,用户线程终于真正读取到数据,继续执行。
-
NIO的特点:应用程序的线程需要不断的进行 I/O 系统调用,轮询数据是否已经准备好,如果没有准备好,继续轮询,直到完成系统调用为止。
-
NIO的优点:每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。
-
NIO的缺点:需要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。
总之, NIO模型在高并发场景下,也是不可用的 。一般 Web 服务器不使用这种 IO
模型。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。java的实际开发中,也不会涉及这种IO模型。
再次说明 ,Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一种模型,叫做IO多路复用模型( IO multiplexing
) 。
- IO多路复用模型 的基本原理就是 select/epoll 系统调用,单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket连接,当某个或者某些socket网络连接有数据到达了,就返回这些可以读写的连接。因此,好处也就显而易见了——通过一次select/epoll系统调用,就查询到到可以读写的一个甚至是成百上千的网络连接。
(1)进行select/epoll系统调用,查询可以读的连接。kernel会查询所有select的可查询socket列表,当任何一个socket中的数据准备好了,select就会返回。
当用户进程调用了select,那么整个线程会被block(阻塞掉)。
(2)用户线程获得了目标连接后,发起read系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。
(3)用户线程才解除block的状态,用户线程终于真正读取到数据,继续执行。
- 多路复用IO的特点:
IO多路复用模型,建立在操作系统kernel内核能够提供的多路分离系统调用select/epoll基础之上的。多路复用IO需要用到两个系统调用(system
call), 一个select/epoll查询调用,一个是IO的读取调用。
和NIO模型相似,多路复用IO需要轮询。负责select/epoll查询调用的线程,需要不断的进行select/epoll轮询,查找出可以进行IO操作的连接。
另外,多路复用IO模型与前面的NIO模型,是有关系的。对于每一个可以查询的socket,一般都设置成为non-
blocking模型。只是这一点,对于用户程序是透明的(不感知)。
- 多路复用IO的优点:用select/epoll的优势在于,它可以同时处理成千上万个连接(connection)。与一条线程维护一个连接相比,I/O多路复用技术的最大优势是:系统不必创建线程,也不必维护这些线程,从而大大减小了系统的开销。
Java的NIO(new IO)技术,使用的就是IO多路复用模型。在linux系统上,使用的是epoll系统调用。
- 多路复用IO的缺点:本质上,select/epoll系统调用,属于同步IO,也是阻塞IO。都需要在读写事件就绪后,自己负责进行读写,也就是说这个读写过程是阻塞的。
如何充分的解除线程的阻塞呢?那就是异步IO模型。
- AIO的基本流程是:用户线程通过系统调用,告知kernel内核启动某个IO操作,用户线程返回。kernel内核在整个IO操作(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操作。
(1)当用户线程调用了read系统调用,立刻就可以开始去做其它的事,用户线程不阻塞。
(2)内核(kernel)就开始了IO的第一个阶段:准备数据。当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存)。
(3)kernel会给用户线程发送一个信号(signal),或者回调用户线程注册的回调接口,告诉用户线程read操作完成了。
(4)用户线程读取用户缓冲区的数据,完成后续的业务操作。
- 异步IO模型的特点:
在内核kernel的等待数据和复制数据的两个阶段,用户线程都不是block(阻塞)的。用户线程需要接受kernel的IO操作完成的事件,或者说注册IO操作完成的回调函数,到操作系统的内核。所以说,异步IO有的时候,也叫做信号驱动
IO 。
- 异步IO模型缺点:
需要完成事件的注册与传递,这里边需要底层操作系统提供大量的支持,去做大量的工作。
目前来说, Windows 系统下通过 IOCP 实现了真正的异步 I/O。但是,就目前的业界形式来说,Windows
系统,很少作为百万级以上或者说高并发应用的服务器操作系统来使用。
而在 Linux 系统下,异步IO模型在2.6版本才引入,目前并不完善。 所以,这也是在 Linux 下,实现高并发网络编程时都是以 IO
复用模型模式为主。
关于 epoll 和 select 的区别,哪些说法是正确的?(多选)
问题:关于 epoll 和 select 的区别,哪些说法是正确的?(多选)
A. epoll 和 select 都是 I/O 多路复用的技术,都可以实现同时监听多个 I/O 事件的状态。
B. epoll 相比 select 效率更高,主要是基于其操作系统支持的I/O事件通知机制,而 select 是基于轮询机制。
C. epoll 支持水平触发和边沿触发两种模式。
D. select 能并行支持 I/O 比较小,且无法修改。 参考答案:A,B,C
【延伸】那在高并发的访问下,epoll使用那一种触发方式要高效些?当使用边缘触发的时候要注意些什么东西?
- 消息传递方式:
select:内核需要将消息传递到用户空间,需要内核的拷贝动作;
poll:同上;
epoll:通过内核和用户空间共享一块内存来实现,性能较高;
- 文件句柄剧增后带来的IO效率问题:
select:因为每次调用都会对连接进行线性遍历,所以随着FD剧增后会造成遍历速度的“线性下降”的性能问题;
poll:同上;
epoll:由于epoll是根据每个FD上的callable函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll不会对性能产生线性下降的问题,如果所有socket都很活跃的情况下,可能会有性能问题;
支持一个进程所能打开的最大连接数:
select:单个进程所能打开的最大连接数,是由FD_SETSIZE宏定义的,其大小是32个整数大小(在32位的机器上,大小是32
32,64位机器上FD_SETSIZE=32 64),我们可以对其进行修改,然后重新编译内核,但是性能无法保证,需要做进一步测试;
poll:本质上与select没什么区别,但是他没有最大连接数限制,他是基于链表来存储的;
epoll:虽然连接数有上线,但是很大,1G内存的机器上可以打开10W左右的连接;
epoll是一种I/O事件通知机制,是linux
内核实现IO多路复用的一个实现。IO多路复用是指,在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。
epoll监控多个文件描述符的I/O事件。epoll支持边缘触发(edge trigger,ET)或水平触发(level
trigger,LT),通过epoll_wait等待I/O事件,如果当前没有可用的事件则阻塞调用线程。
select和poll只支持LT工作模式,epoll的默认的工作模式是LT模式。
-
水平触发:0为无数据,1为有数据。缓冲区有数据则一直为1,则一直触发。
-
边缘触发发:0为无数据,1为有数据,只要在0变到1的上升沿才触发。 所以效率更高
JDK并没有实现边缘触发, Netty重新实现了epoll机制 ,采用边缘触发方式;另外像Nginx也采用边缘触发。
虽然epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
- epoll更高效的原因
-
select和poll的动作基本一致,只是poll采用链表来进行文件描述符的存储,而select采用fd标注位来存放,所以select会受到最大连接数的限制,而poll不会。
-
select、poll、epoll虽然都会返回就绪的文件描述符数量。但是select和poll并不会明确指出是哪些文件描述符就绪,而epoll会。造成的区别就是,系统调用返回后,调用select和poll的程序需要遍历监听的整个文件描述符找到是谁处于就绪,而epoll则直接处理即可。
-
select、poll都需要将有关文件描述符的数据结构拷贝进内核,最后再拷贝出来。而epoll创建的有关文件描述符的数据结构本身就存于内核态中。
-
select、poll采用轮询的方式来检查文件描述符是否处于就绪态,而epoll采用回调机制。造成的结果就是,随着fd的增加,select和poll的效率会线性降低,而epoll不会受到太大影响,除非活跃的socket很多。
-
epoll的边缘触发模式效率高,系统不会充斥大量不关心的就绪文件描述符
多线程
为什么要用线程池
-
降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。
-
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
-
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控
$$多线程,AtomicInteger底层用的啥?cas的原理,AtomicInteger用了Voliate么(是)?voliate的原理,变量加Voliate被修改了其他线程能立刻知道么(是
可见性)?
AtomicIntger 是对 int 类型的一个封装,提供原子性的访问和更新操作,其原子性操作的实现是基于 CAS (compare-and-
swap) ( 比较并替换 ) 技术。它依赖于 Unsafe 提供的一些底层能力,进行底层操作;以 volatile 的 value
字段,Unsafe 会利用 value 字段的内存地址偏移,直接完成操作。CAS 是 Java 并发中所谓 lock-free 机制的基础。
voliate是java虚拟机提供的轻量级的同步机制.他保证了可见性,不保证原子性,禁止指令重排
Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
volatile三个特点
-
如果一个字段被申明为volatile,那么Java内存模型则 可以保证多个线程所看到的值是一致的 。
-
禁止指定重排。
-
volatile只能保证可见性, 不能保证原子性。
可见性的特点是: 获取主内存的那一瞬间,主内存的值新的
注意:voliate 只保证可见性,重点侧重于: 每个可以拿到其他线程修改之后的最新值(但并不是意味,只要有新值就可以 实时 获取最新值)
- 总结
-
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
-
Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。
-
CAS只能保证一个共享变量的原子操作
$sleep()和wait()的区别,调用这两个函数后,线程状态分别作何改变
sleep和wait的区别: 1、sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。
2、sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。
3、它们都可以被interrupted方法中断。
具体来说:
Thread.Sleep(1000)
意思是在未来的1000毫秒内本线程不参与CPU竞争,1000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束,即使这个时候恰巧轮到操作系统进行CPU
分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。另外值得一提的是Thread.Sleep(0)的作用,就是触发操作系统立刻重新进行一次CPU竞争,竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。
wait(1000)表示将锁释放1000毫秒,到时间后如果锁没有被其他线程占用,则再次得到锁,然后wait方法结束,执行后面的代码,如果锁被其他线程占用,则等待其他线程释放锁。注意,设置了超时时间的wait方法一旦过了超时时间,并不需要其他线程执行notify也能自动解除阻塞,但是如果没设置超时时间的wait方法必须等待其他线程执行notify。
![](【精】各大厂问题汇总_files/Image [6].png)
讲一下Java中线程的各种状态
java线程状态在Thread中定义,源码中能看到有个枚举State,总共定义了六种状态:
NEW: 新建状态,线程对象已经创建,但尚未启动
RUNNABLE:就绪状态,可运行状态,调用了线程的start方法,已经在java虚拟机中执行,等待获取操作系统资源如CPU,操作系统调度运行。
BLOCKED:堵塞状态。线程等待锁的状态,等待获取锁进入同步块/方法或调用wait后重新进入需要竞争锁
WAITING:等待状态。等待另一个线程以执行特定的操作。调用以下方法进入等待状态。 Object.wait(),
Thread.join(),LockSupport.park
TIMED_WAITING: 线程等待一段时间。调用带参数的Thread.sleep,
objct.wait,Thread.join,LockSupport.parkNanos,LockSupport.parkUntil
TERMINATED:进程结束状态。
状态之间的转换状态图,总结了下,如下:
![](【精】各大厂问题汇总_files/Image [6].jpg)
其中,Thread.sleep(long)使线程暂停一段时间,进入TIMED_WAITING时间,并不会释放锁,在设定时间到或被interrupt后抛出InterruptedException后进入RUNNABLE状态;
Thread.join是等待调用join方法的线程执行一段时间(join(long))或结束后再往后执行,被interrupt后也会抛出异常,join内部也是wait方式实现的。
wait方法是object的方法,线程释放锁,进入WAITING或TIMED_WAITING状态。等待时间到了或被notify/notifyall唤醒后,回去竞争锁,如果获得锁,进入RUNNABLE,否则进步BLOCKED状态等待获取锁。
下面是一个小例子,主线程中调用多线程,等待超时后如果子线程还未结束,则中断子线程(interrupt)。
$用过线程池吗?讲一下为什么用线程池,怎么用线程池
线程池做的工作主要是控制运行的线程的数量,处理过程中,将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出的数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
他的主要特点:线程复用、控制最大并发数、管理线程。
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要的等待线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
1、什么是线程池: java.util.concurrent.Executors提供了一个
java.util.concurrent.Executor接口的实现用于创建线程池
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。假设一个服务器完成一项任务所需时间为:T1
创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。 如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。
java提供的线程池更加强大,相信理解线程池的工作原理,看类库中的线程池就不会感到陌生了。
![](【精】各大厂问题汇总_files/Image [7].png)![](【精】各大厂问题汇总_files/Image [8].png)
知道多线程和多进程的区别吗?有什么优点呢
进程优点:每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
缺点:需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大。
线程优点:无需跨进程边界;
缺点:每个线程与主程序共用地址空间,受限于2GB地址空间;
区别:
1、操作系统资源管理方式区别
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
2、所处环境区别
在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
3、内存分配方面区别
系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
区别
多进程是立体交通系统,虽然造价高,上坡下坡多耗点油,但是不堵车。
多线程是平面交通系统,造价低,但红绿灯太多,老堵车。
我们现在都开跑车,油(主频)有的是,不怕上坡下坡,就怕堵车。
多进程优点:
1、每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
2、通过增加CPU,就可以容易扩充性能;
3、可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;
4、每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大。
多进程缺点:
1、逻辑控制复杂,需要和主程序交互;
2、需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大;
3、最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。当然你也可以利用多线程+多CPU+轮询方式来解决问题……
4、方法和手段是多样的,关键是自己看起来实现方便有能够满足要求,代价也合适。
多线程的优点:
1、无需跨进程边界;
2、程序逻辑和控制方式简单;
3、所有线程可以直接共享内存和变量等;
4、线程方式消耗的总资源比进程方式好。
多线程缺点:
1、每个线程与主程序共用地址空间,受限于2GB地址空间;
2、线程之间的同步和加锁控制比较麻烦;
3、一个线程的崩溃可能影响到整个程序的稳定性;
4、到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server
2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;
5、线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU。
悲观锁和乐观锁
-
悲观锁、乐观锁使用场景是针对数据库操作来说的,是一种锁机制。
-
悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
-
乐观锁(Optimistic Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制,即对数据做版本控制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
$Synchronized的底层原理,字节码层面如何实现加锁的?
synchronized 是一个Java的关键字,很多的书中都将它称为内置锁,也叫做监视器锁。它的实现完全是有JVM来实现的。
三种方式
- synchronized代码块。
当线程请求一个未被持有的锁(执行monitorenter),JVM将记下锁的持有者(获得Monitor对象),并且将获取锁的计数器置为1.如果同一个线程再次获取锁,计数器的值将加1(重入的实现);当执行monitorexit
(释放)释放锁计数器减1,当计数器为零,退出代码块
- 在实例方法上使用表示是对当前实例的加锁:
出现了一个ACC _SYNCHRONIZED JVM使用它来区分方法是否是同步方法, JVM在执行方法时会检查方法是否有ACC_SYNCHRONIZED
,如果有执行线程将会持有方法所在的对象的Monitor对象,在方法执行期间任何线程无法再获取这个Monitor对象,当线程执行完该方法无论是否有异常,都会在方法结束时候释放掉这个Monitor对象。
- 在类方法上表示对当前类的Class对象加锁:
跟非静态的区别是标示多了一个ACC_STATIC 还有就是args_size = 0 上面的args_size = 1
那么这是为什么呢。因为无论修饰代码块还是修饰方法,方法都是属于该实例的,JVM会在参数中隐式的传入this关键字。当有ACC_STATIC
的时候方法是归Class对象所有,所以参数是零。
synchronized和volatile区别
- 首先需要理解线程安全的两个方面: 执行控制 和 内存可见 。
执行控制 的目的是控制代码执行(顺序)及是否可以并发执行。
内存可见 控制的是线程执行结果在内存中对其它线程的可见性。根据
Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。
-
synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个 内存屏障 ,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都 happens-before 于随后获得这个锁的线程的操作。
-
volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。
使用volatile关键字仅能实现对原始变量(如boolen、 short 、int 、long等)操作的原子性,但需要特别注意,
volatile不能保证复合操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write
i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。
- 在Java 5提供了原子数据类型atomic wrapper classes,对它们的increase之类的操作都是原子操作,不需要使用sychronized关键字。
对于volatile关键字,当且仅当满足以下所有条件时可使用:
-
对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。2. 该变量没有包含在具有其他变量的不变式中。
-
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
-
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
-
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
-
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
-
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
-
$ReentrantLock(可重入 )如何实现非公平锁,和“公平锁”有什么区别?
JUC包图
![](【精】各大厂问题汇总_files/Image [7].jpg)
如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,FIFO。对于非公平锁,只要 CAS ( 比较并替换 )
设置同步状态成功,则表示当前线程获取了锁,而公平锁还需要判断当前节点是否有前驱节点,如果有,则表示有线程比当前线程更早请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。
并发编程中,ReentrantLock的使用是比较多的,包括之前讲的LinkedBlockingQueue和ArrayBlockQueue的内部都是使用的ReentrantLock,谈到它又不能的不说AQS,
AQS的全称是AbstractQueuedSynchronizer
,这个类也是在java.util.concurrent.locks下面,提供了一个FIFO的队列,可以用于构建锁的基础框架,内部通过原子变量state来表示锁的状态,当state大于0的时候表示锁被占用,如果state等于0时表示没有占用锁,ReentrantLock是一个重入锁,表现在state上,如果持有锁的线程重复获取锁时,它会将state状态进行递增,也就是获得一个信号量,当释放锁时,同时也是释放了信号量,信号量跟随减少,如果上一个线程还没有完成任务,则会进行入队等待操作。
![](【精】各大厂问题汇总_files/Image [9].png)![](【精】各大厂问题汇总_files/Image [10].png)
通过构造函数与三个内置类,我们就能知道ReentrantLock是怎么实现“公平锁”与“非公平锁“的。
公平锁会获取锁时会判断阻塞队列里是否有线程再等待,若有获取锁就会失败,并且会加入阻塞队列。
非公平锁获取锁时不会判断阻塞队列是否有线程再等待,所以对于已经在等待的线程来说是不公平的,但如果是因为其它原因没有竞争到锁,它也会加入阻塞队列。
进入阻塞队列的线程,竞争锁时都是公平的,因为队列为先进先出(FIFO)。
并发(Thread)中的常用方法
常用方法
start()
用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
run()
该方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
isAlive()
该方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
Thread.sleep(long millis)
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。当前线程进入TIMED_WAITING状态
yield()
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
wait()
该方法为Object的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long
timeout)超时时间到后还需要返还对象锁);其他线程可以访问。即让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
notify()
唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
notifyAll()
唤醒所有正在等待这个对象的monitor(监视器)的线程;
join()
启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。join是基于wait实现的。先start(),再join()。
interrupt()
Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
具体来说,当对一个线程,调用 interrupt() 时,① 如果线程处于被阻塞状态(例如处于sleep, wait, join
等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。②
如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。
过时的几个方法
stop()
强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
因为它在终止一个线程时会强制中断线程的执行,不管run方法是否执行完了,并且还会释放这个线程所持有的所有的锁对象。这一现象会被其它因为请求锁而阻塞的线程看到,使他们继续向下执行。这就会造成数据的不一致,我们还是拿银行转账作为例子,我们还是从A账户向B账户转账500元,我们之前讨论过,这一过程分为三步,第一步是从A账户中减去500元,假如到这时线程就被stop了,那么这个线程就会释放它所取得锁,然后其他的线程继续执行,这样A账户就莫名其妙的少了500元而B账户也没有收到钱。这就是stop方法的不安全性。
suspend()
suspend被弃用的原因是因为它会造成死锁。suspend方法和stop方法不一样,它不会破换对象和强制释放锁,相反它会一直保持对锁的占有,一直到其他的线程调用resume方法,它才能继续向下执行。
假如有A,B两个线程,A线程在获得某个锁之后被suspend阻塞,这时A不能继续执行,线程B在或者相同的锁之后才能调用resume方法将A唤醒,但是此时的锁被A占有,B不能继续执行,也就不能及时的唤醒A,此时A,B两个线程都不能继续向下执行而形成了死锁。这就是suspend被弃用的原因。
resume()
Thread.suspend很容易死锁。如果目标线程挂起来,他将给监听器上锁用以保护重要的系统资源,其他线程将不能访问该资源直到目标线程恢复工作。如果线程在恢复一个企图给监听器加锁的线程前调用了resume方法,则导致死锁。这种死锁称之为冰冻过程。
Thread使用注意
-
线程执行的业务逻辑,放在run()方法中
-
使用 thread.start() 启动线程
-
wait方法需要和notify方法配套使用
-
守护线程必须在线程启动之前设置
-
如果需要等待线程执行完毕,可以调用 join()方法
$多线程的四种写法
1实现runable接口,实现重写run方法 implements Runable()
2实现callable接口,implements Callable
Callable