栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java基础

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java基础

项目详细讲解

锦鲤日续订活动:

1、网关转发请求后,对入参RSA验签

2、在业务处理器初始化后,把中移动传过来的业务类型参数注入到处理器的属性,然后实现处理器做业务逻辑处理,远程调用用户模块、协议模块暴露的查询服务RPC接口、对用户的uid进行续订资格校验查询,将第三方通知处理结果返回给网关

3、跳转续订页面,小程序后端调用核心模块,如果是续订场景,做业务逻辑处理,续订同步任务记录先初始化入库,更新任务状态为代执行,选择对应的topic,给自己发MQ消息,消费成功时,将同步消息记录表入库,同时调用渠道模块,组装同步信息,走出口网关,同步给中移动发送请求,信息同步给中移

付款模块:

4.1 钱包模块接收到扣款消息,先发送扣款成功半消息
4.2 钱包模块执行本地事务,模拟支付,更新钱包信息,插入扣款流水
4.3 回查逻辑为:查询本地是否存在扣款流水,不存在则回滚事务消息
4.4 订单模块接收到事务消息后,开始更新本地订单状态。此处通过乐观锁保证幂等
算法

  • 冒泡:每两个比较交换然后后移 n2 稳定
  • 选择:每趟从待排序的挑最大的 n2 不稳定
  • 插入:从后往前插入已经排好序的 n2 稳定
  • 快排:给基准数找正确位置 nlog2n
  • 归并:分成n/2 nlogn2 稳定
  • 希尔:n^ 1.3 不稳定
  • 堆排序:nlogn2
基础 JDK8新特性
lambda表达式
stream API
改进的日期API
metaspace替换permGen(永久代)
metaspace 并不在虚拟机中,而是使用本地内存。替换的目的一方面是可以提升对元数据的管理同时提升 GC 效率,另一方面是方便后续 HotSpot 与 JRockit 合并
方法引用
interface提供默认方法
TomCat

servlet生命周期

实例化:在第一次访问或启动tomcat时,tomcat会调用此无参构造方法实例化servlet。(一次)
初始化:tomcat在实例化此 servlet 后,会立即调用 init() 方法初始化servlet。(一次)
服务:容器收到请求后调用 servlet 的 service() 方法来处理请求。(多次)
销毁:容器删除 servlet 对象,删除前会调用 destory() 方法。(一次)

过滤器、监听器、拦截器

Filter:Servlet中的过滤器实现Filter接口,在doFilter过滤字符编码、业务逻辑判断,只初始化一次

Listener:ServletContextListener接口,初始化的内容添加工作。做一些密钥参数的组装

Interceptor:基于JAVA的反射机制,拦截器不依赖与servlet容器

反射机制:Java在将.class字节码文件载入时,JVM将产生一个Class对象,从Class对象中可以获得类的基本信息

Object类常用方法:

toString()equals()hashCode()clone()wait()notify()

IOC

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理

wrapper包装类:

int的包装类Integer称为包装类。Java是面向对象语言,集合的容器要求元素是Object类型。

	Integer i =10;  //自动装箱
	int b= i;     //自动拆箱

实现IOC:

  1. 加载 xml 配置文件,遍历其中的标签构建 ApplicationContext
  2. Resource定位,访问XML配置文件,获取标签中的 id 和 class 属性,加载 class 属性对应的类,并创建 bean
  3. 遍历标签中的标签,获取属性值资源,并将属性值填充到 bean 中
  4. 将 BeanDefiniton 注册到 bean 容器中
TCP和UDP 三次握手
	1、客户端给服务器发送一个 SYN 报文。										发送正常
  2、服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。		接收正常
  3、客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。		
  4、ACK 报文之后,三次握手建立完成。
四次挥手
  1、客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
  2、服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。
  3、如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
  4、客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态
  5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

利用滑动窗口实现
原理:让发送方的发送速率不要太快,要让接收方来得及接收。
原则:发送方的发送窗口不能超过接收方给出的接收窗口的数值。窗口单位是字节,不是报文段。[通过TCP首部窗口字段(rwnd)调整接收方的发送窗口数值大小]

例如在介绍 TCP 建连与断连时,最好能够指出线上如果出现大量 time_wait 时,可以通过调整系统参数加快连接的回收与复用

fail-fast和fail-safe

用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。

java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)

java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

BIO/NIO/AIO

1、BIO/阻塞IO,从系统内核拷贝数据到内存空间时,有线程阻塞等待数据传输完成,然后处理

2、NIO:将BIO等待时间做一个轮询机制,如果数据处理好,就调度线程处理

3、AIO:异步通信,数据加载完后,push发事件通知

TomCat

catalina包:

Container:pipeline-valve是责任链模式,

connector:将底层网络协议的数据封装成符合servlet规范的数据连接Servlet容器

JVM 类加载机制

1、LOAD加载:加载到内存,生成二进制字节流,转化为方法区数据结果,生成对象。

2、link验证:对文件格式、元数据、字节码、引用验证

准备:给类分配内存设置初始值

解析:常量池的符号引用改为直接引用

3、INIT初始化:执行代码

4、使用:使用阶段包括主动引用和被动引用,主动饮用会引起类的初始化,而被动引用不会引起类的初始化

5、卸载:类所有的实例都已经被回收、ClassLoader被回收、无反射(Major GC)

虚拟机

公有线程

程序计数器:线程独立,改变计数器来选取指令

Native方法栈:Java 通过 JNI 直接调用本地 C/C++ 库

Java堆:储存实例对象、1.8存字符串常量 Java堆是垃圾收集器管理的主要区域。线程共享。-Xmx4000m -Xms4000m LargePageSizeInBytes(大分页内存)=128m,使用70%后开始CMS收集

虚拟机栈(java栈):线程私有,存储局部变量表、操作数栈、动态链接、方法出口。

方法区:所有的①类(class)②静态变量(static变量)③静态方法④常量 ⑤成员方法

元空间:Object的System.out、int型常量

字符串放在常量池,jdk7放在堆内存永久代,jdk8放在元空间

双亲委派模型

两种类加载器:

  • 启动类加载器(Bootstrap ClassLoader):加载jr的核心库

  • 其它类的加载器:

    扩展类加载器(Extension ClassLoader) 加载ext目录的扩展包
    应用程序类加载器(Application ClassLoader)java代码

破坏双亲委派模型

1、继承ClassLoader覆盖loadClass() 2、线程上下文类加载器 3、热替换、模块热部署

Tomcat类加载:Catalina类加载器(加载Tomcat专用的类)和Shared类加载器(Web应用程序都复用的类)是兄弟关系

为什么委托机制:递归的向父类查找,防止内存中出现多份同样的字节码

垃圾标记算法

原理:

引用计数算法:有一个地方引用它时,计数器值就加1。缺点:互相引用

根可达算法:GC Roots 的对象作为根,走过的路径称为引用链,没有引用链对象叫垃圾

垃圾收集算法

标记-清除算法、标记-整理算法、复制算法、分代收集算法

垃圾回收机制

Young GC:新生代“消失”的过程

Old GC:只收集old gen的GC

Full GC:收集整个堆,包括young gen、old gen、perm gen

新生代空间的构成:比例8:1:1 一个伊甸园空间(Eden)标记清除、两个幸存者空间(Survivor0、Survivor1)复制

Eden满了,执行YoungGC,移动到Survivor0空间,Survivor0满了,存活对象移动到Survivor1,重复15次,剩下移动到老年代

老年代:对象比较大放老年代,老年代存不下引发Full GC,调优主要是减少 Full GC 的触发次数

finalize()和system.gc():System.gc()是会暂停整个进程的FullGC

finalize()是JVM对垃圾回收对象进行第一次标记加入FinalizerQueue中,如果第二次标记存在引用链,就不会清理

避免OOM:善于利用软引用和弱引用

强引用:内存空间不足,JVM不会回收。引起OOM异常。String str = “hello”; 只能通过 str = null; 取消强引用

软引用:内存的空间足够,JVM不会回收。 SoftReference softName = new SoftReference<>(“张三”);

弱引用:JVM一定会回收。例如ThreadLocalMap的,存的线程名。

虚引用:任何时候都可能被垃圾回收器回收

三色标记法:

GCRoots:方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收

白:未被标记对象 灰:自身被标记,成员变量未被标记 黑:自身和成员变量被标记

1、GCRoots指向的对象置为灰色。

2、把灰对象全部置为黑,第一步的灰对象指向的为灰

3、继续灰变黑,灰指向灰色,等灰色都变成黑色,剩下的白色需要清除

CMS

使用 标记-清理 算法,用两次短暂的暂停来代替串行或并行标记整理算法时候的长暂停。

1、初始标记:停顿线程STW,标记对象

2、并发标记:用户线程可以和GC线程一起并发执行

3、并发预清理:重新扫描,找到2并发标记过程需要被回收的

4、重新标记:停顿线程STW

5、并发清理:并发执行

CMS垃圾清理线程数量 默认为:(CPU核数 + 3) / 4

默认在full gc之后会再次进行stop the world,对内存碎片进行整理,空出连续的内存空间。

默认每次执行完full gc之后会进行一次内存碎片整理,该次数可配置。

G1

并行与并发的标记-复制垃圾回收器。G1将内存划分一个个固定大小的region,每个region可以是年轻代、老年代的一个。

整个堆被划分成2048左右个Region。每个Region的大小在1-32MB之间

Region的状态有三种:young(新生代)old(老年代)Humongous(半满)

把一个对象分配到Region内,只需要简单增加top的值,撞点到top值,就从新变老,维护一个空间Region的链表,回收之后的Region都会被加入到这个链表中。

JVM实战

堆内存分配空间大小

n=核心业务对象大小
m=每秒请求数(单个节点)
x=预期进行minorGC的时间间隔,单位:秒
mn10=每秒所有对象的占用空间
mn10*x=年轻代大小(新生代大小)

jps : 虚拟机进程状况工具,列出正在运行的虚拟机进程

jinfo : Java配置信息工具

jmap : 生成堆转储快照

jstack :生成虚拟机当前时刻的线程快照

分析OOM异常

VisualVM定位堆转储大小,分析异常

1、jps 定位进程号PID

2、输出.hprof文件,堆快照。手工直接导,PID为进程号

jmap -dump:live,format=b,file=m.hprof PID

3、JProfiler 打开目录下的hprof 文件

4、找到最大的几个size,分析占用空间异常原因

5、安装Visual GC,查看老年代回收情况,FullGC时间,并分析

设计模式

在聊到设计模式的时候注意多举例子:
spring框架的AbstarctApplicationContext的refresh()方法用到了模板方法模式
Spring框架的event机制是用到了观察者模式,我写过一个基于观察者模式的单机版本的配置服务,观察者线程去对配置进行拉取并对比,一旦发现需要更新则通知被观察者进行更新。
RocketMQ大量使用代理模式,工厂模式,通过代理模式实现扩展功能,如traceId的实现,事务消息发送者的实现等
单例模式:RocketMQ的MQClientInstance就是应用了单例模式,每个发送者/消费者只有一个MQClientInstannce实例用于提供与远端broker的交互
Dubbo框架中的扩展点,自动包装,就采用了装饰器模式,对协议进行增强,这样可以让底层专注于自己的实现,在上层对
协议进行统一的管理
SpringMVC的dispatcherServlet中的核心逻辑onRefresh采用了模板方法模式

抽象工厂模式

工厂模式的设计思想是通过一个工厂函数,快速批量地建立一系列相同的类。我们也可以用来创建对象、方法。

一个超级工厂创建其他工厂。

抽象工厂创建 查流量 接口,定义继承抽象类 FlowFactory工厂类,创建一个工厂创造器 FactoryProducer。 业务实体类使用FactoryProducer获取FlowFactory的对象,像AbstractFactory传递要查询过期流量的数据,获取所需对象的类型

单例模式

当我们需要统一管理资源,共享资源的时候就需要使用单例模式。单例模式提供了唯一实例的受控访问,因为单例模式封装了他的唯一实例。例如,写一个线程池工具类,ThreadPoolUtil。再或者,枚举类是最简单的单例

getInstance():只初始化一次,不能new来创建对象,只能调用getInstance方法来得到,保证了每次调用都返回相同的对象

代理模式

想在访问一个类时做一些控制。一个类代表另一个类的功能

代理机制:

静态代理:

类A实现接口A,创建一个Proxy类B实现接口A,在不修改被代理对象A的基础上,对类A的方法进行增强

动态代理:

类A实现接口A,创建一个Proxy类B实现InvocationHandler接口,通过反射机制从类A的class字节码获取需要被代理的类A,将类A和代理接口A交给InvocationHandler,对类A的方法进行增强

JDK动态代理:

InvocationHandler.invoke(Object obj,Method method, Object[] args)

Proxy.getProxyClass (ClassLoader loader, Class[] interfaces)

代理类实现InvocationHandler,用CreatProxyedObj()方法,在同一个classloader下通过接口创建出一个对象,通过代理工厂创建一个代理对象,然后实现代理增强

CGLib动态代理:

继承实现接口的父类公开方法,然后重写时增强。CglibProxy implements MethodInterceptor。

CGlib提供了方法过滤器(CallbackFilter),来过滤不需要被重写的方法

区别:Spring默认使用JDK代理,如果需要被代理的类没有接口,用CGLib

模版模式 组合模式 责任链设计模式

为请求创建了一个接收者对象的链。

创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。

缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。

spring

注入方式:DI(依赖注入)实现IOC(控制反转),注入方式:构造方法注入,setter注入,基于注解的注入

循环依赖:提前暴露

三级缓存:

singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例

earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例

singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

事务失效的原因

1、数据库引擎不支持事务,MyISAM 引擎是不支持事务操作的

2、没有被 Spring 管理,去掉注解@Service,不会被 Spring 管理

3、@Transactional 注解只能应用到 public 可见度的方法上,用在其他不会报错,但被注解的方法将不会展示已配置的事务设置

4、自身调用问题,方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的方法不生效

bean生命周期

beanfactory生命周期:去掉ApplicationContextAware接口步骤

1、实例化Bean

调用createBean进行实例化,实例化对象被包装在BeanWrapper对象中

2、 设置对象属性(依赖注入)

3、 注入Aware接口,实现BeanNameAware接口,BeanFactoryAware接口,ApplicationContextAware接口

4、前置处理——》检查配置自定义的init方法——〉后置处理

5、使用

6、Bean是否实现DisposableBean接口,调用destroy方法

7、是否配置destroy-method属性自动销毁

注解

@Autowired按byType自动注入,而@Resource默认按 byName自动注入

集合 Iterator

迭代器不是一个集合,它是一种用于访问集合的方法

it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。

it.hasNext() 用于检测集合中是否还有元素。

it.remove() 将迭代器返回的元素删除

List

linkedList 和 ArrayList 都是线程不安全的,可以使用 Collections 中的方法在外部封装一下。

  • ArrayList,数组集合,默认容量=10,扩容1.5倍

  • linkedList ,双向链表,由 Node 对象构成

    public Object pop() {
    //堆栈
    return this.list.removeLast();
    //队列
    return this.list.removeFirst();
    }

Map
  • TreeMap,红黑树实现,内部元素的排序,但性能差
  • HashTable,为了实现HashTable的多线程安全,所有方法加synchronized,已被淘汰

HashMap和HashTable的区别

1、父类不同:HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。

2、对外提供的接口不同:Hashtable比HashMap多提供了elments() 和contains() 两个方法。elments()返回value的枚举。contains()判断传入的value

3、对null的支持不同:Hashtable:key和value都不能为null;HashMap:key仅一个可以为null,多个value为null

4、安全性不同:HashMap是线程不安全的,会产生死锁环链;Hashtable是线程安全的,所有方法加synchronized,已被淘汰

HashTable:初始容量是11,扩容策略是翻倍加1,即当前容量 capacity * 2 + 1

put:哈希取模,头插法插入重复元素 get:做Hash映射,得到对应的index,重复元素尾出

  • linkedHashMap,双向链表的HashMap,元素有序

  • HashMap,数组+单链表+红黑树,默认容量=16,扩容2倍

1、哈希函数

  1. 对key对象的hashcode进行扰动:低16位是和高16位进行异或
  2. 通过取模求得数组下标

2、哈希冲突

出现冲突后采用的是链地址法,

  • 当链表的长度>=8且数组长度>=64时,会把链表转化成红黑树。
  • 当链表长度>=8,但数组长度<64时,会优先进行扩容,而不是转化成红黑树。
  • 当红黑树节点数<=6,自动转化成链表。

3、扩容方案

1.7:遍历所有的节点,把所有的节点依次通过hash函数计算新的下标,再头插法插入到新数组的链表中

1.8:2倍长度,一定是原来位置+原数组长度。

4、线程安全

HashMap并不是线程安全的,在多线程的情况下导致数据丢失,死循环问题:线程新建节点采用头插法找不到链表尾部形成死循环

线程A将节点插入下标2的位置,在判null之后,线程被挂起;线程A将节点插入下标2的位置;恢复线程A,节点会覆盖,导致数据丢失

jdk1.8采用了尾插法,解决死循环问题,未解决多线程数据丢失

5、解决数据一致性问题

synchronize锁对象方法:整个对象加synchronize关键字进行上锁。重量级锁,单线程进行读写,限制了并发

1、采用Hashtable;2、调用Collections.synchronizeMap()方法来让HashMap具有多线程能力

  • ConcurrentHashMap,线程安全的、支持高效并发的HashMap

在1.7中,AQS+ReentrantLock

ConcurrentHashMap由Segment+HashEntry,Segment继承自ReentrantLock,一个ConcurrentHashMap里默认有16个Segment数组,1个Segment包含2个HashEntry数组,第二次插入会扩容,是一个链表结构的table

在1.8中,CAS+Synchronized(CAS )

用Node数组+链表+红黑树的数据结构,插入位置没有数据,就直接CAS无锁插入,存在hash冲突,锁住链表或者红黑树的头结点

SET

用于存储无序(存入和取出的顺序不一定相同)元素,必须重写hashCode(),equals()方法。判断省份区分

  • HashSet,通过hashMap实现,适合访问快速的 Set
    调用add(),先调用hashCode()计算hash值,如果hash值相同,调用equals()方法比较

  • TreeSet,底层结构是红黑树排好序的输出

  • linkedHashSet,FIFO插入有序,记录插入时的顺序

    底层数据结构是linkedHashMap。

Queue

队列是只允许在表的前端获取,后端插入的线性表

poll() 获取并移除此队列的头,如果此队列为空,则返回 null

peek() 获取队列的头但不移除此队列的头。如果此队列为空,则返回 null

offer() 将指定的元素插入此队列,队列满了返回false

  • ArrayBlockingQueue:循环数组实现的有界阻塞队列,put()添加,take()删除,通过设置true 开启公平队列

利用一个ReentrantLock来进行加锁操作,入队、出队、获取size()会加锁。enqueue和dequeue 操作会触发将线程的Condition通信,发出notFull.signal(),信号量,通知队列为空或满

offer():尾插入队,可中断,设定等待时间

Poll():取出头部,设定等待时间,超时结束

put():尾插入队,一直阻塞

take():取出头部,一直阻塞

  • linkedBlockingQueue:链表实现的Node有界/无界队列阻塞队列,使用两个ReenterLock来控制并发执行吞吐量要高于ArrayBlockingQueue,take()时,需要获取1、takeLock;2、判断notEmpty,put()时获取1、putLock();2、notFull

offer():尾插入队。抢占锁,加入队列,判断还能不能再加,能的话notFull.signal()唤醒下一个添加线程

Poll():取出头部。抢占锁,拿出队列,判断还能不能再拿,能的话唤醒notFull.signal()唤醒下一个消费线程

  • ConcurrentlinkedQueue:基于单链接节点的无界线程安全队列,先进先出排序

    offer():1、通过tail节点来定位尾节点 2、使用CAS算法能将入队节点设置成尾节点的next节点,如不成功则重新获取尾节点

    Poll():1、通过head节点判空,不为空就用CAS将节点改为null,返回该节点,为空则从新获取head节点

  • SynchronousQueue:一个不存储元素、没有内部容量的阻塞同步队列。使用场景:线程池里,来了新任务就创建线程

并发 多线程

创建线程

1、继承Thread类,重写run()方法

2、实现Runnable接口,重写run()接口。

3、实现Callable接口,重写call(),带返回值,将FutureTask作为返回的承接对象

4、线程池的实现

Executor接口定义execute方法,接收一个Runable实例,执行任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,比如,ExecutorService提供关闭方法shutdown,以及跟踪任务执行状况的Future 方法。

//流打印
list.parallelStream().forEach(System.out::println);
//流计算求和
return list.parallelStream().mapToInt(i -> i).sum();

线程的状态

1、new:使用 new 创建一个线程,仅仅只是在堆中分配了内存空间

2、runnable:线程对象等待 JVM 的调度;running:线程对象获得 JVM 调度

3、blocked:线程暂时停止运行, JVM 不会给线程分配 CPU,进入阻塞状态的唯一前提是在等待获取同步锁

4、waiting:等待状态只能被其他线程唤醒,此时使用的是无参数的 wait() 方法

5、timed waiting:调用了带参数的 wait(long time)或 sleep(long time) 方法

6、terminated:通常称为死亡状态,表示线程终止

线程的方法

1、start():开启一个新的线程来执行用户定义的子任务

2、run():继承Thread类重写run方法,执行任务

3、sleep(long millis):线程休眠,不会释放同步锁。

4、wait():执行到wait(),就释放当前的锁,必须在同步代码块或同步方法中,锁会使线程进入等待状态。

5、notify()/notifyAll():唤醒wait的一个或所有的线程,会放弃同步锁,需和wait()成对使用,必须在同步代码块或同步方法中

6、join():表示这个线程等待另一个线程完成后(死亡)才执行,join 方法被调用之后,线程对象处于阻塞状态。写在哪个线程中,哪个线程阻塞,使得线程之间的并行执行变为串行执行

7、yield():礼让线程,让出 CPU 资源,线程对象进入就绪状态,会优先给更高优先级的线程运行机会

多线程的通信

1、共享内存通信:线程A与线程B 持有的是同一个对象:object,谁抢占资源,谁执行

2、状态通知通信:wait()/notify()

3、管道通信:PipedInputStream和PipedOutputStream

  • 悲观锁:synchronized关键字和Lock的实现类都是悲观锁。在获取数据的时候会先加锁,确保数据不会被别的线程修改。
  • 乐观锁:volatile关键字和atomic包都是CAS,在更新数据前去判断其他线程先更新数据。保证原子操作

volatile:免锁机制,保证共享变量对所有线程的可见性;禁止进行指令重排序

atomic包:利用CAS来实现原子性的自增、自减、加减操作

Synchronized是一个同步关键字,锁的是Java对象头。

锁代码块:monitorenter插入到同步代码块开始位置,monitorexit指令插入到同步代码块结束的位置

锁方法:方法调用指令来读取运行时常量池中的ACC_SYNCHRONIZED标志隐式实现的

Synchronized锁的升级

  • 偏向锁:无竞争,在java对象头和栈帧中记录偏向的锁的threadID,再次获取锁时,比较线程的threadID和对象头中的threadID
  • 轻量级锁:轻度竞争,自旋,替换线程1存储的锁记录,等待获取锁
  • 重量级锁:把除了拥有锁的线程都阻塞,防止CPU空转

锁的排队

  • 公平锁:基于锁内部维护的一个双向链表,每次都是依次从队首请求当前锁,ReenttrantLock

  • 非公平锁:synchronized

  • 可重入锁:可重复调用的锁,ReentrantLock和synchronized

  • 不可重入锁:递归调用就发生死锁。同一线程两次调用lock(),如果不执行unlock(),第二次自旋会产生死锁

ReentrantLock:可重入、互斥、实现了Lock接口的锁,默认为非公平锁

RetrantLock 利用 AQS 的 state 状态来判断资源是否已锁,加锁+1,解锁-1,state=0,获取资源

Synchronized和ReentrantLock的区别

相同点:

ReentrantLock 和 syncronized ,都是可重入锁,都是独占锁

不同点:

1、ReentrantLock,需要 lock() 和unlock()

2、synchronized 是非公平锁,先抢到锁先执行。ReentrantLock能设置ture/fasle来设置公平/非公平锁

3、syncronized可能阻塞,ReentrantLock 有 tryLock方法,超时跳过

4、ReentrantLock可以中断锁

ThreadLocal

线程内部的存储类,各个线程的数据互不干扰。实际存储的数据结构类型是ThreadLocalMap,长度为16的Entry数组。Entry继承了WeakReference。类似hashMap,但是没有 next 引用,采用线性探测的方式

key:ThreadLock对象

value:修改的变量

内存泄露:使用ThreadLocal的set方法之后,插入Entry的Key对象在ThreadLocalMap中是弱引用,value是强引用得不到回收,没有显示的调用remove方法,就有可能发生内存泄露

辅助类
  • CountDownLatch:通过初始值为线程数量的计数器来实现,允许线程一直等待,直到其他线程的操作完后再执行
await()://调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
countDown() //将count值减1
  • Semaphore:控制同时访问特定资源的线程数量,可以设置公平/非公平。设置限流
acquire()     		//获取一个许可
release()					//释放一个许可
tryAcquire()			//尝试获取一个许可
线程池

Worker:

线程池中的线程包装成了一个个 Worker,就是线程池中做任务的线程

线程池状态:

  • RUNNING:接受新的任务,处理等待队列中的任务
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务
  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程
  • TIDYING:所有的任务都销毁了,workCount 为 0。线程池的状态在转换为 TIDYING 状态时,会执行 terminated()
  • TERMINATED:terminated() 方法结束后,线程池的状态就会变成这个
线程池使用:CPU密集型和IO密集型

CPU密集型和IO密集型

线程池提交方式有两种:execute()和submit()

三大方法

Executors.newSingleThreadExecutor();–创建一个线程的线程池
   Executors.newFixedThreadPool(int n);–创建指定线程的线程池
   Executors.newCachedThreadPool();–创建可缓存的线程池

七大参数

int corePoolSize, //核心线程池大小

核心线程池的选择:

  • CPU密集型:线程池线程数可以设置为CPU核数+1,减少线程上下文的切换

  • IO密集型:大部分线程都阻塞,故需要多配置线程数

    参考公式:CPU核数 /(1 - 阻系数);比如8核CPU:8/(1 - 0.9)=80个线程数;阻塞系数在0.8~0.9之间

int maximumPoolSize, //最大核心线程池大小
   long keepAliveTime, //超时了 无调用会释放
   TimeUnit unit, //超时单位
   BlockingQueue workQueue, //阻塞队列
   ThreadFactory threadFactory, //用于创建线程的线程工厂
   RejectedExecutionHandler handler //拒绝策略

四大拒绝策略

AbortPolicy 队列满了,抛异常
  CallerRunsPolicy 哪来的回哪去(一般回主线程)
  DiscardPolicy 队列满了,丢掉任务,不抛异常
  DiscardOldestPolicy 队列满了,尝试和最早线程竞争,不会抛出异常

数据库 数据库三范式

1.第一范式(1NF):列不可再分。
2.第二范式(2NF):属性完全依赖于主键。
3.第三范式(3NF):属性不依赖于其它非主属性 属性直接依赖于主键。

B+Tree

B树:节点存储key和data,叶子结点指针为空。时间复杂度基本

B+树:只有叶子结点存储data

Mysql的B+树:增加了顺序访问指针,优化范围查找

SQL优化

EXPLAIN命令:

MYSQL会在查询上设置一个标记,当执行查询时,显示出执行计划中的每一部分和执行的次序。

Explain Select * From db;

  • 哪些索引可以使用

explain查询计划优化章节,即explain的输出结果Extra字段为Using index时,能够触发索引覆盖

案例:

设计一张退订原因记录表,索引设为主键id,手机号+退订原因+创建时间的联合索引,查询某月或者某几个月时间段数据效率太低。通过EXPLAIN,发现ref中创建时间段没用上,了解到最左前缀原理,新增范围索引年份+月份的parttime列,来进行数据分片查询。

回表

从普通索引无法直接定位行记录,扫码两遍索引树,先通过普通索引定位到主键id,在通过聚集索引定位到具体行记录。出现using index condition就是二级索引回表

查询优化:

slow_query_log打开用慢查询日志,用日志分析工具mysqldumpslow,查看慢查询的次数和sql语句。

InnoDB mysql隐藏列

MySQL使用的B+Tree结构都在经典B+Tree的基础上进行了优化,在B+Tree的每个叶子节点增加一个指向相邻叶子节点的顺序访问指针,优化区间访问性能。

data存的是数据本身。索引也是数据。数据和索引存在一个文件中。

行级锁(row-level):开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;

事务的隔离级别
  • 读未提交(READ UNCOMMITTED) 脏读,读到未提交的数据
  • 读已提交(READ COMMITTED) 不可重复读 事务未提交,可能多次读取数据不一致
  • 可重复读(REPEATABLE READ) 发生幻读 读到别人提交的记录
  • 串行化(SERIALIZABLE) 事务串行

隔离级别导致的可能异常:

脏写:事务A更新NULL为a,B读取到a更新为b,A用 undo log 日志执行回滚,将b改为NULL

不可重复读:事务A读取到a,事务B将a改为b,事务C将a改为c,事务A查询到三次的结果为,a,b,c。update、delete

幻读:事务A范条件索引得到,a、b,事务B insert 后在相同条件中新增c条,事务A再次读取发现a,b,c insert

MySQL的MVCC

ACID,分别为隔离性、一致性、原子性、持久性。在InnoDB中,利用日志恢复技术保证了事务的原子性和持久性,利用并发控制技术,保证了事务之间的隔离性。

多版本并发控制

MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read)。

MVCC是通过保存数据在某个时间点的快照来进行控制的。使用MVCC就是允许同一个数据记录拥有多个不同的版本。然后在查询时通过添加相对应的约束条件,就可以获取用户想要的对应版本的数据。

MVCC过程:

InnoDB的MVCC,是通过在每行纪录后面保存两个隐藏的列来实现的。一个保存了行的创建时间,一个保存了行的过期时间

在可重复读隔离级别下,MVCC具体的操作如下:

1、开始事务

2、记录数据行数据快照到undo log

3、更新数据

4、将undo log写到磁盘

5、将数据写到磁盘

6、提交事务

日志缓存

redo log(重做日志):包括重做日志缓冲(redo log buffer),日志易失;重做日志文件(redo log file),是持久的

记录的是每一个数据页中的更改

undo log(回滚日志):保存了事务发生之前的数据的一个版本,当事务提交之后,undo log 并不能立马被删除,而是放入待清理的链表,由 purge 线程判断是否有其它事务在使用 undo 段中表的上一个事务之前的版本信息。

binlog (二进制日志):存储着每条变更的SQL语句

分库分表

垂直拆分:通过用户的唯一uid做关联。将用户的相关数据拆分成为用户表、账户表、记录表、流水表等相关表

水平拆分:同一张表中的数据拆分到不同的数据库中进行存储、把一张表拆分成 n 多张小表。例如32库1024表格

读写分离:更新数据时,应用将数据写入master主库,主库将数据同步给多个slave从库。查询数据时,选择某个slave节点读取

分片策略:常用的方法有:HASH取模、范围分片、地理位置分片、时间分片等

分布式全局ID选择方案:

公司方案:根据手机号作为路由规则值计算字符串,通过zal分库分表工具类,计算序列值。再用手机号计算的字符串和分库数量分表数量进行运算,最后拼接三个字符串得到全局唯一id

​ 1、支付宝uid作为索引,查询慢

​ 1、redis自增

​ 2、雪花算法 :0+41bit时间戳+机器id+序列号组成

​ 3、Mysql自增主键:与业务方案不符,影响用户id搜索效率

推荐连续自增的主键id,推荐不要使用uuid或者不连续不重复的雪花id

Redis

https://www.cnblogs.com/gtblog/p/13536391.html

单线程的 Redis 为什么这么快:

纯内存操作,不用上下文切换、单线程操作、非阻塞 I/O 多路复用机制

跳表:

String:一个 key 对应一个 value,string 类型的值最大能存储 512MB。做一些复杂的计数功能

Hash:Redis hash 是一个键值(key=>value)对集合适合用于存储对象。做单点登录的时候,存储用户信息

List:简单的字符串列表,按照顺序插入。简单的消息队列、基于 Redis 的分页功能

Set:是 string 类型的无序集合,集合是通过哈希表实现的 做全局去重

zset:也是string类型元素的集合,且不允许重复的成员,每个元素都会关联一个double类型的分数,通过分数来为集合中的成员进行从小到大的排序 排行榜功能

Stream:主要用于消息队列,缺点就是消息无法持久化。

Bitmap位图:基于string定义了一组bit位操作,一串很长的二进制向量,初始默认值都是0,利用hash函数置为1

队列

对于Redis的队列,如果队列没有数据,使用普通的lpop rpush会空轮询,换成blpop

brpush等阻塞出入队操作会更好。

阻塞读取指令在队列中没有数据的时候,会立即进行休眠状态,一旦有数据则立即唤醒取数据,

消息的延迟几乎为0。

内存淘汰机制

**allkeys-lru:**当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key

**volatile-lru:**当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key

Redis 和数据库双写一致性问题

首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

并发竞争

单节点:setnx+lua:

set key value px milliseconds nx:

  1. set命令要用set key value px milliseconds nx;
  2. value要具有唯一性;
  3. 释放锁时要验证value值,不能误解锁;

//获取锁(unique_value可以是UUID等)

SET resource_name unique_value NX PX 30000

// 释放锁(lua脚本中,一定要比较value,防止误解锁)

if redis.call("get",KEYS[1]) == ARGV[1]

then
return redis.call("del",KEYS[1])
else
return 0
end

多节点:RedLock算法

假设有5个完全独立的redis主服务器:

1、获取当前时间戳

2、按照顺序使用相同的key,value获取所有redis服务的锁

3、client通过获取所有能获取锁的时间减去时间戳,小与TTL。至少有3个redis实例成功获取锁

4、锁的有效时间是TTL—获取锁事件5

5、获取锁失败,解锁所有redis实例,在随机时间后重试获取锁,设定次数限制

持久化机制

RDB机制(异步):save, BGSAVE 命令会触发一个操作,父进程执行fork操作创建子进程,遍历hash table,子进程创建RDB数据文件,fork() 可能会非常耗时,会丢失数据

AOF机制:持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,记录数据库状态

集群策略

主从+哨兵模式、Cluster集群

内存淘汰算法

LRU(最近最少使用)、LFU(最不经常使用页置换算法)

缓存穿透

请求穿过了缓存层,直接打到了数据库

缓存穿透:缓存和数据库中都没有

鉴权校验,将key-value对写为key-null

缓存击穿:数据库有,缓存没有

热点key永不失效;加互斥锁,缓存没有数据,就获取锁,读库更新缓存

缓存雪崩:大量key同时过期

过期时间随机;热点key分布在不同数据库中,

BLoom过滤器

bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。穷举法

实现:

1、bitmap:一串很长的二进制向量,初始默认值都是0,利用hash函数置为1

2、Redisson工具

缓存雪崩

给缓存的失效时间、使用互斥锁、双缓存(从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B)

数据不一致问题

采用rehash自动漂移策略,在节点多次上下线之后,也会产生脏数据

缓存降级

可以砍掉某个功能,也可以砍掉某些模块。

缓存限流(热点key)

限流算法有:计数器、漏桶和令牌桶算法。

计数器:使用linkedList来记录滑动窗口的10个格子,每次移动都需要记录当前服务请求的次数。当前访问次数和linkedList中最后一个相差是否超过100,如果超过就需要限流了。

漏桶算法:使用队列来实现

令牌桶算法:存放固定容量令牌(token)的桶,按照固定速率往桶里添加令牌

系统设计

缓存的技术方案分类:

1)实时性比较高的那块数据,比如说库存,销量之类的这种数据,我们采取的实时的缓存+数据库双写的技术方案,双写一致性保障的方案。
2)实时性要求不高的数据,比如说商品的基本信息,等等,我们采取的是三级缓存架构的技术方案,就是说由一个专门的数据生产的服务,去获取整个商品详情页需要的各种数据,经过处理后,将数据放入各级缓存中。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/301498.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号