【精】各大厂问题汇总

创建时间: 2022/6/26 14:34
更新时间: 2023/3/21 19:27
作者: HelloXF
标签: 知识库, 重要文件

Java

基础

JAVA SE

$关键字

Java 语言目前定义了 51 个关键字,这些关键字不能作为变量名、类名和方法名来使用。以下对这些关键字进行了分类。

  1. 数据类型:boolean、int、long、short、byte、float、double、char、class、interface。

  2. 流程控制:if、else、do、while、for、switch、case、default、break、continue、return、try、catch、finally。

  3. 修饰符:public、protected、private、final、void、static、strict、abstract、transient、synchronized、volatile、native。

  4. 动作:package、import、throw、throws、extends、implements、this、supper、instanceof、new。

  5. 保留字: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+红黑树。

  1. 数据结构 :取消了 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 等等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpU1zUix-1681014581778)(【精】各大厂问题汇总_files/Image.png)]

来源于Java.util包,是非常实用常用的数据结构 字面意思就是容器。

在Java的util包中有两个所有集合的父接口Collection和Map,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vEpDdUdu-1681014581779)(【精】各大厂问题汇总_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框架。

$多线程

evernote:///view/13973867/s64/d000038d-c37c-4a2a-90d0-0bc09d80c590/d000038d-c37c-4a2a-90d0-0bc09d80c590/

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配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了

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

FilterTest

/*

元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet
名称和资源访问的请求路径

子元素用于设置filter的注册名称。该值必须是在元素中声明过的过滤器的名字

设置 filter 所拦截的请求路径(过滤器关联的URL样式)

指定过滤器所拦截的Servlet名称。

指定过滤器所拦截的资源被 Servlet
容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个
子元素用来指定 Filter 对资源的多种调用方式进行拦截。如下:

testFilter

/index.jsp

REQUEST

FORWARD

子元素可以设置的值及其意义:

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。

  1. 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()

  1. response对象。

response对象是javax.servlet.http.HttpServletResponse的一个实例。服务端
的相应信息封装在response中返回。

重定向客户端请求 response.sendRedirect(index.jsp)

  1. 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)

  1. application对象。

application对象是javax.servlet.ServletContext的一个实例。

实现了用户间数据的共享,可存放全局变量。它开始于服务器的启动,直到服务器的关闭,在此期间,此对象将一直存在。

添加obj到Application对象 application.setAttribute(String key,Object obj)

获取Application对象中的值 application.getAttribute(String key)

  1. out 对象。

out对象是javax.servlet.jsp.jspWriter的一个实例。用于浏览器输出数据。

输出各种类型数据 out.print()

输出一个换行符 out.newLine()

关闭流 out.close()

  1. pageContext 对象。

pageContext 对象是javax.servlet.jsp.PageContext的一个对象。作用是取得任何范围的参数,通过它可以获取
JSP页面的out、request、reponse、session、application 等对象。

  1. config 对象。

config 对象是javax.servlet.ServletConfig的一个对象。主要作用是取得服务器的配置信息。通过 pageConext对象的
getServletConfig() 方法可以获取一个config对象。

  1. cookie 对象。

cookie 对象是Web服务器保存在用户硬盘上的一段文本。唯一的记录了用户的访问信息。

将Cookie对象传送到客户端 Cookie c = new Cookie(username",john");

读取保存到客户端的Cookie response.addCookie©

  1. exception 对象。

exception 对象的作用是显示异常信息,只有在包含 isErrorPage=“true” 的页面中才可以被使用。

forword与redirect的区别

1.从[地址栏](https://www.baidu.com/s?wd=%E5%9C%B0%E5%9D%80%E6%A0%8F&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=%E6%B5%8F%E8%A7%88%E5%99%A8&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z).[浏览器](https://www.baidu.com/s?wd=%E6%B5%8F%E8%A7%88%E5%99%A8&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z)根本不知道服务器发送的内容从哪里来的,所以它的[地址栏](https://www.baidu.com/s?wd=%E5%9C%B0%E5%9D%80%E6%A0%8F&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-TLwGUv3En1R1nW6snj6z)还是原来的地址.
redirect是[服务端](https://www.baidu.com/s?wd=%E6%9C%8D%E5%8A%A1%E7%AB%AF&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z)根据逻辑,发送一个状态码,告诉[浏览器](https://www.baidu.com/s?wd=%E6%B5%8F%E8%A7%88%E5%99%A8&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9Ph7bPyDvuADzmHNBPv7-0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-
bIi4WUvYETgN-
TLwGUv3En1R1nW6snj6z)重新去请求那个地址.所以[地址栏](https://www.baidu.com/s?wd=%E5%9C%B0%E5%9D%80%E6%A0%8F&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=%E6%95%B0%E6%8D%AE%E5%85%B1%E4%BA%AB&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标签库进行解决这个问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvUaxwzc-1681014581780)(【精】各大厂问题汇总_files/Image [2].png)]![](【精】各大厂问题汇总_files/Image
[3].png)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mkFf8d7h-1681014581780)(【精】各大厂问题汇总_files/Image [4].png)]

EL

EL是JSP的表达式语言

,EL表达式使我们在访问JavaBean中的数据非常简单,EL
表达式语法为【${expr}】,在jsp页面中,常用于获取后台传递的数据。通常情况下,我们将JSTL标签库与EL表达式进行结合使用,能很方便的进行数据的展示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRHgVoiD-1681014581781)(【精】各大厂问题汇总_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加载数据就是一种异步技术),同时还可以起到削峰作用(应对瞬时高并发)。&quot;能

推迟处理的者都要推迟处理”是网站优化的第二定律,而异步是践行网站优化第二定律的重要手段。

一亢余 :各种服务器都要提供相应的亢余服务器以便在某台或某些服务器岩机时还能保证网站可

以正常工作,同时也提供了灾难恢复的可能性。亢余是网站高可用性的重要保证。

$J2EE中常用的名词解释

  1. web容器 :给处于其中的应用程序组件(JSP,SERVLET)提供一个环境,使JSP,SERVLET直接和容器中的环境变量接接口互,不必关注其它系统问题。主要有WEB服务器来实现。例如:TOMCAT,WEBLOGIC,WEBSPHERE等。该容器提供的接口严格遵守J2EE规范中的WEB APPLICATION 标准。我们把遵守以上标准的WEB服务器就叫做J2EE中的WEB容器。

  2. Web container :实现J2EE体系结构中Web组件协议的容器。这个协议规定了一个Web组件运行时的环境,包括安全,一致性,生命周期管理,事务,配置和其它的服务。一个提供和JSP和J2EE平台APIs界面相同服务的容器。一个Web container 由Web服务器或者J2EE服务器提供。

  3. EJB容器 :Enterprise java bean 容器。更具有行业领域特色。他提供给运行在其中的组件EJB各种管理功能。只要满足J2EE规范的EJB放入该容器,马上就会被容器进行高效率的管理。并且可以通过现成的接口来获得系统级别的服务。例如邮件服务、事务管理。一个实现了J2EE体系结构中EJB组件规范的容器。 这个规范指定了一个Enterprise bean的运行时环境,包括安全,一致性,生命周期,事务, 配置,和其他的服务。

  4. JNDI :(Java Naming & Directory Interface)JAVA命名目录服务。主要提供的功能是:提供一个目录系统,让其它各地的应用程序在其上面留下自己的索引,从而满足快速查找和定位分布式应用程序的功能。

  5. JMS :(Java Message Service)JAVA消息服务。主要实现各个应用程序之间的通讯。包括点对点和广播。

  6. JTA :(Java Transaction API)JAVA事务服务。提供各种分布式事务服务。应用程序只需调用其提供的接口即可。

JPA(java persistence API)

JPA 通过JDK5.0的注解或XML来描述 对象-关系表的映射关系,并 将运行期的实体对象持久化存储到数据库中

  1. JAF :(Java Action FrameWork)JAVA安全认证框架。提供一些安全控制方面的框架。让开发者通过各种部署和自定义实现自己的个性安全控制策略。

  2. 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两节点间时间需同步

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nba3OiNV-1681014581782)(【精】各大厂问题汇总_files/Image.jpg)]

具体步骤:

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主从形式

(一)一主一从

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VpLP5ZxP-1681014581783)(【精】各大厂问题汇总_files/Image [1].jpg)]

(二)主主复制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MZuNuczc-1681014581783)(【精】各大厂问题汇总_files/Image [2].jpg)]

(三)一主多从

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R2jbLive-1681014581783)(【精】各大厂问题汇总_files/Image [3].jpg)]

(四)多主一从

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Czg6hTAK-1681014581784)(【精】各大厂问题汇总_files/Image [4].jpg)]

(五)联级复制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZhbDF92U-1681014581784)(【精】各大厂问题汇总_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;引用失效时,计数器 -1。当计数器为0时对象就不再被引用。

但主流Java虚拟机没有采用这种算法,主要原因是:它难以解决对象之间循环引用的问题

  1. 可达性分析算法

通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连(即从 GC
Roots 到该对象不可达),则此对象是不可用的,会判断为可回收对象。

Java中,可作为 GC Roots 的对象包括:

  1. 栈(栈帧中的本地变量表)中引用的对象2. 方法区中类 static 静态属性引用的对象3. 方法区中 final 常量引用的对象4. 本地方法栈中 JNI 引用的对象

讲讲jvm的内存管理机制(对比着c和java讲了下,主要详细说了下对象的创建过程,说了下如何判别对象该被回收了(可达性分析算法)、垃圾回收算法、垃圾收集器)

在虚拟机遇到 new 指令时:

  1. 类加载:确保常量池中存放的是已解释的类,且对象所属类型已经初始化过,如果没有,则先执行类加载

  2. 为新生对象分配内存:对象所需内存大小在类加载时可以确定,将确定大小的内存从Java堆中划分出来

  • 分配空闲内存方法:

    • 指针碰撞:假如堆是规整的,用过的内存和空闲的内存各一边,中间使用指针作为分界点,分配内存时则将指针移动对象大小的距离

    • 空闲列表:假如堆是不规整的,虚拟机需要维护哪些内存块是可用的列表,分配时候从列表中找出足够大的空闲内存划分,并更新列表记录

  • 对象创建在并发情况下保证线程安全:例如,正在给对象A分配内存,指针还没修改,对象B同时使用了原来的指针来分配内存

    • CAS配上失败重试

    • 本地线程分配缓冲TLAB(ThreadLocal Allocation Buffer):将内存分配动作按线程划分到不同空间中进行,即每个线程在Java堆中预先分配一小块内存

将分配的内存空间初始化为零值:保证对象的实例在Java代码中可以不赋值就可直接使用,能访问到这些字段的数据类型对应的零值(例如,int类型参数默认为0)

  1. 设置对象头:设置对象的类的元数据信息、哈希码、GC分代年龄等

  2. 执行方法初始化:将对象按照程序员的意愿初始化

  • 垃圾回收算法:

  • 标记-清除算法、

    • 效率问题;遍历了两次内存空间(第一次标记,第二次清除)。

    • 空间问题:容易产生大量内存碎片,当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发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. 开闭原则——面向对象设计原则

  2. 里氏替换原则——面向对象设计原则

  3. 依赖倒置原则——面向对象设计原则

  4. 单一职责原则——面向对象设计原则

  5. 接口隔离原则——面向对象设计原则

  6. 迪米特法则——面向对象设计原则

  7. 合成复用原则——面向对象设计原则

重载和重写。为什么要有重载,我随便命名一个别的函数名不行吗?谈谈你是怎么理解的。

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)

  1. JUC 简介
  • 在 Java 5.0 提供了

java.util.concurrent

(简称JUC)包,在此包中增加了在并发编程中很常用的工具类, 用于定义类似于线程的自定义子系统,包括线程池,异步 IO
和轻量级任务框架;还提供了设计用于多线程上下文中 的 Collection 实现等;

atom,retranLock,线程池,blokingqueue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GNFsvsNs-1681014581785)(【精】各大厂问题汇总_files/Y3MZZf.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NnpwB6zg-1681014581785)(【精】各大厂问题汇总_files/RrUjee.png)]

TCP三次握手和四次挥手,挥手时各个时刻的状态是什么?

三次握手

(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状态,完成四次挥手。

Java有哪些引用类型,分别是什么特点

强引用,弱引用,软引用 ,虚引用

虚引用有哪些应用场景

虚引用

也称为幻影引用:一个对象是都有虚引用的存在都不会对生存时间都构成影响,也无法通过虚引用来获取对一个对象的真实引用。唯一的用处:能在对象被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是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑贬值在一起。

  1. JDK动态代理的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例哪?答案就是CGLib。

  2. 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. 任务异步处理

  • 特点:可以解耦合

  • 通信模式:

  1. 点对点 (queue)

》一个消息只能被一个服务接收》消息一旦被消费,就会消失》如果没有被消费,就会一直等待,直到被消费》多个服务监听同一个消费空间,先到先得

  1. 发布 / 订阅模式 (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 blockingQueue = new
LinkedBlockingDeque(CAPACITY); Thread producer1 = new Producer(“P-1”,
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更高效的原因
  1. select和poll的动作基本一致,只是poll采用链表来进行文件描述符的存储,而select采用fd标注位来存放,所以select会受到最大连接数的限制,而poll不会。

  2. select、poll、epoll虽然都会返回就绪的文件描述符数量。但是select和poll并不会明确指出是哪些文件描述符就绪,而epoll会。造成的区别就是,系统调用返回后,调用select和poll的程序需要遍历监听的整个文件描述符找到是谁处于就绪,而epoll则直接处理即可。

  3. select、poll都需要将有关文件描述符的数据结构拷贝进内核,最后再拷贝出来。而epoll创建的有关文件描述符的数据结构本身就存于内核态中。

  4. select、poll采用轮询的方式来检查文件描述符是否处于就绪态,而epoll采用回调机制。造成的结果就是,随着fd的增加,select和poll的效率会线性降低,而epoll不会受到太大影响,除非活跃的socket很多。

  5. epoll的边缘触发模式效率高,系统不会充斥大量不关心的就绪文件描述符

多线程

为什么要用线程池

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。

  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控

$$多线程,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三个特点

  1. 如果一个字段被申明为volatile,那么Java内存模型则 可以保证多个线程所看到的值是一致的

  2. 禁止指定重排。

  3. volatile只能保证可见性, 不能保证原子性。

可见性的特点是: 获取主内存的那一瞬间,主内存的值新的

注意:voliate 只保证可见性,重点侧重于: 每个可以拿到其他线程修改之后的最新值(但并不是意味,只要有新值就可以 实时 获取最新值)

  • 总结
  1. 更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

  2. Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。

  3. 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。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KxazDt2s-1681014581786)(【精】各大厂问题汇总_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:进程结束状态。

状态之间的转换状态图,总结了下,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TNRCxld3-1681014581786)(【精】各大厂问题汇总_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提供的线程池更加强大,相信理解线程池的工作原理,看类库中的线程池就不会感到陌生了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7LoqYDQM-1681014581787)(【精】各大厂问题汇总_files/Image [7].png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AVXlDxSF-1681014581787)(【精】各大厂问题汇总_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关键字,当且仅当满足以下所有条件时可使用:

  1. 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。2. 该变量没有包含在具有其他变量的不变式中。

    • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

    • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的

    • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性

    • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

    • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

$ReentrantLock(可重入 )如何实现非公平锁,和“公平锁”有什么区别?

JUC包图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDFlnkdG-1681014581788)(【精】各大厂问题汇总_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状态进行递增,也就是获得一个信号量,当释放锁时,同时也是释放了信号量,信号量跟随减少,如果上一个线程还没有完成任务,则会进行入队等待操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HV7qaasy-1681014581788)(【精】各大厂问题汇总_files/Image [9].png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JJC2ts1d-1681014581788)(【精】各大厂问题汇总_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,实现重写call()方法,多线程

Callable oneCallable =new Tickets();

FutureTask oneTask =newFutureTask(oneCallable);

Thread t =new Thread(oneTask);

System.out.println(Thread.currentThread().getName());

t.start();

3.多线程

ExecutorService service=Executors.newFixedThreadPool(5);

Future result1=service.submit(zhiBoJian1);

4继承thread类,重写run方法,run()

public class ttt extend Thread{@Override public void run(){}}

分布式

Nginx做了负载均衡,那你知道Spring

cloud(Dubbo)也有负载均衡吗,那你说一下这两个负载均衡有什么区别,为什么两个地方都有负载均衡。

1.服务器端负载均衡Nginx

nginx是客户端所有请求统一交给nginx,由nginx进行实现负载均衡请求转发,属于服务器端负载均衡。

既请求有nginx服务器端进行转发。

2.客户端负载均衡Ribbon

Ribbon是从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地,让后在本地实现轮训负载均衡策略。

既在客户端实现负载均衡。

应用场景的区别:

Nginx适合于服务器端实现负载均衡 比如Tomcat
,Ribbon适合与在微服务中RPC远程调用实现本地服务负载均衡,比如Dubbo、SpringCloud中都是采用本地负载均3.

衡。

Ribbon是Spring Cloud (本地)客户端负载均衡器

$$了解Spring cloud(Dubbo)框架不,看过源码没,了解实现原理不+1

Spring Cloud是一个全家桶式的技术栈,包含了很多组件:Eureka、Ribbon、Feign、Hystrix、Zuul

dubbo由于是二进制的传输,占用带宽会更少

springCloud是http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大

dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决

springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级

dubbo的注册中心可以选择zk,redis等多种,springcloud的注册中心只能用eureka或者自研

  • Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wurVSORm-1681014581789)(【精】各大厂问题汇总_files/Image [11].png)]

Feign的一个关键机制就是使用了动态代理

* 首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理* 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心* Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址* 最后针对这个地址,发起请求、解析响应

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3y5vIPVr-1681014581789)(【精】各大厂问题汇总_files/Image [12].png)]

Ribbon就是专门解决这个问题的。它的作用是 负载均衡,默认使用的最经典的Round
Robin轮询算法Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体如下:

*  **首先Ribbon会从 Eureka Client里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号。***  **然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器***  **Feign就会针对这台机器,构造并发起请求。**

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZJVUZ3eh-1681014581790)(【精】各大厂问题汇总_files/Image.webp)]

Hystrix微服务架构中恐怖的服务雪崩问题但是如果积分服务都挂了,每次调用都要去卡住几秒钟干啥呢?有意义吗?当然没有!所以我们直接对积分服务熔断不就得了,比如在5分钟内请求积分服务直接就返回了,不要去走网络请求卡住几秒钟,这个过程,就是所谓的熔断!那人家又说,兄弟,积分服务挂了你就熔断,好歹你干点儿什么啊!别啥都不干就直接返回啊?没问题,咱们就来个降级:每次调用积分服务,你就在数据库里记录一条消息,说给某某用户增加了多少积分,因为积分服务挂了,导致没增加成功!这样等积分服务恢复了,你可以根据这些记录手工加一下积分。这个过程,就是所谓的降级。

为帮助大家更直观的理解,接下来用一张图,梳理一下Hystrix隔离、熔断和降级的全流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kQJausiY-1681014581790)(【精】各大厂问题汇总_files/Image [1].webp)]

Zuul,也就是微服务网关。 这个组件是负责网络路由的。可以做统一的降级、限流、认证授权、安全

总结:

最后再来总结一下,上述几个Spring Cloud核心组件,在微服务架构中,分别扮演的角色:

  • Eureka :各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里

  • Ribbon :服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台

  • Feign :基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求

  • Hystrix :发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题

@FeignClient(name= “spring-cloud-producer”,fallback =
HelloRemoteHystrix.class)public interface HelloRemote { @RequestMapping(value
= “/hello”) public String hello(@RequestParam(value = “name”) String name); }

  • Zuul :如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务

  • zuul过滤器

以上就是我们通过一个电商业务场景,阐述了Spring Cloud微服务架构几个核心组件的底层原理。

文字总结还不够直观?没问题! 我们将Spring Cloud的5个核心组件通过一张图串联起来,再来直观的感受一下其底层的架构原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ss5TjMpx-1681014581791)(【精】各大厂问题汇总_files/Image [2].webp)]

ZooKeeper在这一套体系中起到什么作用?

简单的说,zookeeper= 文件系统+通知机制。

ZooKeeper 是一个开源的分布式协调服务,由雅虎创建,是 Google Chubby 的开源实现。 分布式应用程序可以基于 ZooKeeper
实现诸如数据发布/订阅、负载均衡、命名服务、分布式协 调/通知、集群管理、Master 选举、配置维护,名字服务、分布式同步、分布式锁和分布式队列 等功能。

  • Zookeeper对节点的watch:

  • Zookeeper的选举机制:

  • Client对于Zookeeper的ServerList的轮询机制:

zookeeper的实际运用场景:

场景一:有一组服务器向客户端提供某种服务(例如:我前面做的分布式网站的服务端,就是由四台服务器组成的集群,向前端集群提供服务),我们希望客户端每次请求服务端都可以找到服务端集群中某一台服务器,这样服务端就可以向客户端提供客户端所需的服务。

场景二:分布式锁服务。当分布式系统操作数据,例如:读取数据、分析数据、最后修改数据。

场景三:配置管理。在分布式系统里,我们会把一个服务应用分别部署到n台服务器上,这些服务器的配置文件是相同的。统一修改配置文件

场景四:为分布式系统提供故障修复的功能。集群管理是很困难的,在分布式系统里加入了zookeeper服务,能让我们很容易的对集群进行管理。

分布式锁有哪些实现方式?都有哪些优缺点?

简单的来说就是对资源操作时的一种控制策略

何为资源:能被程序访问的所有信息或是媒介;比如常见的文件,数据库,内存中的数据等

何为操作:像读,写,修改等

在单体应用或是单实例部署的情况下,所有的请求处理都是在同一个JVM中。可借助java
中的锁机制来控制对资源的访问比如:synchronized关键字,java.util.concurrent.*
中的api但是如果应用程序是分布式部署或是集群部署,请求会别分发到不同的实例上来处理。这样就会带来一个问题:如果资源是被一个JVM给锁定了(比如说通过synchronized),那么其它的JVM是如何知道资源的锁定状态呢?

实际上java在处理请求时没有办法直接跨越JVM的,这个时候只能是借用一个"第三方"来饰演资源的公共角色?这个“第三方”,就可以是我们常见的文件,数据库等外部的一切可访问资源

分布式锁的基本原则

1.互斥性:在同一时刻只能由一个客户端持久

2.独立性:在正常情况下,只有锁的拥有者才可以释放锁即可识别锁拥有者的身份

3.可用性:即在使用端出现异常的情况下,锁还是可以正常被使用的,即不会存在死锁或是长时间无资源可用的状态

常见实现有三种 1.基于数据库
2.基于Redis(一定要设置Key的超时时间,防止客户端在获取到了锁之后没有释放锁或是客户端在获致到锁之后宕机,锁不能被释放) 3.基于Zookeeper
4.其它第三实现(redisson / https://github.com/redisson/redisson)

推荐系统,考虑过分布式系统嘛,QPS怎么测试出来

jmeter吞吐量,压测工具 Throughput:吞吐量。默认表示每秒完成的请求数(Request per second); 若使用Transaction
Controller,则表示每秒处理的事务数(Transaction per second)

$数据结构

integer和int的自动装箱和拆箱以及为什么要用integer类

int是JAVA八大基本数据类型(baibyte,shor,int,long,char,boolean,float,double)之一。

  • Integer是int的包装类,int则是java的一种基本数据类型

  • Integer变量必须实例化后才能使用,而int变量不需要

  • Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 。

  • Integer的默认值是null,int的默认值是0

自动装箱Integer b = 88;拆箱int d = b;

Java中绝大部分方法或类都是用来处理类类型对象的, 如ArrayList集合类就只能以类作为他的存储对象
,而这时如果想把一个int型的数据存入list是不可能的,必须把它包装成类,也就是Integer才能被List所接受。

string类的用法

1、int length():获取长度

2、char charAt(int index);根据位置获取位置上某个字符。

3、int indexOf(int ch):返回的是ch在字符串中第一次出现的位置。

4、int indexOf(int ch,int fromIndex):从fromIndex指定位置开始,获取ch在字符串中出现的位置。

5、int indexOf(String str):返回的是str在字符串中第一次出现的位置。

6、int indexOf(String str,int fromIndex):从fromIndex指定位置开始,获取str在字符串中出现的位置。

7、int lastIndexOf(String str):反向索引。

8、boolean contains(str);字符串中是否包含某一个子串

9、boolean isEmpty():原理就是判断长度是否为0。

10、boolean startsWith(str);字符串是否以指定内容开头。

11、boolean endsWith(str);字符串是否以指定内容结尾。

12、boolean equals(str);判断字符内容是否相同

13、boolean.equalsIgnorecase();判断内容是否相同,并忽略大小写。

14、String trim();将字符串两端的多个空格去除

15、int compareTo(string);对两个字符串进行自然顺序的比较

16、String toUpperCsae() 大转小 String toLowerCsae() 小转大

17、 String subString(begin); String subString(begin,end);获取字符串中子串

18、String replace(oldchar,newchar);将字符串指定字符替换。

JDK1.8都有什么新特性

  • Lambda表达式,这意味着java也开始承认了函数式编程,并且尝试引入其中。代码更少

  • @FunctionalInterface注解来定义函数式接口

  • 接口中可以定义默认实现方法和静态方法,在接口中可以使用default和static关键字来修饰接口中定义的普通方法

  • default关键字

  • Optional容器,可以快速的定位NPE,并且在一定程度上可以减少对参数非空检验的代码量。最大化减少空指针异常

  • 新的日期API LocalDate | LocalTime | LocalDateTime

      • 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum
      • java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
      • java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍
      • 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。
  • Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。(关键字:递归分合、分而治之。)

    • Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。

    • Stream操作的三个步骤:创建stream,中间操作(过滤、map),终止操作

  • 方法与构造函数引用

  • 局部变量限制

$ConcurrentHashMap原理。concurrentHashmap是安全的吧,那你知道concurrentHashmap的size()怎么求吗,在并发场景下,需要怎么设定锁?

  • 在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成。

JDK1.8的实现已经摒弃了Segment的概念,而是直接用 Node数组+链表+红黑树的数据结构
来实现,并发控制使用Synchronized和CAS来操作, 整个看起来就像是优化过且线程安全的HashMap

* Node是ConcurrentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据,Node数据结构很简单,就是一个链表,但是只允许对数据进行查找,不允许进行修改* TreeNode继承与Node,但是数据结构换成了二叉树结构,它是红黑树的数据的存储结构,用于红黑树中存储数据,当链表的节点数大于8时会转换成红黑树的结构,他就是通过TreeNode作为存储结构代替Node来转换成黑红树 * TreeBin从字面含义中可以理解为存储树形结构的容器,而树形结构就是指TreeNode,所以TreeBin就是封装TreeNode的容器,它提供转换黑红树的一些条件和锁的控制
  • 在JDK1.8版本中,对于size的计算,在扩容和addCount()方法就已经有处理了,JDK1.7是在调用size()方法才去计算,其实在并发集合中去计算size是没有多大的意义的,因为size是实时在变的,只能计算某一刻的大小,但是某一刻太快了,人的感知是一个时间段,所以并不是很精确

JDK 1.8的ConcurrentHashMap又采用了哪些技术呢?

悲观锁策略(阻塞同步)互斥同步最主要的问题就是线程阻塞和唤醒带来的性能问题,因此这种同步也称阻塞同步。从处理方式来讲,互斥同步是一种悲观的并发策略,总是认为只要不去做正确的的同步措施(列入加锁),那肯定会出现问题,无论共享数据是否出现竞争,都需要加锁。(需要线程挂起)

乐观锁策略(非阻塞同步)基于冲突检测的乐观并发策略就是先进行操作,如果没有其他线程争用共享资源,那么操作就成功了。如果共享数据有争用,出现冲突,那就再采取其他的不就措施。(不需要线程挂起)

$jdk hashmap 底层存储?mysql用的啥引擎?为啥数据库底层用B+树不用红黑树?

  • 在Jdk1.8中HashMap的实现方式做了一些改变,但是基本思想还是没有变得,只是在一些地方做了优化,下面来看一下这些改变的地方,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。

  • MyISAM存储引擎 InnoDB存储引擎(MySQL5.5后的默认) MEMORY存储引擎

1MyISAM 更适合读密集的表,不支持事务,用的是表锁 而 InnoDB 更适合写密集的的表,支持事务,行级锁(并发效率高)
在数据库做主从分离的情况下,经常选择MyISAM 作为主库的存储引擎。

2可使用 MEMORY 来存储非永久需要的数据,或能够从基于磁盘的表中重新生成的数据。 MEMORY(记忆)

1.红黑树必须存在内存里的,数据库表太大了,存不进去。

2.即使你找到了把红黑树存进硬盘的方法,红黑树查找一个节点最多要查logN层,每一层都是一个内存页(虽然你只是想找一个节点,但硬盘必须一次读一个页。。),那么一共logN次IO,伤不起阿!

讲讲java容器(吹了半天HashMap和ConcurrentHashMap)

hashmap扩容是需要重新哈希吗?如果二次哈希后还是哈希冲突呢?

需要

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YcvYs5d0-1681014581792)(【精】各大厂问题汇总_files/1036837-20200510205147888-1261461043.gif)]

Java在处理hash冲突的时候使用了链表

图中的0到10号
的方块就是entry(键值对),如果发生hashcode的冲突,就会像4号方块那样,开始向后追加,注意看4号方块的next的属性,那个属性不是null,而是指向了一个方块

什么是哈希冲突?

哈希冲突就是指当插入一个Entry<k,v>时,将key经过hash计算出的下标中已经存在另一个Entry<k,v>,这时就会产生哈希冲突。

2、如何解决?

解决哈希冲突的方式有四种: 1、开放地址法(包括线性探测、二次探测、伪随机探测等) 2、链地址法 3、再哈希发 4、建立一个公共的溢出区

众所周知,HasMap的底层是通过 数组 + 链表 实现的。此处的“ 链表
”就是HashMap为我们提供的解决哈希冲突的方法。也就是 连地址法

HashMap扩容时限插入还是先扩容?

当然是先扩容!!!

①HashMap的初始容量默认为16,负载因子默认为0.75。当插入的数据数量大于二者之积时就要考虑进行扩容。

②HashMap的扩容是需要将其扩至2倍,再哈希计算将所有数据移至新的HashMap中,如果先插入,那我岂不是要多计算一次哈希值,多一次插入吗?

hashmap和hashtable的区别?

我们都知道HashMap在多线程情况下,在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发 扩容操作,就是rehash
,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。

我们来了解另一个键值存储集合HashTable,它是线程安全的,它在所有涉及到多线程操作的都加上了synchronized关键字来锁住整个table,这就意味着所有的线程都在竞争一把锁,在多线程的环境下,它是安全的,但是无疑是效率低下的。

其实HashTable有很多的优化空间,锁住整个table这么粗暴的方法可以变相的柔和点,比如在多线程的环境下,对不同的数据集进行操作时其实根本就不需要去竞争一个锁,因为他们不同hash值,不会因为rehash造成线程不安全,所以互不影响,
这就是锁分离技术,将锁的粒度降低,利用多个锁来控制多个小的table

链表和数组有什么区别?

1)数组在内存中是逐个存放的,也就是说倘若数组的第一个元素在地址A,则数组第二个元素就在地址A+1。

而链表则不是,链表每个节点没有相对固定的位置关系。某个节点在地址A其后的节点不一定是A+1,而在内存的其他空闲区域,呈现一种随机的状态。

2)数组一旦显式的被申明后,其 大小就固定 了,不能 动态进行扩充 。而链表则可以,可以动态生成节点并且添加到已有的链表后面。

3)链表灵活,但是空间和时间额外耗费较大;数组大小固定,元素位置固定,但是操作不灵活,且容易浪费空间,但是时间耗费较小,尤其是元素变化不大的时候效率很高。双向链表比单向的更灵活,但是空间耗费也更大

链表的特性是在中间任意位置添加删除元素的都非常的快,不需要移动其它的元素

链表顾名思义,要把各个元素链接起来才算撒。

通常链表每一个元素都要保存一个指向下一个元素的指针(单链表)。

双链表的化每个元素即要保存到下一个元素的指针,还要保存一个上一个元素的指针。

循环链表则把最后一个元素中保存下一个元素指针指向第一个元素。

数组是一组具有相同类型和名称的变量的集合。这些变量称为数组的元素,每个数组元素都有一个编号,这个编号叫做下标,我们可以通过下标来区别这些元素。数组元素的个数有时也称之为数组的长度。

数组查改方便,链表增删效率高。

写一个list删除目标元素的函数,然后写个测试用例测试一下能不能通,为什么不能正向遍历

声明一个新的list存放符合要求的数据,迭代器删除,使用JDK1.8的List过滤功能,代码最简洁

list = list.stream().filter(a -> a%2==0).collect(Collectors.toList());

//查找身高在1.8米及以上的男生 List boys = studentList .stream()
.filter(s->s.getGender() && s.getHeight() >=
1.8).collect(Collectors.toList());

因为注释部分的代码将指针指向了最后,所以可以反向遍历

三,lterator和Listlterator的区别

(1)ListIterator有add()方法,可以向List中添加对象,而Iterator不能

(2)ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。

(3)ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

(4)都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。

因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。其实,数组对象也可以用迭代器来实现。

二叉树镜像,时间复杂度,空间复杂度

遍历二叉树的无论按照哪一种次序进行遍历,对n个结点时间复杂度为O(n),所需要的辅助空间为树的深度O(d).

二叉树的四种遍历方式分别是:先序、中序、后序和层次。 它们的时间复杂度都是O(n),因为它们只访问每个节点一次,不存在多余的访问。
三种深度优先遍历方法(先序、中序和后序)的时间复杂度是O(h),其中h是二叉树的深度,额外空间是函数递归的调用栈产生的,而不是显示的额外变量。层次遍历的时间复杂度是O(w),其中w是二叉树的宽度(拥有最多节点的层的节点数),因为层次遍历通常是用一个queue来实现的。

请评估一下程序的执行结果?

blockngqueue
BlockingQueue

A. true true true 1 3 B. true true true (阻塞) C. false false false null 0 D.
false false false (阻塞) 参考答案:D

public class SynchronousQueueQuiz { public static void main(String[] args)
throws Exception { BlockingQueue queue = new SynchronousQueue<>();
System. out .print(queue.offer(1) + " "); System. out .print(queue.offer(2) +
" "); System. out .print(queue.offer(3) + " "); System. out
.print(queue.take() + " "); System. out .println(queue.size()); } }

算法

按数量级递增排列,常见的时间复杂度有:常数阶 O(1) ,对数阶 **O(log 2 n),**线性阶 O(n), 线性对数阶
**O(nlog 2 n),**平方阶 **O( n 2),**立方阶 O( n 3),…, k次方阶 **O( n
k),**指数阶 O( 2 n)

当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1);当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为0(10g2n);当一个算法的空I司复杂度与n成线性比例关系时,可表示为0(n).若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。

给定一个二叉搜索树(BST),找到树中第 K 小的节点

在二叉查找树种:

(1)若任意结点的左子树不空,则左子树上所有结点的值均小于它的根结点的值。

(2)任意结点的右子树不空,则右子树上所有结点的值均大于它的根结点的值。

(3)任意结点的左、右子树也分别为二叉查找树。

(4)没有键值相等的结点。

二叉树的遍历次序:

前序顺序是根节点排最先,然后同级先左后右;中序顺序是先左后根最后右;后序顺序是先左后右最后根。

树相关的题目,第一眼就想到递归求解,左右子树分别遍历。联想到二叉搜索树的性质,root 大于左子树,小于右子树,如果左子树的节点数目等于 K-1,那么
root 就是结果,否则如果左子树节点数目小于 K-1,那么结果必然在右子树,否则就在左子树。因此在搜索的时候同时返回节点数目,跟 K
做对比,就能得出结果了。 public class TreeNode { int val; TreeNode left; TreeNode right;
TreeNode(int x) { val = x; }} class Solution { private class ResultType {
boolean found; // 是否找到 int val; // 节点数目 ResultType(boolean found, int val) {
this.found = found; this.val = val; } } public int kthSmallest(TreeNode root,
int k) { return kthSmallestHelper(root, k).val; } private ResultType
kthSmallestHelper(TreeNode root, int k) { if (root == null) { return new
ResultType(false, 0); } ResultType left = kthSmallestHelper(root.left, k); //
左子树找到,直接返回 if (left.found) { return new ResultType(true, left.val); } //
左子树的节点数目 = K-1,结果为 root 的值 if (k - left.val == 1) { return new
ResultType(true, root.val); } // 右子树寻找 ResultType right =
kthSmallestHelper(root.right, k - left.val - 1); if (right.found) { return new
ResultType(true, right.val); } // 没找到,返回节点总数 return new ResultType(false,
left.val + 1 + right.val); } }

给定一个链表,删除链表的倒数第 N 个节点,并且返回链表的头结点

public class Num19 { public static void main(String[] args) { int[] arr = {3,
2, 5, 8, 4, 7, 6, 9}; ListNode head = new ListNode(arr); head =
removeNthFromEnd(head, 8); System.out.println(head.toString()); } public
static ListNode removeNthFromEnd(ListNode head, int n) {
//当链表为空或者要删除的节点小于等于0的时候,直接返回head if (head == null || n <= 0) return head;
//建立一个虚拟的表头结点,因为需要删除的节点有可能是头结点, // 所以建立虚拟头结点可以不用分是否是头结点两种情况 ListNode tempHead
= new ListNode(0); tempHead.next = head; ListNode p = tempHead, q = tempHead;
//p指针比q指针先跑n次 for (int i = 0; i < n; i++) { //如果p为空的时候,说明这个节点的长度不足n,返回head if
(p == null ) return head; else { p = p.next; } } //p,q一起往前跑,直到p的next为空, //
q所指向的下一个结点就是要删除的元素的位置 while (p.next != null) { p = p.next; q = q.next; }
//删除q指向的节点的下一个元素 q.next = q.next.next; //删除虚拟头结点 return tempHead.next; }
static class ListNode { int val; ListNode next; ListNode(int x) { val = x; }
public ListNode(int[] arr) { if (arr == null || arr.length == 0) throw new
IllegalArgumentException(“arr can to be empty”); this.val = arr[0]; ListNode
cur = this; for (int i = 1; i < arr.length; i++) { cur.next = new
ListNode(arr[i]); cur = cur.next; } } @Override public String toString() {
StringBuilder res = new StringBuilder(); ListNode cur = this; while (cur !=
null) { res.append(cur.val + “->”); cur = cur.next; } res.append(“NULL”);
return res.toString(); } } }

反转链表按k,最长重复子串

给定一个整数数组和一个整数,返回两个数组的索引,这两个索引指向的数字

的加和等于指定的整数。需要最优的算法,分析算法的空间和时间复杂度

public int[] twoSum(int[] nums, int target) { if(nums==null || nums.length<2)
return new int[]{0,0}; HashMap<Integer, Integer> map = new HashMap<Integer,
Integer>(); for(int i=0; i<nums.length; i++){ if(map.containsKey(nums[i])){
return new int[]{map.get(nums[i]), i}; }else{ map.put(target-nums[i], i); } }
return new int[]{0,0};} 分析:空间复杂度和时间复杂度均为 O(n)

已知 sqrt (2)约等于 1.414,要求不用数学库,求 sqrt (2)精确到小数点后 10 位。

public class Main { public static void main(String[] args) { double l = 1.41;
double r = 1.42; double mid = (r+l)/2; double flag = 0.0000000001; while(r-l >
flag){ mid = (r+l)/2; if(mid*mid < 2) l = mid; else r = mid; }
System.out.println(mid); } }

如何实现一个高效的单向链表逆序输出?

直接递归(简单,但O(n)空间复杂度不支持大数据量)

// 直接递归实现核心代码片段 public void reverse(head){ // 递归终止条件 if(head.next == null){
print(head); return; } // 下一层需要做的事儿 reverse(head.next); // 本层需要做的事儿
print(head); }

JAVA算法:最长重复子串问题(JAVA解题)

package com.bean.algorithm.basic; public class LongestRepeatingSubstring { //
Returns the longest repeating non-overlapping // substring in str static
String longestRepeatedSubstring(String str) { int n = str.length(); int
LCSRe[][] = new int[n + 1][n + 1]; String res = “”; // To store result int
res_length = 0; // To store length of result // building table in bottom-up
manner int i, index = 0; for (i = 1; i <= n; i++) { for (int j = i + 1; j <=
n; j++) { // (j-i) > LCSRe[i-1][j-1] to remove // overlapping if (str.charAt(i

    1. == str.charAt(j - 1) && LCSRe[i - 1][j - 1] < (j - i)) { LCSRe[i][j] =
      LCSRe[i - 1][j - 1] + 1; // updating maximum length of the // substring and
      updating the finishing // index of the suffix if (LCSRe[i][j] > res_length) {
      res_length = LCSRe[i][j]; index = Math.max(i, index); } } else { LCSRe[i][j] =
      0; } } } // If we have non-empty result, then insert all // characters from
      first character to last // character of String if (res_length > 0) { for (i =
      index - res_length + 1; i <= index; i++) { res += str.charAt(i - 1); } }
      return res; } // Driver program to test the above function public static void
      main(String[] args) { String str = “aabaabaaba”;
      System.out.println(longestRepeatedSubstring(str)); } }

给一个数组,找出出现次数大于数组长度一半的那个数。(我直接无脑用哈希表,面试官皱着眉头说能优化不,我想了半天没想出来)。

package test; public class Search { public static void main(String[] args) {
//System.out.println(“Hello World!”); Integer []a={4,5,5,2,3,5,2,5,5,5};
Integer len= a.length; Integer result = search(a,len);
System.out.println(result); } public static Integer search(Integer A[],Integer
len){ if(Anull || len<=0) { return -1; } Integer k=null, j=0; for(Integer
i=0;i<len;++i) { if(j0) { k=A[i]; } if(k==A[i]) { ++j; }else { --j; } }
return k; } }

给一个链表1->2->3->4->5->6->7 和 一个数字n每n个反转一次链表。如 n = 2时,2->1->4->3->6->5->7;n =

3时,3->2->1>6->5->4->7

罗马数字转整数 leetcode13

class Solution { public int romanToInt(String s) { Map<Character,Integer> map
= new HashMap<>(); map.put(‘I’,1); map.put(‘V’,5); map.put(‘X’,10);
map.put(‘L’,50); map.put(‘C’,100); map.put(‘D’,500); map.put(‘M’,1000);
//用map结构来存储字母与带表的值 char[] temp = s.toCharArray();//将其转化为数组会索引时会更快 int length =
temp.length;//现记录长度,避免循环的时候每次都要去计算 int sum = 0;//原来保存最后的值 for(int
i=0;i<length-1;i++){//只循环到倒数第二个,因为最后一个肯定是被加上的数 int curTemp =
map.get(temp[i]);//获取当前字母的值 int nextTemp = map.get(temp[i+1]);//获取相邻字母的值 sum
+= curTemp < nextTemp ? -curTemp : curTemp;//比较,如果当前值小于右边,就应该减,否则加 } return
sum+ map.get(temp[length-1]);//之前循环的时候并没有循环最后一个,因为最后一个无论如何都是会被加上的,所以此时加上 } }

反转链表,删除排好序数组中重复元素

/** * 单链表相关操作 * * @author yinhr * / public class LinkedListUtil { /* *
反转链表(3指针pre head headNext) * * @param head * @return / ListNode
reverse(ListNode head) { ListNode pre = null; ListNode headNext; while (head
!= null) { headNext = head.next; head.next = pre; pre = head; head = headNext;
} return pre; } /
* * 删除有序链表中重复值,重复值不保留(头结点+三指针pre head end) * * @param head *
@return / ListNode deleteRepeat(ListNode head) { // 创建头节点用于返回链表 ListNode
headNode = new ListNode(0, head); ListNode pre = headNode; // 用于寻找第一个不同值节点
ListNode end; while (head != null && head.next != null) { // 当前值和它的下个值不等则移动两指针
if (head.value != head.next.value) { pre = head; head = head.next; } else {
end = head.next.next; while (end != null && end.value == head.value) { end =
end.next; } pre.next = end; head = end; } } return headNode.next; } /
* *
删除有序链表中重复值,重复值保留一个(1)两指针listHead head,相邻两个相等删除后一个(2)三指针listHead head end * *
@param head * @return */ ListNode deleteRepeatKeepOne(ListNode head) { //
用于返回链表 ListNode listHead = head; // (1)用于寻找第一个不同值节点 ListNode end; while (head
!= null && head.next != null) { // 当前值和它的下个值不等则移动指针 if (head.value !=
head.next.value) { head = head.next; } else { // (2)也可只用list.next =
list.next.next; end = head.next.next; while (end != null && end.value ==
head.value) { end = end.next; } head.next = end; head = end; } } return
listHead; } }

数据库

大数据

HBase底层数据存储的结构

HBase底层存储原理——我靠,和cassandra本质上没有区别啊!都是kv
列存储,只是一个是p2p另一个是集中式而已!

首先HBase不同于一般的关系 数据库,

  • 它是一个适合于 非结构化数据 存储的数据库.

  • 另一个不同的是HBase 基于列的 而不是基于行的模式.

什么是BigTable:

  • Bigtable是一个 疏松的分布式的持久的多维排序的map ,

  • 这个map被 行键,列键,和时间戳索引.

  • 每一个值都是连续的byte数组.

Hadoop wiki的 **
HBase架构** 页面提到:

  • HBase使用和Bigtable非常相同的数据模型.

  • 用户存储数据行在一个表里. 一个数据行拥有一个可选择的键和任意数量的列.

  • 表是疏松的 存储的,因此 用户可以 给行定义各种不同的列.

关系型数据库

sql执行顺序

(1)from

(2) on

(3) join

(4) where

(5)group by(开始使用select中的别名,后面的语句中都可以使用)

(6) avg,sum…

(7)having

(8) select

(9) distinct

(10) order by

$MySQL 的数据如何恢复到任意时间点?

恢复到任意时间点以定时的做全量备份,以及备份增量的 binlog 日志为前提。恢复到任意时间点首先将全量备份恢复之后,再此基础上回放增加的 binlog
直至指定的时间点。

从 innodb 的索引结构分析,为什么索引的 key 长度不能太长?

key 太长会导致一个页当中能够存放的 key 的数目变少,间接导致索引树的页数目变多,索引层次增加,从而影响整体查询变更的效率。

MySQL用的什么索引?

btree或哈希hash

在计算机数据结构(不懂数据结构的自行充电)体系中,为了加速查找的速度,常见的数据结构有两种:

  • Hash哈希结构,例如Java中的HashMap,这种数据组织结构可以让查询/插入/修改/删除的平均时间复杂度都为O(1);

  • Tree 树 结构 , 这种数据组织结构可以让查询/插入/修改/删除的平均时间复杂度都为O(log(n));

确实用HASH索引更快,因为每次都只查询一条信息(重名的雇员姓名也才几条而已),但实际上业务对于SQL的应用场景是:

  • orderby 需要排个序

  • groupby 还要分个组

  • 还要比较大小 大于或小于等等

这种情况下如果继续用HASH类型做索引结构,其时间复杂度会从O(1)直接退化为O(n),相当于全表扫描了,而Tree的特性保证了不管是哪种操作,依然能够保持O(log(n))的高效率,有种我自岿然不动的赶脚!所以抛开应用场景谈设计其实是耍流浪(比如很多java程序员被安利阿里的fastjson比jackson快,故而抛弃jackson一样),实际上MySQL中也不把HASH类型的索引作为主流索引。

为什么MySQL不能支撑高并发,你有做过测试吗?

由于 mysql是一个连接给一个线程
,当并发高的时候,每秒需要几百个甚至更多的线程,其中创建和销毁线程还好说,大不了多耗费点内存,线程缓存命中率下降还有创建销毁线程的性能增加问题—
这个问题不是特别大,
重点是mysql底层瞬间处理这几百个线程提交的sql(有时候一个页面会有10多条sql,cpu一次只能处理一条sql)会导致cpu的
上下文切换
,性能抖动,然后性能下降
,虽然mysql对多核cpu有做了优化,但是也并不完美,
也仅限于24核心上下 (也许是64以内,有做过测试,超过24再增加核心效果不明显)。 所以mysql企业版本推出了线程池技术
,另外percona的mysql有免费的线程池提供。可以极大提高高并发下mysql的性能(也就比没有大量并发的时候低20-30%左右这样子),如果要讲解线程池
我一句半句也说不清楚,你可以百度下mysql线程池去了解相关原理。

大量数据迁移

四个步骤:存量,增量,校验,切流

增量数据迁移

  • DTS: 阿里云的DTS算是一条龙服务了,在提供存量数据迁移的同时也提供了增量数据迁移,只不过需要按量收费。

  • 服务双写:比较适合于系统没有切换的迁移,也就是只换了存储但是系统还是同一个,比如说分库分表,redis数据同步等,这个的做法比较简单直接在代码里面同步的去写入需要迁移的数据,但是由于不是同一个数据库就不能保证事务,有可能导致迁移数据的时候会出现数据丢失,这个过程通过后续的数据校验会进行解决。

  • MQ异步写入:这个可以适用于所有的场景,当有数据修改的时候发送一个MQ消息,消费者收到这个消息之后再进行数据更新。这个和上面的双写有点类似,但是他把数据库的操作变成了MQ异步了出问题的概率就会小很多

  • 监听binlog: 我们可以使用之前说过的canal或者其他的一些开源的如databus去进行binlog监听,监听binlog的方式 就和上面的消息MQ方式一样,只是发送消息的这一步被我们省略了。这个方式的一个开发量来说基本是最小的。

这么多种方式我们应该使用哪种呢?我个人来说是比较推荐监听binlog的做法的,监听binlog减少开发成本,我们只需要实现consumer逻辑即可,数据能保证一致性,因为是监听的binlog这里不需要担心之前双写的时候不是一个事务的问题。

Nosql

$redis的使用场景

1、Redis底层ZSet跳表是如何设计与实现的

zset的两种实现方式

ziplist(压缩链表):满足以下两个条件的时候

元素数量少于128的时候

每个元素的长度小于64字节

skiplist(跳跃链表):不满足上述两个条件就会使用跳表,具体来说是组合了map和skiplist

map用来存储member到score的映射,这样就可以在O(1)时间内找到member对应的分数

skiplist按从小到大的顺序存储分数

skiplist每个元素的值都是[score,value]对

ziplist原理

每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。压缩列表中的元素按照分数从小到大依次紧挨着排列,有效减少了内存空间的使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cszcv5ZI-1681014581792)(【精】各大厂问题汇总_files/Image [13].png)]

skiplist原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXgTOM37-1681014581793)(【精】各大厂问题汇总_files/Image [14].png)]

上图用a,b,c,d,e五种有序链表说明了跳跃表的motivation.

[a]单链表:查询时间复杂度O(n)

[b]level-2单链表:每隔一个节点为一个level-2节点,每个level-2节点有2个后继指针,分别指向单链表中的下一个节点和下一个level-2节点。查询时间复杂度为O(n/2)

[c]level-3单链表:每隔一个节点为一个level-2节点,每隔4个节点为一个level-3节点,查询时间复杂度O(n/4)

[d]指数式单链表:每隔一个节点为一个level-2节点,每隔4个节点为一个level-3节点,每隔8个节点为一个level-4节点(每2^i个节点的level为i+1),查询时间复杂度为O(log2N)

[e]跳跃表:各个level的节点个数同指数式单链表,但出现的位置随机,查询复杂度是O(logN)

2、Redis底层ZSet实现压缩列表和跳表如何选择

ziplist(压缩链表):满足以下两个条件的时候

元素数量少于128的时候

每个元素的长度小于64字节

skiplist(跳跃链表):不满足上述两个条件就会使用跳表

3、Redis高并发场景热点缓存如何重建

1.解决Redis把内存爆满的三种方法

1.1 定期删除

让程序员均匀设置过期时间,Redis定时去扫描数据是否有过期的过期的删除掉,全盘扫描太浪费资源了采用的是随机选取一部分缓解内存压力,但是有一部分键值对每次都没有被随机选择算法选中。在定期删除的基础上添加了惰性删除

1.2 惰性删除

那些逃过随机选择算法的,一但被查询的时候先查看是否过期,过期就直接删除,,存在问题,还是有很多数据到了过期时间但是没有去查询删除而存在内存中,造成内存爆满。为解决内存不足时该如何解决最后出现了内存淘汰策略

1.3 内存淘汰策略

以下8种策略,给出了在内存不足时,该如果决策

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AKr7Nq4u-1681014581793)(【精】各大厂问题汇总_files/Image [15].png)]

  1. 缓存穿透——缓存击穿——缓存雪崩

缓存穿透是某个热点数据访问缓存和数据库中都不存在(访问数据库中不存在的数据)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bx9QzEaQ-1681014581794)(【精】各大厂问题汇总_files/Image [16].png)]

缓存击穿
是某个热点数据访问缓存中不存在 数据库中存在(访问缓存中不存在的数据)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WoxUzNJj-1681014581794)(【精】各大厂问题汇总_files/Image [17].png)]

缓存雪崩
是大量数据访问缓存都失效,大量请求去访问数据库把数据库给怼宕机了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HfNrNR7v-1681014581795)(【精】各大厂问题汇总_files/Image [18].png)]

3. 如何解决线上缓存穿透问题

过期时间均匀分布+热点数据永不过期

3.1 缓存击穿(缓存失效)

解决思路:

只需要加上过期时间随机值,能够保证上万条数据不同时过期,减少对数据库的压力,同一时刻缓存中还是有数据的

redisUtil.set(key, JSON.toJSONString(cookies), 24 * 60 * 60 ,
TimeUnit.SECONDS);

new Random().nextInt(30)60; 随机产生一个[0~3060)int类型的数值

redisUtil.set(key, JSON.toJSONString(cookies), 24 * 60 * 60 + new
Random().nextInt(30)*60, TimeUnit.SECONDS);

3 .2 缓存穿透(数据删除)

上万条数据同时请求数据,缓存中没有数据,去数据库中数据库中也没有数据,穿透所有的持久层

解决思路:

为了防止缓存穿透,在第一条数据进行请求的时候发现缓存和数据库中都没有要求请的数据,就在缓存中放一个空串{},下次再次请求的时,读取到缓存中的值,判断值如果是空串则返回null
,否则返回对应的值。

4. 基于DCL机制解决热点缓存并发重建问题实战

上万条请求同时请求某一条数据

方法一:

添加同步锁机制synchronized,,,,

(双重检测锁)解决突发热点并发重建导致DB压力暴增

让第一个请求先查Redis缓存,若不存在查DB,查到写入Redis缓存

后续的请求再访问Redis时,直接取值

synchronized(this){

//Redis缓存中取数据,

//缓存中不存在该条数据,请求DB,存到Redis中

}

方法二:

使用Redis分布式锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xr0rfIIH-1681014581795)(【精】各大厂问题汇总_files/Image [19].png)]

引入依赖

org.redisson

redissson

3.6.5

@Autowired

private Redisson redisson;

RLock hotCacheCreateLock = redisson.getLock(key);

hotCacheCreateLock.locak(); //相当于执行了setnx(k,v) // 加锁

try{

//Redis缓存中取数据,

//缓存中不存在该条数据,请求DB,存到Redis中

} finally {

hotCacheCreateLock.unlock(); //删除锁 del k 删除key

}

  1. Redis分布式锁解决缓存与数据库双写不一致问题实战

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaaNILAF-1681014581796)(【精】各大厂问题汇总_files/Image [20].png)]

解决办法:Redis 分布式锁

@Autowired

private Redisson redisson;

RLock hotCacheCreateLock = redisson.getLock(key);

hotCacheCreateLock.locak(); //相当于执行了setnx(k,v) // 加锁

try{

//Redis缓存中取数据,

RLock updateCreateLock = redisson.getLock(key2);

updateCreateLock.locak(); //相当于执行了setnx(k,v) // 加锁

try{

//缓存中不存在该条数据,读取DB中数据,并存到Redis中

} finally {

updateCreateLock.unlock(); //删除锁 del k 删除key2

}

} finally {

hotCacheCreateLock.unlock(); //删除锁 del k 删除key

}

优化:可以用读写锁

对读多写少进行优化——所有的读操作并行执行,读和写互斥串行执行

读数据库中的数据

@Autowired

private Redisson redisson;

RLock hotCacheCreateLock = redisson.getLock(key);

hotCacheCreateLock.locak(); //相当于执行了setnx(k,v) // 加锁

try{

//Redis缓存中取数据,

//RLock updateCreateLock = redisson.getLock(key2);

RReadWriteLock readWriteLock = redisson.getReadWriteLock(ke2)

RLock rLock = readWriteLock.readLock();

rLock.locak(); // 加读锁 setnx(k,v)

try{

//缓存中不存在该条数据,读取DB中数据,并存到Redis中

} finally {

rLock.unlock(); //删除锁

}

} finally {

hotCacheCreateLock.unlock(); //删除锁 del k 删除key

}

更新数据库中数据

RReadWriteLock readWriteLock = redisson.getReadWriteLock(ke2)

RLock wLock = readWriteLock.writeLock();

wLock.locak(); // 加写锁 setnx(k,v)

try{

//修改数据库中的数据操作

} finally {

wLock.unlock();

}

读数据缓存锁优化

@Autowired

private Redisson redisson;

RLock hotCacheCreateLock = redisson.getLock(key);

//hotCacheCreateLock.locak(); //相当于执行了setnx(k,v) // 加锁

hotCacheCreateLock.tryLock(2,TimeUnit.SECONDS); //等待2秒后锁失效(设置合适的时间)

try{

//Redis缓存中取数据,

//RLock updateCreateLock = redisson.getLock(key2);

RReadWriteLock readWriteLock = redisson.getReadWriteLock(ke2)

RLock rLock = readWriteLock.readLock();

rLock.locak(); // 加读锁 setnx(k,v)

try{

//缓存中不存在该条数据,读取DB中数据,并存到Redis中

} finally {

rLock.unlock(); //删除锁

}

} finally {

hotCacheCreateLock.unlock(); //删除锁 del k 删除key

}

  1. 利用多级缓存架构解决Redis线上集群缓存雪崩问题

Redis单节点高并发最高10W+,,,瞬间来了几十W上百W的请求来查Redis中数据,Redis直接宕机了,,,缓存雪崩问题

web应用分流,,,

多级缓存——JVM进程级别缓存

JVM进程级别的缓存一般来说都是有容量上的限制

原文链接:https://blog.csdn.net/qq_45896330/article/details/125975423

4、高并发场景缓存穿透 &失效&雪崩如何解决

5、Redis集群架构如何抗住双十一的洪峰流量

6、Redis缓存与数据库双写不一致如何解决

7、Redis分布式锁主从架构锁失效问题如何解决

8、从CAP角度解释下Redis &Zookeeper锁架构异同

9、超大并发的分布式锁架构该如何设计

10、双十一亿级用户日活统计如何用Redis快速计算

11、双十一电商推荐系统如何用Redis实现

12、双十一电商购物车系统如何用Redis实现

13、类似微信的社交App朋友圈关注模型如何设计实现

14、美团单车如何基于Redis快速找到附近的车

15、Redis 6.0 多线程模型比单线程优化在哪里了

缓存,session,数据库,

redis应用场景总结redis平时我们用到的地方蛮多的,下面就了解的应用场景做个总结:

1、热点数据的缓存

由于redis访问速度块、支持的数据类型比较丰富,所以redis很适合用来存储热点数据,另外结合expire,我们可以设置过期时间然后再进行缓存更新操作,这个功能最为常见,我们几乎所有的项目都有所运用。

2、限时业务的运用

redis中可以使用 expire
命令设置一个键的生存时间,到时间后redis会删除它。利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。

3、计数器相关问题

redis由于 incrby
命令可以实现原子性的递增,所以可以运用于高并发的秒杀活动、分布式序列号的生成、具体业务还体现在比如限制一个手机号发多少条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等。

4、排行榜相关问题

关系型数据库在排行榜方面查询速度普遍偏慢,所以可以借助redis的 SortedSet 进行热点数据的排序。

在奶茶活动中,我们需要展示各个部门的点赞排行榜,
所以我针对每个部门做了一个SortedSet,然后以用户的openid作为上面的username,以用户的点赞数作为上面的score,
然后针对每个用户做一个hash,通过zrangebyscore就可以按照点赞数获取排行榜,然后再根据username获取用户的hash信息,这个当时在实际运用中性能体验也蛮不错的。

5、分布式锁

这个主要利用redis的 setnx 命令进行,setnx:"set if not exists"就是如果不存在则成功设置缓存同时返回1,否则返回0
,这个特性在俞你奔远方的后台中有所运用,因为我们服务器是集群的,定时任务可能在两台机器上都会运行,所以在定时任务中首先
通过setnx设置一个lock,如果成功设置则执行,如果没有成功设置,则表明该定时任务已执行。
当然结合具体业务,我们可以给这个lock加一个过期时间,比如说30分钟执行一次的定时任务,那么这个过期时间设置为小于30分钟的一个时间
就可以,这个与定时任务的周期以及定时任务执行消耗时间相关。

当然我们可以将这个特性运用于其他需要分布式锁的场景中,结合过期时间主要是防止死锁的出现。

Redis Setnx( SET if N ot e X ists )命令在指定的 key 不存在时,为 key
设置指定的值,这种情况下等同 SET 命令。当
key存在时,什么也不做。

6、延时操作

这个目前我做过相关测试,但是还没有运用到我们的实际项目中,下面我举个该特性的应用场景。
比如在订单生产后我们占用了库存,10分钟后去检验用户是够真正购买,如果没有购买将该单据设置无效,同时还原库存。
由于redis自2.8.0之后版本提供Keyspace
Notifications功能,允许客户订阅Pub/Sub频道,以便以某种方式接收影响Redis数据集的事件。
所以我们对于上面的需求就可以用以下解决方案,我们在订单生产时,设置一个key,同时设置10分钟后过期,
我们在后台实现一个监听器,监听key的实效,监听到key失效时将后续逻辑加上。
当然我们也可以利用rabbitmq、activemq等消息中间件的延迟队列服务实现该需求。

7、分页、模糊搜索

redis的set集合中提供了一个zrangebylex方法,语法如下:

ZRANGEBYLEX key min max [LIMIT offset count]

通过ZRANGEBYLEX zset - + LIMIT 0 10 可以进行分页数据查询,其中- +表示获取全部数据

zrangebylex key min max
这个就可以返回字典区间的数据,利用这个特性可以进行模糊查询功能,这个也是目前我在redis中发现的唯一一个支持对存储内容进行模糊查询的特性。

前几天我通过这个特性,对学校数据进行了模拟测试,学校数据60万左右,响应时间在700ms左右,比mysql的like查询稍微快一点,但是由于它可以避免大量的数据库io操作,所以总体还是比直接mysql查询更利于系统的性能保障。

8、点赞、好友等相互关系的存储(sdd 添加 smember查看)

Redis
set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
又或者在微博应用中,每个用户关注的人存在一个集合中,就很容易实现求两个人的共同好友功能。

这个在奶茶活动中有运用,就是利用set存储用户之间的点赞关联的,另外在点赞前判断是否点赞过就利用了sismember方法,当时这个接口的响应时间控制在10毫秒内,十分高效。

9、队列

由于redis有list push和list pop这样的命令,所以能够很方便的执行队列操作。

redis单线程多线程?原因有什么好处?如何实现高效?

单线程,使用内存,使用多路I/O复用模型,非阻塞IO;数据结构简单

$redis能否当消息队列,用过哪些中间件消息队列,有什么不同?

在项目中用到了redis作为缓存,再学习了ActiveMq之后想着用redis实现简单的消息队列,下面做记录。

Redis的列表类型键可以用来实现队列,并且支持阻塞式读取,可以很容易的实现一个高性能的优先队列。同时在更高层面上,Redis还支持
"发布/订阅"的消息模式,可以基于此构建一个聊天系统。

一、redis的列表类型天生支持用作消息队列。 (类似于MQ的队列模型–任何时候都可以消费,一条消息只能消费一次)

在Redis中,List类型是按照插入顺序排序的字符串链表。

将redis发布订阅模式用做消息队列和rabbitmq的区别:(redis由订阅端实现监听)

可靠性

  • .redis :没有相应的机制保证消息的可靠消费,如果发布者发布一条消息,而没有对应的订阅者的话,这条消息将丢失,不会存在内存中;

  • rabbitmq:具有消息消费确认机制,如果发布一条消息,还没有消费者消费该队列,那么这条消息将一直存放在队列中,直到有消费者消费了该条消息,以此可以保证消息的可靠消费,;

实时性

  • redis:实时性高,redis作为高效的缓存服务器,所有数据都存在在服务器中,所以它具有更高的实时性

消费者负载均衡:

  • rabbitmq队列可以被多个消费者同时监控消费,但是每一条消息只能被消费一次,由于rabbitmq的消费确认机制,因此它能够根据消费者的消费能力而调整它的负载;

  • redis发布订阅模式,一个队列可以被多个消费者同时订阅,当有消息到达时,会将该消息依次发送给每个订阅者;

持久性

  • redis:redis的持久化是针对于整个redis缓存的内容,它有RDB和AOF两种持久化方式(持久化介绍:

持久化(persistence) — Redis
命令参考http://doc.redisfans.com/topic/persistence.html

  • ),可以将整个redis实例持久化到磁盘,以此来做数据备份,防止异常情况下导致数据丢失。

  • rabbitmq:队列,消息都可以选择性持久化,持久化粒度更小,更灵活;

队列监控

  • rabbitmq实现了后台监控平台,可以在该平台上看到所有创建的队列的详细情况,良好的后台管理平台可以方面我们更好的使用;

  • redis没有所谓的监控平台。

总结 redis: 轻量级,低延迟,高并发,低可靠性;

rabbitmq:重量级,高可靠,异步,不保证实时;

rabbitmq是一个专门的AMQP协议(即Advanced Message Queuing
Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计)队列,他的优势就在于提供可靠的队列服务,并且可做到异步,而redis主要是用于缓存的
,redis的发布订阅模块,可用于实现及时性,且可靠性低的功能。

redis中的哈希槽你知道吧?(用于均匀存值至集群)

Redis 哈希槽 - 孤独信徒 - 博客园Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-
value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384
求余data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGRlZnM+PGNsaXBQYXRoIGlkPSJwcmVmaXhfX2NsaXAtcGF0aCI+PHBhdGggY2xhc3M9InByZWZpeF9fY2xzLTEiIGQ9Ik0yOS40OCAyNS4yNGMtLjE2LTEuODktLjA4LTEuNS0uMjQtMy4xNmE3Mi4yNCA3Mi4yNCAwIDAwLTItMTAuMzdMMjYgNi4yNWwtMi42IDUuNDEtLjQ0Ljg5cS0uMjQuNDgtLjU0IDFhMjAuNDMgMjAuNDMgMCAwMS0xLjI0IDEuODQgMjAuMDggMjAuMDggMCAwMS0zLjA4IDMuMjQgMTkuNzIgMTkuNzIgMCAwMS0zLjg3IDIuNTNjLS43Mi4zMy0xLjQ1LjcxLTIuMjMgMS0uMzQuMTQtMS4xOS40NC0xLjkzLjY2YTIuNTUgMi41NSAwIDAxLTEuMDctLjNjLS4zNC0xLS44My0yLjI0LTEuMi0zLjE5LS41LTEuMjUtMS0yLjUtMS41MS0zLjczYTU5LjExIDU5LjExIDAgMDAtMy42Mi03LjI1IDU5LjgyIDU5LjgyIDAgMDAxLjUxIDcuOTRjLjMgMS4zLjcgMi41OCAxLjA1IDMuODdTNiAyMi43MSA2LjQ0IDI0YTEuNCAxLjQgMCAwMDEuMjkuOTNjMS4yOCAwIDIuNTcuMDYgMy44Ni4wNXMyLjU5IDAgMy44OC0uMTFhNTYgNTYgMCAwMDcuNzktLjg3Yy0xLjkyLS40OS03LjUtLjgxLTEwLjc5LTEuMDZhNDMgNDMgMCAwMDYuNTMtMS43IDI0LjA2IDI0LjA2IDAgMDA1LjM4LTMuNDVjLjcxLS42MiAxLjIxIDEuODggMi4zOSA1LjI3LjYyIDEuNjkuMzQgMSAxIDIuNjZzMS4zNyA0LjM4IDIuMTcgNmMtLjA3LTEuODItLjMxLTQuNjctLjQ2LTYuNDh6Ii8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9InByZWZpeF9fY2xpcC1wYXRoLTIiPjxwYXRoIGNsYXNzPSJwcmVmaXhfX2Nscy0xIiBkPSJNMjAuOTIgMy40NmE0LjI1IDQuMjUgMCAwMS4zMSA1Ljg2IDMuOTEgMy45MSAwIDAxLTUuNjYuMjQgNC4yNSA0LjI1IDAgMDEtLjMxLTUuODYgMy45MSAzLjkxIDAgMDE1LjY2LS4yNG0xLjM1LTEuNTRhNS44OSA1Ljg5IDAgMDAtOC41My4zNiA2LjQxIDYuNDEgMCAwMC40OCA4LjgzIDUuOTEgNS45MSAwIDAwOC41My0uMzYgNi40MSA2LjQxIDAgMDAtLjQ4LTguODN6Ii8+PC9jbGlwUGF0aD48c3R5bGU+LnByZWZpeF9fY2xzLTF7ZmlsbDojM2UzYTM5fUBtZWRpYSAocHJlZmVycy1jb2xvci1zY2hlbWU6ZGFyayl7LnByZWZpeF9fY2xzLTF7ZmlsbDojZWZlZmVmfX08L3N0eWxlPjwvZGVmcz48ZyBzdHlsZT0iaXNvbGF0aW9uOmlzb2xhdGUiPjxnIGlkPSJwcmVmaXhfX2xheWVyXzEiIGRhdGEtbmFtZT0ibGF5ZXIgMSI+PHBhdGggY2xhc3M9InByZWZpeF9fY2xzLTEiIGQ9Ik0yOS40OCAyNS4yNGMtLjE2LTEuODktLjA4LTEuNS0uMjQtMy4xNmE3Mi4yNCA3Mi4yNCAwIDAwLTItMTAuMzdMMjYgNi4yNWwtMi42IDUuNDEtLjQ0Ljg5cS0uMjQuNDgtLjU0IDFhMjAuNDMgMjAuNDMgMCAwMS0xLjI0IDEuODQgMjAuMDggMjAuMDggMCAwMS0zLjA4IDMuMjQgMTkuNzIgMTkuNzIgMCAwMS0zLjg3IDIuNTNjLS43Mi4zMy0xLjQ1LjcxLTIuMjMgMS0uMzQuMTQtMS4xOS40NC0xLjkzLjY2YTIuNTUgMi41NSAwIDAxLTEuMDctLjNjLS4zNC0xLS44My0yLjI0LTEuMi0zLjE5LS41LTEuMjUtMS0yLjUtMS41MS0zLjczYTU5LjExIDU5LjExIDAgMDAtMy42Mi03LjI1IDU5LjgyIDU5LjgyIDAgMDAxLjUxIDcuOTRjLjMgMS4zLjcgMi41OCAxLjA1IDMuODdTNiAyMi43MSA2LjQ0IDI0YTEuNCAxLjQgMCAwMDEuMjkuOTNjMS4yOCAwIDIuNTcuMDYgMy44Ni4wNXMyLjU5IDAgMy44OC0uMTFhNTYgNTYgMCAwMDcuNzktLjg3Yy0xLjkyLS40OS03LjUtLjgxLTEwLjc5LTEuMDZhNDMgNDMgMCAwMDYuNTMtMS43IDI0LjA2IDI0LjA2IDAgMDA1LjM4LTMuNDVjLjcxLS42MiAxLjIxIDEuODggMi4zOSA1LjI3LjYyIDEuNjkuMzQgMSAxIDIuNjZzMS4zNyA0LjM4IDIuMTcgNmMtLjA3LTEuODItLjMxLTQuNjctLjQ2LTYuNDh6Ii8+PGcgY2xpcC1wYXRoPSJ1cmwoI3ByZWZpeF9fY2xpcC1wYXRoKSI+PHBhdGggY2xhc3M9InByZWZpeF9fY2xzLTEiIGQ9Ik0tLjg3LS43OGgzNC40MnYzMy4wN0gtLjg3eiIvPjwvZz48cGF0aCBjbGFzcz0icHJlZml4X19jbHMtMSIgZD0iTTIwLjkyIDMuNDZhNC4yNSA0LjI1IDAgMDEuMzEgNS44NiAzLjkxIDMuOTEgMCAwMS01LjY2LjI0IDQuMjUgNC4yNSAwIDAxLS4zMS01Ljg2IDMuOTEgMy45MSAwIDAxNS42Ni0uMjRtMS4zNS0xLjU0YTUuODkgNS44OSAwIDAwLTguNTMuMzYgNi40MSA2LjQxIDAgMDAuNDggOC44MyA1LjkxIDUuOTEgMCAwMDguNTMtLjM2IDYuNDEgNi40MSAwIDAwLS40OC04LjgzeiIvPjxnIGNsaXAtcGF0aD0idXJsKCNwcmVmaXhfX2NsaXAtcGF0aC0yKSI+PHBhdGggY2xhc3M9InByZWZpeF9fY2xzLTEiIGQ9Ik0tLjg3LS43OGgzNC40MnYzMy4wN0gtLjg3eiIvPjwvZz48L2c+PC9nPjwvc3ZnPg==https://www.cnblogs.com/unqiang/p/10524177.html

Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16
算法算出一个结果,然后把结果对 16384 求余数,

这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念。

Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽。

这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。

使用哈希槽的好处就在于可以方便的添加或移除节点。

当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;

当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;

在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务。

"用了哈希槽的概念,而没有用一致性哈希算法,不都是哈希么?这样做的原因是为什么呢?"Redis
Cluster是自己做的crc16的简单hash算法,没有用一致性hash。Redis的作者认为它的crc16(key) mod
16384的效果已经不错了,虽然没有一致性hash灵活,但实现很简单,节点增删时处理起来也很方便。

"为了动态增删节点的时候,不至于丢失数据么?"节点增删时不丢失数据和hash算法没什么关系,不丢失数据要求的是一份数据有多个副本。

“还有集群总共有2的14次方,16384个哈希槽,那么每一个哈希槽中存的key 和 value是什么?”当你往Redis
Cluster中加入一个Key时,会根据crc16(key) mod 16384计算这个key应该分布到哪个hash slot中,一个hash
slot中会有很多key和value。你可以理解成表的分区,使用单节点时的redis时只有一个表,所有的key都放在这个表里;改用Redis
Cluster以后会自动为你生成16384个分区表,你insert数据时会根据上面的简单算法来决定你的key应该存在哪个分区,每个分区里有很多key。

介绍一下redis有哪些过期策略?

面试题 redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?

常见的有两个问题:

• 往 redis 写入的数据怎么没了? 可能有同学会遇到,在生产环境的 redis
经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 redis 你就没用对啊。redis 是缓存,你给当存储了是吧?
啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个 G 的内存,但是可以有几个 T
的硬盘空间**。redis 主要是基于内存来进行高性能、高并发的读写操作的**。 那既然内存是有限的,比如 redis 就只能用 10G,你要是往里面写了
20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。

• 数据明明过期了,怎么还占用着内存? 这是由 redis 的过期策略来决定。

面试题剖析 redis 过期策略 redis 过期策略是:定期删除+惰性删除。 所谓定期删除,指的是 redis 默认是每隔 100ms
就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。 假设 redis 里放了 10w 个
key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key
上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些
key 来检查和删除的。

但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis
会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 获取 key 的时候,如果此时 key
已经过期,就删除,不会返回任何东西。 但是实际上这还是有问题的,如果定期删除漏掉了很多过期
key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?

答案是:走内存淘汰机制。

内存淘汰机制 redis 内存淘汰机制有以下几个:

• noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。

• allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。

• allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的
key 给干掉啊。

• volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。

• volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。

• volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

项目中为什么要用redis来缓存赞和踩?数据不一致怎么办?如何保证数据的持久性?

取数快,有set

散列hash:Redis中的散列可以看成具有String key和String value的map容器,可以将多个key-
value存储到一个key中。每一个Hash可以存储4294967295个键值对。

因此,最终的结论是,需要解决的不一致,产生的原因是更新数据库成功,但是删除缓存失败。

解决方案大概有以下几种:

1. 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。

2. 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。

3. 给所有的缓存一个失效期。

task-blog-
BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-
task-blog-
BlogCommendFromMachineLearnPai2-1.control](https://blog.csdn.net/xinbumi/article/details/89742930?utm_medium=distribute.pc_relevant.none-
task-blog-
BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-
task-blog-BlogCommendFromMachineLearnPai2-1.control)

怎么设置一个key的过期时间?

EXPIRE 和 PERSIST 命令。Redis PERSIST 命令用于移除给定 key 的过期时间,使得 key 永不过期。

EXPIRE key seconds

为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。

在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。

$redis可能存在的坑

如何保证缓存和数据库同步

应用建设方案

假设有一亿个用户每个用户订阅了一个手机闹铃闹铃类型不同,都是7点钟的闹铃,如何保证7点时数据库的负载最小,简述你的方案

数据存储redis缓存

主从数据库

编程题:一个二维矩阵,矩阵中标0的位置是道路,标1的位置是墙壁,矩阵的边界也是墙壁,然后有一个起始点p和终点q,问:一个小球起始位于p,小球停止时可以选择一个方向一直滚动,碰到墙壁就停下来,问:小球能否从p滚到q,即最终要在q静止,滚过去不行。

编程题:给定一颗二叉树,实现一个方法让每个节点新增一个next,next指向当前节点右边的第一个兄弟节点

$场景题:有A、B、C三个方法,分别是循环输出A,输出B,输出C 10次,使用多线程实现按照“ABC”的顺序输出10次

package thread1; public class Main_thread2 { static boolean isThreadA = true;
static boolean isThreadB = false; static boolean isThreadC = false; public
static void main(String[] args) throws Exception{ byte []print = new byte[0];
Thread t1 = new Thread(new Runnable(){ public void run(){ synchronized
(print){ for(int i=0;i<10; i++){ while(!isThreadA){ try{ print.wait(); } catch
(InterruptedException e){ e.printStackTrace(); } } System.out.print(“A”);
isThreadA = false; isThreadB = true; isThreadC = false; print.notifyAll(); } }
} }); Thread t2 = new Thread(new Runnable(){ public void run(){ synchronized
(print){ for(int i=0;i<10; i++){ while(!isThreadB){ try{ print.wait(); } catch
(InterruptedException e){ e.printStackTrace(); } } System.out.print(“B”);
isThreadA = false; isThreadB = false; isThreadC = true; print.notifyAll(); } }
} }); Thread t3 = new Thread(new Runnable(){ public void run(){ synchronized
(print){ for(int i=0;i<10; i++){ while(!isThreadC){ try{ print.wait(); } catch
(InterruptedException e){ e.printStackTrace(); } } System.out.print(“C”);
isThreadA = true; isThreadB = false; isThreadC = false; print.notifyAll(); } }
} }); t1.start(); t2.start(); t3.start(); } }

$推荐算法-排名算法,权值计算

探究 Stack Overflow
网站的热点问题排名算法

$微信红包实现

1.红包算法,给定总金额,个数等参数,事先计算好随机金额的红包;

红包算法:每个人当前能抢到的金额,服从一个0.01到当前剩余均值两倍的左开右闭区间的均匀分布

random.nextInt(剩余平均红包金额*2)+1

高并发问题,一个人只能抢一个,一个红包不能被多人抢:

查询用户是否已参与过活动:可以使用Set的特性,集合中不能出现重复的数据,每个用户发起抢的动作就将用户标识放入Set中,如果Set中已存在这个用户标识,则说明用户不可再抢了

获取一个可抢的红包:可以使用队列的数据结构,每次获取红包都是一个出队的动作,解决了多人获取同一个红包和超发的问题

建立红包与用户的关系:同样使用队列的数据结构,每次建立都是一个入队动作,使用单独线程每秒尝试出队多个,批量存储到数据库中

  1. 实现功能
* 准备工作:生成可抢红包,入队redis的list结构,命令为RPUSH* 用户发出抢的请求:用户标识写入redis的Set中,命令SADD,返回1标识插入成功,可继续获取红包,返回0则为已抢,直接返回(一个人只能抢一个)* 获取红包:list出队一条红包数据,命令为LPOP,如果返回不为nil时,代表获取成功,继续下一步,反之则说明已抢完,返回个红包不能被多人抢)* 建立红包与用户的关系:构建红包与用户的关系对象,入队Redis的list,使用单独线程每秒尝试出队1000个(举例),批量存储到数据库中

lpush(name,values) --> 实际上是左添加

在name对应的list中添加元素,每个新的元素都添加到列表的最左边

如: # r.lpush(‘oo’, 11,22,33) # 保存顺序为: 33,22,11

扩展:# rpush(name,values) 表示从右向左操作

lpop(name) -->列表的左侧获取第一个元素并在列表中移除,返回值是第一个元素

更多: # rpop(name) 表示从右向左操作

Redis Incr 命令将 key 中储存的数字值增一。

如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。

如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。

redis 127.0.0.1:6379> INCR KEY_NAME

如何把一个文件快速下发到100个服务器

想一想迅雷的下载方式就可以了。下载好的主机同样可以作为上传服务器。不断扩散,速度很快

保证发送消息的有序性,消息处理的有序性

3.2.1 rabbitmq

拆分多个queue,每个queue一个consumer 就是多一些queue而已,确实麻烦点
或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HlIBvjLx-1681014581797)(【精】各大厂问题汇总_files/Image [21].png)]

10亿个数最大的10个

局部淘汰法,分治法,Hash法,最小堆法

redis2.6.12过期时间设置

EXPIRE cache_page 30000 # 更新过期时间 TTL cache_page #查看剩余生存时间 PERSIST
message#移除过期时间设置 从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改: EX second
:设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。 PX
millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于
PSETEX key millisecond value 。 NX :只在键不存在时,才对键进行设置操作。SET key value NX 效果等同于
SETNX key value 。 XX :只在键已经存在时,才对键进行设置操作。

分布式集群保证线程的安全性

避免并发,
相比于单一部署的服务器来说,分布式架构同一个模块的系统部署了多台;对于单一服务来说,只要保证一台机器上的对于共享资源的访问是同步进行的就能保证线程安全了;但是对于分布式系统而已,保证一台服务器的同步,并不能保证访问共享资源是同步的;所以可以考虑使用分布式锁的方式来保证分布式中的线程的安全线,这样不同的服务不同的线程通过竞争分布式锁来获取共享资源的操作权限;例如redis的分布式锁、zookeeper锁,都可以作为分布式线程安全的手段。

十万个数输出从小到大

位图法 bitmap

在海量数据中查找出重复出现的元素或者去除重复出现的元素是面试中常考的文图。针对此类问题,可以使用位图法来解决。例如:已知某个文件内包含若干个电话号码,要求统计不同的号码的个数,甚至在O(n)时间复杂度内对这些号码进行排序。

位图法需要的空间很少(依赖于数据分布,但是我们也可以通过一些放啊发对数据进行处理,使得数据变得密集),在数据比较密集的时候效率非常高。例如:8位整数可以表示的最大十进制数值为99999999,如果每个数组对应于一个bit位,那么把所有的八进制整数存储起来只需要:99Mbit
= 12.375MB.

十万个单词,找出重复次数最高十个

先遍历十万个单词 统计每个单词出现次数,然后局部淘汰法排序取前十

多个电脑存储几亿搜索日志,你有一台2G的电脑,找出搜索热度最高的10个词

分别统计每台电脑搜索日志的关键词的重复次数,然后排序

原理篇

基础

NIO

NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统 IO 基于字节流和字 符流进行操作,而 NIO 基于
Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区 中,或者从缓冲区写入到通道中。单个线程可以监听多个数据通道

JMM

Java内存模型(即Java Memory
Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行。

首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

RabbitMQ 高级消息队列协议 AMQP 的开源实现

首先来看看RabbitMQ里的几个重要概念:

  • 生产者(Producer):发送消息的应用。

  • 消费者(Consumer):接收消息的应用。

  • 队列(Queue):存储消息的缓存。

  • 消息(Message):由生产者通过RabbitMQ发送给消费者的信息。

  • 连接(Connection):连接RabbitMQ和应用服务器的TCP连接。

  • 通道(Channel):连接里的一个虚拟通道。当你通过消息队列发送或者接收消息时,这个操作都是通过通道进行的。

  • 交换机(Exchange):交换机负责从生产者那里接收消息,并根据交换类型分发到对应的消息列队里。要实现消息的接收,一个队列必须到绑定一个交换机。

  • 绑定(Binding):绑定是队列和交换机的一个关联连接。

  • 路由键(Routing Key):路由键是供交换机查看并根据键来决定如何分发消息到列队的一个键。路由键可以说是消息的目的地址。

生产者(Producer) 发送/发布消息到代理-> 消费者(Consumer)
从代理那里接收消息。哪怕生产者和消费者运行在不同的机器上, RabbitMQ 也能扮演代理中间件的角色。

当生产者发送消息时,它并不是直接把消息发送到队列里的,而是使用交换机(Exchange)来发送。下面的设计图简单展示了这三个主要的组件之间是如何连接起来的。

交换机代理(exchange
agent)负责把消息分发到不同的队列里。这样的话,消息就能够从生产者发送到交换机,然后被分发到消息队列里。这就是常见的“发布”方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdAh6ThT-1681014581798)(【精】各大厂问题汇总_files/Image [22].png)]

往多个队列里发送消息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arCxzTpa-1681014581798)(【精】各大厂问题汇总_files/Image [23].png)]

给带有多个队列的交换机发送的消息是通过绑定和路由键来进行分发的。绑定是你设置的用来连接一个队列和交换机的连接。路由键是消息的一个属性。交换机会根据路由键来决定消息分发到那个队列里(取决于交换机的类型)。

交换机(Exchange)

消息并不是直接发布到队里里的,而是被生产者发送到一个交换机上。交换机负责把消息发布到不同的队列里。交换机从生产者应用上接收消息,然后根据绑定和路由键将消息发送到对应的队列里。绑定是交换机和队列之间的一个关系连接。

rabbitmq和kafka区别

RabbitMQ采用AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个进程间传递 异步消息

网络协议

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-efvbSlvb-1681014581799)(【精】各大厂问题汇总_files/Image [24].png)]

RabbitMQ的broker由Exchange,Binding,queue组成

kafka采用mq结构:broker 有part 分区的概念

rabbitMQ的负载均衡需要单独的loadbalancer进行支持。

kafka采用zookeeper对集群中的broker、consumer进行管理

RabbitMQ 采用push的方式

kafka采用pull的方式

rabbitMQ支持对消息的可靠的传递,支持事务,不支持批量的操作;基于存储的可靠性的要求存储可以采用内存或者硬盘。金融场景中经常使用

kafka具有高的吞吐量,内部采用消息的批量处理,zero-
copy机制,数据的存储和获取是本地磁盘顺序批量操作,具有O(1)的复杂度(与分区上的存储大小无关),消息处理的效率很高。(大数据)

RabbitMQ里的消息流程

  • 生产者(producer) 把消息发送给交换机。当你创建交换机的时候,你需要指定类型。交换机的类型接下来会讲到。

  • 交换机(exchange) 接收消息并且负责对消息进行路由。根据交换机的类型,消息的多个属性会被使用,例如路由键。

  • 绑定(binding) 需要从交换机到队列的这种方式来进行创建。在这个例子里,我们可以看到交换机有到两个不同队列的绑定。交换机根据消息的属性来把消息分发到不同的队列上。

  • 消息(message) 消息会一直留在队列里直到被消费。

  • 消费者(consumer) 处理消息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-smWtnCak-1681014581800)(【精】各大厂问题汇总_files/Image [25].png)]

  • 直接(Direct) :直接交换机通过消息上的路由键直接对消息进行分发。 最简单模式

  • 扇出(Fanout) :一个扇出交换机会将消息发送到所有和它进行绑定的队列上。 广播模式

  • 主题(Topic) :这个交换机会将路由键和绑定上的模式进行通配符匹配。 最能契合复杂应用

  • 消息头(Headers) :消息头交换机使用消息头的属性进行消息路由。

生产者 private final static String QUEUE_NAME = “hello”; public static void
main(String[] args) throws Exception{ // TODO Auto-generated method stub
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(“localhost”);//因为两个进程在同一个机器上 Connection connection = null;
Channel channel = null; connection = factory.newConnection(); channel =
connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false,
false, null); String message = “Hello World!”; channel.basicPublish("",
QUEUE_NAME, null, message.getBytes(“UTF-8”)); System.out.println(" [Producer]
Sent ‘" + message + "’"); channel.close(); connection.close(); } 消费者 private
final static String QUEUE_NAME = “hello”; public static void main(String[]
args) throws Exception{ ConnectionFactory factory = new ConnectionFactory();
factory.setHost(“localhost”); Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,
false, false, false, null); System.out.println(" [*] Waiting for messages. To
exit press CTRL+C"); Consumer consumer = new DefaultConsumer(channel) {
@Override public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException { String
message = new String(body, “UTF-8”); System.out.println(" [Consumer] Received
‘" + message + "’"); } }; channel.basicConsume(QUEUE_NAME, true, consumer); }

Kafka 高吞吐量、分布式、基于发布/订阅的消息系统

消息中间件,作用: 解耦、异步、削峰

一、 点对点模式

如上图所示,点对点模式通常是基于拉取或者轮询的消息传送模型,这个模型的特点是发送到队列的消息被一个且只有一个消费者进行处理。生产者将消息放入消息队列后,由消费者主动的去拉取消息进行消费。点对点模型的的优点是消费者拉取消息的频率可以由自己控制。但是消息队列是否有消息需要消费,在消费者端无法感知,所以在消费者端需要额外的线程去监控。

二、 发布订阅模式

如上图所示,发布订阅模式是一个基于消息送的消息传送模型,改模型可以有多种不同的订阅者。生产者将消息放入消息队列后,队列会将消息推送给订阅过该类消息的消费者(类似微信公众号)。由于是消费者被动接收推送,所以无需感知消息队列是否有待消费的消息!但是consumer1、consumer2、consumer3由于机器性能不一样,所以处理消息的能力也会不一样,但消息队列却无法感知消费者消费的速度!所以推送的速度成了发布订阅模模式的一个问题!假设三个消费者处理速度分别是8M/s、5M/s、2M/s,如果队列推送的速度为5M/s,则consumer3无法承受!如果队列推送的速度为2M/s,则consumer1、consumer2会出现资源的极大浪费!

上面简单的介绍了为什么需要消息队列以及消息队列通信的两种模式,接下来就到了我们本文的主角——kafka闪亮登场的时候了!Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据,具有高性能、持久化、多副本备份、横向扩展能力

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Up08swhD-1681014581800)(【精】各大厂问题汇总_files/Image [26].png)]

如果看到这张图你很懵逼,木有关系!我们先来分析相关概念

Producer :Producer即生产者,消息的产生者,是消息的入口。

kafka cluster

Broker
:Broker是kafka实例,每个服务器上有一个或多个kafka的实例,我们姑且认为每个broker对应一台服务器。每个kafka集群内的broker都有一个
不重复 的编号,如图中的broker-0、broker-1等……

Topic :消息的主题,可以理解为消息的分类,kafka的数据就保存在topic。在每个broker上都可以创建多个topic。

Partition
:Topic的分区,每个topic可以有多个分区,分区的作用是做负载,提高kafka的吞吐量。同一个topic在不同的分区的数据是不重复的,partition的表现形式就是一个一个的文件夹!

Replication
:每一个分区都有多个副本,副本的作用是做备胎。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本(包括自己)。

Message :每一条发送的消息主体。。

Consumer :消费者,即消息的消费方,是消息的出口。

Consumer Group
:我们可以将多个消费组组成一个消费者组,在kafka的设计中同一个分区的数据只能被消费者组中的某一个消费者消费。同一个消费者组的消费者可以消费同一个topic的不同分区的数据,这也是为了提高kafka的吞吐量!

Zookeeper :kafka集群依赖zookeeper来保存集群的的元信息,来保证系统的可用性。

为什么要用 Partition :Topic的分区

1、 方便扩展 。因为一个topic可以有多个partition,所以我们可以通过扩展机器去轻松的应对日益增长的数据量。

2、 提高并发 。以partition为读写单位,可以多个消费者同时消费数据,提高了消息的处理效率。

import java.util.Properties; import
org.apache.kafka.clients.producer.KafkaProducer; import
org.apache.kafka.clients.producer.ProducerRecord; import
org.apache.kafka.common.serialization.StringSerializer; /** * * Title:
KafkaProducerTest * Description: * kafka 生产者demo * Version:1.0.0 * @author
pancm * @date 2018年1月26日 / public class KafkaProducerTest implements Runnable
{ private final KafkaProducer<String, String> producer; private final String
topic; public KafkaProducerTest(String topicName) { Properties props = new
Properties(); props.put(“bootstrap.servers”,
“master:9092,slave1:9092,slave2:9092”); props.put(“acks”, “all”);
props.put(“retries”, 0); props.put(“batch.size”, 16384);
props.put(“key.serializer”, StringSerializer.class.getName());
props.put(“value.serializer”, StringSerializer.class.getName()); this.producer
= new KafkaProducer<String, String>(props); this.topic = topicName; }
@Override public void run() { int messageNo = 1; try { for(;

【精】JAVA各大厂问题汇总-HELLO XF相关推荐

  1. Java面试题大汇总,2021年附答案解析

    最新常Java面试题大汇总(含答案解析)发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全汇总,希望对大家有帮助哈 本套Java面试题大全,全的不能再全,哈哈~ ...

  2. 阿里最全Java面试100题汇总:涵盖天猫、蚂蚁金服等面试题!含答案~

    [阿里天猫.蚂蚁.钉钉面试题目] 1.微信红包怎么实现. 2.海量数据分析. 3.测试职位问的线程安全和非线程安全. 4.HTTP2.0.thrift. 5.面试电话沟通可能先让自我介绍. 6.分布式 ...

  3. 闭关30天,献上【Java一线大厂高岗面试题解析合集】,冲刺金九银十!

    概述 时间不等人,2022年转眼就要过去大半了,春招在疫情中度过,不知有多少人还在惋惜... 马上又是秋招的高峰"金九银十",估计现在就已经有不少的程序猿(媛)朋友早就踏上提前批之 ...

  4. 闭关28天,奉上[Java一线大厂高岗面试题解析合集],备战金九银十

    前言 时间不等人,2022年转眼就要过去大半了,春招在疫情中度过,不知有多少人还在惋惜... 马上又是秋招的高峰"金九银十",估计现在就已经有不少的程序猿(媛)朋友早就踏上提前批之 ...

  5. 万字长文,冲刺备战金九银十,奉上[Java一线大厂高岗面试题解析合集]

    时间不等人,2022年转眼就要过去大半了,春招在疫情中度过,不知有多少人还在惋惜... 马上又是秋招的高峰"金九银十",估计现在就已经有不少的程序猿(媛)朋友早就踏上提前批之路了吧 ...

  6. OpenCV3 Java 机器学习使用方法汇总

    原文链接:OpenCV3 Java 机器学习使用方法汇总  前言 按道理来说,C++版本的OpenCV训练的版本XML文件,在java中可以无缝使用.但要注意OpenCV本身的版本问题.从2.4 到3 ...

  7. Java List面试题汇总

    转载自 Java List面试题汇总 1.你知道的List都有哪些? 2.List和Vector有什么区别? 3.List是有序的吗? 4.ArrayList和LinkedList的区别?分别用在什么 ...

  8. 【月报】Java知音的五月汇总

    往期: Java知音的十月:[月报]Java知音十月汇总 Java知音的十一月:[月报]Java知音十一月汇总 Java知音的十二月:[月报]Java知音十二月汇总 Java知音的一月:[月报]Jav ...

  9. 【月报】Java知音的四月汇总

    往期: Java知音的十月:[月报]Java知音十月汇总 Java知音的十一月:[月报]Java知音十一月汇总 Java知音的十二月:[月报]Java知音十二月汇总 Java知音的一月:[月报]Jav ...

最新文章

  1. Semtech与Lacuna从太空接收信息
  2. 读懂正则表达式就这么简单
  3. P4126-[AHOI2009]最小割【网络流,tarjan】
  4. [react] React中在哪捕获错误?
  5. IGMP SSM Mapping原理与实验
  6. JavaWeb初级篇-HttpPost使用教程
  7. 翻译: 3.7. Softmax 回归的简明实现 pytorch
  8. C#,提取avi,mpeg,mp4,rmvb,mkv,flv等等视频文件的摘要信息的方法及其源程序
  9. mac上好用的压缩_Mac上好用的解压缩软件有哪些?推荐四款免费的mac解压缩工具...
  10. 转载:ultraiso制作超过4G的系统U盘启动盘教程
  11. [LTE] RRU BBU 和 前传(fronthaul)
  12. vue-amap的使用
  13. 【SAMMY】DOS下操作隐藏文件、文件夹
  14. 制作深山红叶(WinPe)运行在usb盘中的方法
  15. Redis 缓存回收的7种策略volatile设置过期时间及allkeys所有数据范围内
  16. win10开机桌面图像获取
  17. 在浏览器查看base64格式的图片
  18. macos 旧版本 lightroom 找不到新镜头配置文件 新镜头配置导入lr/ps
  19. 弹幕插件easyDanmaku.js使用详解
  20. 3DMAX打开模型一直未响应

热门文章

  1. bilibili校招题目——扭蛋机
  2. Android 9.0系统源码_SystemUI(六)滑动锁屏的创建
  3. 英语影视台词---无敌破坏王2大脑互联网
  4. 函数式思维: 运用函数式思维,第2 部分
  5. C++读取netcdf文件
  6. 有关2pc, 3pc,Tcc 的理解
  7. 阿里系App抓包详细分析
  8. 帮优质粉丝脱单|【英国女】No.33|22岁,硕士,喜欢旅行爱好做饭,消费者心理学...
  9. dom影像图形成数字地形图_数字正射影像图DOM
  10. Jboot 跨域请求