当前位置: 首页 > news >正文

青岛门户网站建设电子商务网站建设背景

青岛门户网站建设,电子商务网站建设背景,notepad做网站技巧,成都城乡建设局官方网站文章目录 项目介绍1.不动产项目项目难点机器学习算法调研图像提取算法调研数据集-ImageNetXceptionVGGInceptionDensenetMobilenet 系统流程图 2.图书项目技术栈ShiroMybatisMyBatis:Mybatis Plus: 面试问题 Java基础基本数据类型反射接口和抽象类异常代理模式1. 静态代理2. 动… 文章目录 项目介绍1.不动产项目项目难点机器学习算法调研图像提取算法调研数据集-ImageNetXceptionVGGInceptionDensenetMobilenet 系统流程图 2.图书项目技术栈ShiroMybatisMyBatis:Mybatis Plus: 面试问题 Java基础基本数据类型反射接口和抽象类异常代理模式1. 静态代理2. 动态代理2.1 JDK 动态代理机制2.2 CGLIB 动态代理机制2.3 二者对比 代码到运行的过程 Java 集合1. List2. Set3. Queue4. MapHashMapHashMap v.s Hashtable(5点)ConcurrentHashMap v.s Hashtable(2点) Java并发JMM(Java内存模型)什么是JMM为啥需要JMMJava内存区域和 JMM 有什么区别JMM如何抽象线程和主内存之间的关系happens-before 原则并发编程三个重要特性 线程1. 进线程区别2. 多线程3. 线程安全4. 线程通信5. 创建线程的三种方式6. 生命周期状态7. 上下文切换8. sleep()和wait() volatile如何保证变量的可见性如何禁止指令重排序volatile 可以保证原子性么 乐观锁/悲观锁1. 悲观锁2. 乐观锁 synchronized1. 使用场景2. 底层原理synchronized 同步语句块(monitor两个指令)synchronized 修饰方法(ACC_SYNCHRONIZED 标识) 3. 和volatile区别 ReentrantLock和synchronized区别 AQS(抽象队列)1. 原理2. Semaphore作用3. Semaphore原理4. CountDownLatch ThreadLocal1. 作用场景2. 原理实现3. 内存泄露问题 线程池1. 线程池作用2. 创建线程池方法3. 线程池饱和策略4. 常用阻塞队列5. 线程池处理任务流程6. 线程池大小7. 线程池参数 Future JVM一、内存结构运行时数据区域线程私有程序计数器俩栈程序计数器很小的内存空间当前线程的**行号**指示器虚拟机栈保存方法的局部变量、操作数、动态链接、方法返回地址本地方法栈线程共享堆方法区直接内存堆运行时数据区域保存所有类的实例和数组字符串常量池方法区存储加载的类信息、class/method/field等元数据、常量、静态变量运行时常量池直接内存非运行时数据区域 HotSpot虚拟机对象1. 对象创建默写2. 对象的内存布局3. 对象的访问定位 二、类加载机制三、垃圾回收GC1. 引用类型2. 垃圾收集3. 垃圾回收算法内存回收方法论4. 垃圾收集器内存回收具体实现**Serial单 新 复制****ParNew多 新 复制****Parallel Scavenge多 新 复制****Serial Old单 老 整理****Parallel Old多 老 整理****CMS多 老 清除****G1多 新/老 整理复制**总结 5. GC调优 四、JVM调优JVM参数总结OOM排查 1、堆内存不足原因解决方法2、永久代/元空间溢出原因解决方法3、方法栈溢出原因解决方法4、GC overhead limit exceeded原因解决方法 设计模式单例模式工厂模式责任链模式Jdk中的设计模式 MySQL一、存储引擎二、索引1. 优缺点2. 底层数据结构BB树 3. 索引类型 三、事务1. ACID2. 并发事务问题3. 并发事务控制方式锁MVCC RC和RR下 MVCC 的差异4. 隔离级别 四、锁机制1. 行级锁、表级锁2. 共享锁、排它锁3. 意向锁4. 当前读、快照读5. 自增锁(了解) 五、日志1. undo log回滚为什么需要Buffer pool2. redo log重做刷盘时机3. binlog归档 Redis为什么用redis做mysql缓存数据结构常见数据结构底层数据结构特殊数据结构 线程模型1. 单线程模式2. 单线程 v.s 多线程 IO多路复用 缓存设计缓存读写策略1. 旁路缓存模式2. 读写穿透3. 写回策略 数据库和缓存一致性1. 先更db还是先更缓存2. 先更db还是先删缓存先更db再删缓存cache aside 旁路缓存策略 持久化机制RDB快照AOF日志怎么选择RDB还是AOF 生产问题缓存穿透缓存击穿缓存雪崩 过期删除内存淘汰策略过期删除内存淘汰策略 数据结构排序算法快速排序 计算机网络输入url到页面展示HTTP(应用层)基本概念GET和POSTHTTP缓存技术HTTPS 1.0 1.1 2.0HTTPS TCP v.s UDP(传输层)七点区别TCP为什么可靠三次握手建立TCP连接四次挥手断开TCP连接TCP Keepalive 和 HTTP Keep-Alive DNS域名-IP映射 操作系统内核态和用户态进程和线程进程通信7种方式线程同步的4种方式 内存管理虚拟内存管理方式 死锁 Spring概述Spring,Spring MVC,Spring Boot 之间什么关系? IoC(控制反转)Component 和 Bean 的区别是什么Autowired 和 Resource 的区别是什么Bean的生命周期 AoP(面向切面编程)自动装配原理Sping中的设计模式注解常用注解事务 Transactional**1. Spring事务介绍****2. spring事务管理方式****3. Spring事务管理接口**4. 事务属性5. Transactional注解使用 分布式RPCZooKeeper 特点常见RPC框架Dubbo 分布式锁基于Redis实现分布式锁基于ZooKeeper实现分布式锁 Linux常用命令查询进程占用内存 银行查询进程占用内存 银行 项目介绍 1.不动产项目 项目难点 模型准确率20%出头-找原因数据量不足学习不充分模型本身的问题全选的基于树的模型针对数据扩充数据集包装法筛特征针对模型增加模型选型岭回归stacking boosting bagging集成网格法调参结果准确率90% 怎么部署到服务器上的 docker 前端 本地vue项目运行npm run build生成dist文件夹然后通过Xftp6传输到远程服务器同时还要将相应的dockerfile和default.conf文件传输过去并且因为用的是https加密所以传过加密证书。在远程docker进入相应的前端文件夹执行docker build -t gangoffivecloud .命令 即可将dist打包成一个docker镜像名为gangoffivecloud。 然后执行docker run -p 8081:8085 -d --name gangoffivecloud gangoffivecloud 即可将名为gangoffivecloud的镜像运行到名为gangoffivecloud的容器里。这里前端就已经开始在服务器上运行了。 后端的话将springboots打包成一个GangOfFive-0.0.1-SNAPSHOT.jar的jar文件这里我用的是idea 然后通过Xftp6把jar包和相应dockerfile传过去。在服务器处进入相应的后端文件夹运行docker build -t gangoffive .命令就可以得到一个名为gangoffive的docker镜像然后执行命令docker run -p 8085:8085 -d --name gangoffive gangoffive便可将名gangoffive的docker镜像运行在名为gangoffive的容器里。此时后端也在服务器里运行了。 mysql docker pull mysql:latest 下载mysql数据库最新镜像然后 docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 mysql 将mysql镜像运行到名为mysql-test的容器里并且初始root管理员密码为123456 后端yml里怎么写连接远程数据库 机器学习算法调研 12种基学习器评价指标为RMSE、MAE、MAPE和R2最终选定Catboost和LightGBM 图像提取算法调研 数据集-ImageNet Xception 论文Xception: Deep Learning with Depthwise Separable Convolutions (CVPR 2017) 源码Keras开源代码 VGG Very Deep Convolutional Networks for Large-Scale Image Recognition Inception Densenet Mobilenet [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WHQGEfqE-1686815973681)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20230419190536360.png)] 系统流程图 2.图书项目 JPA和Mybatis区别 技术栈 前端服务器Nginx后端服务器Tomcat开发前端内容时可以把前端的请求通过前端服务器转发给后端称为反向代理 用户信息明文存储在数据库中不安全 Shiro 三大概念 Subject负责存储与修改当前用户的信息和状态SecurityManager安全相关的操作实际上是由她管理的Realms负责从数据源中获取数据并加工后传给 SecurityManager 四大功能 Authentication认证Authorization授权Session Management会话管理Cryptography加密 Mybatis MyBatis: 1所有SQL语句全部自己写 2手动解析实体关系映射转换为MyBatis内部对象注入容器 3不支持Lambda形式调用 Mybatis Plus: 1强大的条件构造器,满足各类使用需求 2内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作 3支持Lambda形式调用 4提供了基本的CRUD功能,连SQL语句都不需要编写 5自动解析实体关系映射转换为MyBatis内部对象注入容器 Mybatis Plus 加载流程 1、加载配置文件(数据源以及映射文件)解析配置文件生成ConfigurationMapperedStatement 2、通过使用Configuration对象创建sqlSessionFactory用来生成SqlSeesion 3、sqlSession通过调用api或者mapper接口传入statementId找到对应的MapperedStatement来调用执行sql 4、通过Executor核心器负责sql动态语句的生成和查询缓存的维护来进行sql的参数转换动态sql的拼接生成Statement对象 5、借助于MapperedStatement来访问数据库它里面封装了sql语句的相关信息以及返回结果信息 面试问题 Spring Security和Shiro的区别 Shiro比Spring更容易使用实现和理解Shiro 功能强大、且 简单、灵活Shiro是 Apache 下的项目比较可靠不跟任何的框架或者容器绑定可以独立运行 项目中redis用什么客户端部署 Java 访问 Redis 主要是通过 Jedis 和 Lettuce 两种由不同团队开发的客户端提供访问、操作所需的 APIJedis 比较原生Lettuce 提供的能力更加全面。 本项目用Spring Data RedisSpring Data Redis是在 Lettuce 的基础上做了一些封装与 Spring 生态更加贴合使用起来也更简便。 java怎么连接数据库 配置maven依赖-配置数据库application.properties 项目还有哪些不足之处 后端提升响应速度 仅就软件来说努力的方向有三个一是 代码二是 “技术应用”三是 “优化设计”。 系统安全问题 sql注入的原因及解决 原因程序开发过程中不注意书写规范对sql语句和关键字未进行过滤导致客户端可以通过全局变量get或者post提交sql语句到服务器端正常运行;解决预防SQL注入大概有两种思路 一是提升对输入內容的查验二是应用参数化语句来传递客户输入的內容。 过滤掉一些常见的数据库关键字select、insert、update、delete、and等对于常用的方法加以封装避免直接暴露sql语句开启安全模式safe_modeon SQL中**# 与$ 的区别** #将传入的数据都当成一个字符串在很大程度上能够防止sql注入 将传入的数据直接显示生成在 s q l 中 ∗ ∗ 将传入的数据直接显示生成在sql中** 将传入的数据直接显示生成在sql中∗∗方式无法防止sql注入**一般能用#的就别用$ 用户注册登录流程 用户管理 用户信息 显示用户的基本信息昵称、联系方式、角色、部门等 组织架构 显示、配置增删改组织架构一般为树结构 用户操作 为用户分配角色多对多、组织架构多对多删除用户 用户黑白名单 对特殊用户进行特别控制 角色管理 角色信息 显示角色的基本信息名称、权限等 角色操作 根据需要增删角色、为角色分配权限多对多按不同粒度分配并实现权限的互斥性检验 权限管理菜单、功能、数据 开发要点 用户、角色、权限、组织架构表结构设计用户身份验证、授权、会话管理用户信息的加密存储不同粒度权限的具体实现 菜单权限 使用 “全局前置守卫”router.beforeEach在导航触发时向后端发送一个包含用户信息的请求后端查询数据库中该用户可以访问的菜单也就是 vue 的路由信息并返回前端把接收到的数据添加到路由里并根据新的路由表动态渲染出导航栏使不同用户登录后看到不同的菜单。同时由于路由表也是按需加载的所以用户也无法通过 URL 访问没有权限的页面 功能权限 不管三七二十一前端组件全部加载出来但需要调用后端接口时进行判断如果无权限则弹出相应提示。这种适合对按钮的控制图表直接不加载数据就显得不是很友好 数据权限 可访问性控制可访问性可以针对表、字段或满足某些条件的数据。针对表、字段的控制主要依靠在业务逻辑执行前进行判断比如在调用对收支信息表的查询前判断当前用户是否具有财务权限。而访问特定数据可以直接通过 SQL 语句WHERE 条件来实现比如当前用户只能查询出自身拥有的书籍就可以通过类似 SELECT * FROM book WHERE uid #{uid}的语句来实现。数据量控制常见的比如一天内普通用户只能访问 2000 条数据公众号好像就有这个限制可以通过引入计数机制来实现调用接口或执行业务逻辑时先进行判断同时限制本次查询的最大数量。此外还有需要对一次的访问量进行控制、对某段时间能够处理的数据量进行控制等应用场景等等 用户加密加盐 用户注册时输入用户名密码明文向后台发送请求后台将密码加上随机生成的盐并 hash再将 hash 后的值作为密码存入数据库盐也作为单独的字段存起来用户登录时输入用户名密码明文向后台发送请求后台根据用户名查询出盐和密码组合并 hash将得到的值与数据库中存储的密码比对若一致则通过验证 认证方案 session许多语言在网络编程模块都会实现会话机制即 session。利用 session我们可以管理用户状态比如控制会话存在时间在会话中保存属性等。其作用方式通常如下 服务器接收到第一个请求时生成 session 对象并通过响应头告诉客户端在 cookie 中放入 sessionId客户端之后发送请求时会带上包含 sessionId 的 cookie服务器通过 sessionId 获取 session 进而得到当前用户的状态是否登录等信息 也就是说客户端只需要在登录的时候发送一次用户名密码此后只需要在发送请求时带上 sessionId服务器就可以验证用户是否登录了。token虽然 session 能够比较全面地管理用户状态但这种方式毕竟占用了较多服务器资源所以有人想出了一种无需在服务器端保存用户状态称为 “无状态”的方案即使用 token令牌来做验证。一个真正的 token 本身是携带了一些信息的比如用户 id、过期时间等这些信息通过签名算法防止伪造也可以使用加密算法进一步提高安全性但一般没有人会在 token 里存储密码所以不加密也无所谓反正被截获了结果都一样。一般会用 base64 编个码方便传输在 web 领域最常见的 token 解决方案是 JWTJSON Web Token思路 用户使用用户名密码登录服务器验证通过后根据用户名或用户 id 等按照预先设置的算法生成 token其中也可以封装其它信息并将 token 返回给客户端可以设置到客户端的 cookie 中也可以作为 response body客户端接收到 token并在之后发送请求时带上它利用 cookie、作为请求头或作为参数均可 服务器对 token 进行解密、验证 token 的优势是无需服务器存储 延时双删先清除缓存在更新数据库后等一段时间再去第二次执行删除操作。 项目中学到了什么 技术方面 沟通交流 项目难点如何解决 Java基础 Java 程序从源代码到运行的过程 基本数据类型 反射 反射就是把java类中的各种成分映射成一个个的Java对象主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法你还可以调用这些方法和属性。 Spring/Spring Boot、MyBatis 这些框架中大量使用了动态代理而动态代理的实现也依赖反射。 优缺点 优点 增加程序的灵活性避免将程序写死到代码里代码简洁提高代码的复用率外部调用方便对于任意一个类都能够知道这个类的所有属性和方法对于任意一个对象都能够调用它的任意一个方法 缺点 性能问题使用反射基本上是一种解释操作用于字段和方法接入时要远慢于直接代码。使用反射会模糊程序内部逻辑程序人员希望在源代码中看到程序的逻辑反射等绕过了源代码的技术因而会带来维护问题。反射代码比相应的直接代码更复杂。安全限制使用反射技术要求程序必须在一个没有安全限制的环境中运行。内部暴露由于反射允许代码执行一些在正常情况下不被允许的操作比如访问私有的属性和方法所以使用反射可能会导致意料之外的副作用代码有功能上的错误降低可移植性。反射代码破坏了抽象性因此当平台发生改变的时候代码的行为就有可能也随着变化。 场景 接口和抽象类 抽象类abstract a、抽象类不能被实例化只能被继承 b、包含抽象方法的一定是抽象类但是抽象类不一定含有抽象方法 c、抽象类中的抽象方法的修饰符只能为public或者protected默认为public d、一个子类继承一个抽象类则子类必须实现父类抽象方法否则子类也必须定义为抽象类 e、抽象类可以包含属性、方法、构造方法但是构造方法不能用于实例化主要用途是被子类调用。 接口interface a、接口可以包含变量、方法变量被隐士指定为public static final方法被隐士指定为public abstractJDK1.8之前b、接口支持多继承即一个接口可以extends多个接口间接的解决了Java中类的单继承问题c、一个类可以实现多个接口d、JDK1.8中对接口增加了新的特性1、默认方法default methodJDK 1.8允许给接口添加非抽象的方法实现但必须使用default关键字修饰定义了default的方法可以不被实现子类所实现但只能被实现子类的对象调用如果子类实现了多个接口并且这些接口包含一样的默认方法则子类必须重写默认方法2、静态方法static methodJDK 1.8中允许使用static关键字修饰一个方法并提供实现称为接口静态方法。接口静态方法只能通过接口调用接口名.静态方法名。 异常 **异常类层次结构图**所有异常的祖先都是Throwable 类 Checked Exception 和 Unchecked Exception 有什么区别 Checked Exception编译过程中若没有被 catch或者throws 关键字处理的话就没办法通过编译。Unchecked Exception不处理也可以正常通过编译。 try-catch-finally-return执行顺序 不管是否有异常产生finally块中代码都会执行当try和catch中有return语句时finally块仍然会执行finally是在return后面的表达式运算执行的所以函数返回值在finally执行前确定的无论finally中的代码怎么样返回的值都不会改变仍然是之前return语句中保存的值finally中最好不要包含return否则程序会提前退出返回值不是try或catch中保存的返回值 public static int getInt() {int a 10;try {System.out.println(a / 0);a 20;} catch (ArithmeticException e) {a 30;return a;/** return a 在程序执行到这一步的时候这里不是return a 而是 return 30这个返回路径就形成了* 但是呢它发现后面还有finally所以继续执行finally的内容a40* 再次回到以前的路径,继续走return 30形成返回路径之后这里的a就不是a变量了而是常量30*/} finally {a 40;}return a; } //结果是30代理模式 1. 静态代理 针对每个目标类都单独创建一个代理类 2. 动态代理 从 JVM 角度来说动态代理是在运行时动态生成类字节码并加载到 JVM 中的。 Spring AOP、RPC框架的实现依赖动态代理 2.1 JDK 动态代理机制 InvocationHandler 接口和 Proxy 类是核心 2.2 CGLIB 动态代理机制 JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题我们可以用 CGLIB 动态代理机制来避免。在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。 2.3 二者对比 灵活性 动态代理更加灵活不需要必须实现接口可以直接代理实现类并且可以不需要针对每个目标类都创建一个代理类。另外静态代理中接口一旦新增加方法目标对象和代理对象都要进行修改这是非常麻烦的JVM 层面 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码并加载到 JVM 中的。 代码到运行的过程 编译 在Java中指将**.java**文件转化为 .class文件字节码文件的过程。其中这个字节码文件真正的实现了跨平台、跨语言。因为JVM里运行的就是.class文件只要符合这个格式就能运行。所以在任何平台用任何语言只要你能把程序编译成字节码文件就能在JVM里运行。 加载 类加载器会在指定的classpath中找到.class这些文件然后读取字节流中的数据将其存储在JVM方法区 根据.class的信息建立一个Class对象作为运行时访问这个类的各种数据的接口一般也在方法区。 验证格式、语义等 为类的静态变量分配内存并设为JVM默认的初值对于非静态的变量则不会为它们分配内存。 静态变量的初值为JVM默认的初值而不是我们在程序中设定的初值。 字节码文件中存放的部分方法、字段等的符号引用可以解析为其在内存中的直接引用无需等到运行时解析。 此时执行引擎会调用()方法对静态字段进行代码中编写的初始化操作。 执行 引擎寻找main方法执行其中字节码指令对象实例会被放进JVM的java堆一个线程产生一个java栈当运行到一个方法就创建一个栈帧包含局部变量表、操作栈、方法返回值将它入栈方法执行结束出栈。 Java 集合 Java集合两大接口 Collection接口主要用于存放单一元素Map 接口主要用于存放键值对 1. List List和ArrayList的区别 List是一个接口而ArrayList是List接口的一个实现类 ArrayList和LinkedList区别 2. Set 3. Queue 4. Map Map常见实现类 底层线程安全 HashMap 使用ArrayList、HashMap,需要线程安全怎么办呢 Collections.synchronizedList(list); Collections.synchronizedMap(m); 底层使用synchronized代码块锁虽然也是锁住了所有的代码但是锁在方法里边并所在方法外边性能可以理解 构造方法四种 HashMap有几个构造方法但最主要的就是指定初始值大小和负载因子的大小。如果我们不指定默认HashMap的大小为16负载因子的大小为0.75在HashMap里用的是**位运算((n - 1) hash)**来代替取模能够更加高效地算出该元素所在的位置。为什么HashMap的大小只能是2次幂因为只有大小为2次幂时才能合理用位运算替代取模。而负载因子的大小决定着哈希表的扩容和哈希冲突。比如现在我默认的HashMap大小为16负载因子为0.75这意味着数组最多只能放12个元素一旦超过12个元素则哈希表需要扩容。 put()方法 首先对key做hash运算计算出该key所在的index。如果没碰撞直接放到数组中如果碰撞了需要判断目前数据结构是链表还是红黑树根据不同的情况来进行插入。假设key是相同的则替换到原来的值。最后判断哈希表是否满了(当前哈希表大小*负载因子如果满了则扩容 public V put(K key, V value) {return putVal(hash(key), key, value, false, true); }final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {NodeK,V[] tab; NodeK,V p; int n, i;// table未初始化或者长度为0进行扩容if ((tab table) null || (n tab.length) 0)n (tab resize()).length;// (n - 1) hash 确定元素存放在哪个桶中桶为空新生成结点放入桶中(此时这个结点是放在数组中)if ((p tab[i (n - 1) hash]) null)tab[i] newNode(hash, key, value, null);// 桶中已经存在元素处理hash冲突else {NodeK,V e; K k;//快速判断第一个节点table[i]的key是否与插入的key一样若相同就直接使用插入的值p替换掉旧的值e。if (p.hash hash ((k p.key) key || (key ! null key.equals(k))))e p;// 判断插入的是否是红黑树节点else if (p instanceof TreeNode)// 放入树中e ((TreeNodeK,V)p).putTreeVal(this, tab, hash, key, value);// 不是红黑树节点则说明为链表结点else {// 在链表最末插入结点for (int binCount 0; ; binCount) {// 到达链表的尾部if ((e p.next) null) {// 在尾部插入新结点p.next newNode(hash, key, value, null);// 结点数量达到阈值(默认为 8 )执行 treeifyBin 方法// 这个方法会根据 HashMap 数组来决定是否转换为红黑树。// 只有当数组长度大于或者等于 64 的情况下才会执行转换红黑树操作以减少搜索时间。否则就是只是对数组扩容。if (binCount TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);// 跳出循环break;}// 判断链表中结点的key值与插入的元素的key值是否相等if (e.hash hash ((k e.key) key || (key ! null key.equals(k))))// 相等跳出循环break;// 用于遍历桶中的链表与前面的e p.next组合可以遍历链表p e;}}// 表示在桶中找到key值、hash值与插入元素相等的结点if (e ! null) {// 记录e的valueV oldValue e.value;// onlyIfAbsent为false或者旧值为nullif (!onlyIfAbsent || oldValue null)//用新值替换旧值e.value value;// 访问后回调afterNodeAccess(e);// 返回旧值return oldValue;}}// 结构性修改modCount;// 实际大小大于阈值则扩容if (size threshold)resize();// 插入后回调afterNodeInsertion(evict);return null; }get()方法 对key做hash运算计算出该key所在的index然后判断是否有hash冲突假设没有冲突直接返回假设有冲突则判断当前数据结构是链表还是红黑树分别从不同的数据结构中取出。 HashMap v.s Hashtable(5点) ConcurrentHashMap v.s Hashtable(2点) 底层数据结构不同 实现线程安全方式不同 Java并发 JMM(Java内存模型) 什么是JMM Java 定义的并发编程相关的一组规范除了抽象了线程和主内存之间的关系之外其还规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范主要目的是为了简化多线程编程增强程序可移植性的。 为啥需要JMM 并发编程下CPU多级缓存和指令重排会导致程序运行出现一些问题JMM定义一些规范解决这些问题。JMM 屏蔽掉各种硬件和操作系统的内存访问差异以实现让java程序在各种平台下都能达到一致的并发效果。 Java内存区域和 JMM 有什么区别 JVM 内存结构和 Java 虚拟机的运行时区域相关定义了 JVM 在运行时如何分区存储程序数据就比如说堆主要用于存放对象实例。Java 内存模型和 Java 的并发编程相关抽象了线程和主内存之间的关系就比如说线程之间的共享变量必须存储在主内存中规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范其主要目的是为了简化多线程编程增强程序可移植性的。 JMM如何抽象线程和主内存之间的关系 Java内存模型不仅仅是JVM内存分区调用栈和本地变量存放在线程栈上对象存放在堆上。 主内存 本地内存 主内存所有线程创建的实例对象都放主内存本地内存每个线程都有一个本地内存存储共享变量的副本本地内存时JMM抽象出来的概念 JMM示意图每个线程有个本地内存放副本共享变量放主内存中 从示意图看线程1和2咋通信 线程1本地内存中修改过的共享变量副本值–(同步)–主内存线程2到主内存中读取对应共享变量的值 多线程下可能出现的线程安全问题 when 线程1修改共享变量线程2读取同一个共享变量线程2读取的是修改前的值还是修改后的不确定因为线程1和2都是先将共享变量 主内存–(拷贝)–对应线程工作内存 SoJMM定义了8种同步操作一些同步规则规定一个变量如何从工作内存同步到主内存 同步操作lock、unlock、read、load、use、assign、store、write happens-before 原则 程序员追求易于理解和编程的强内存模型编译器和处理器追求较少约束的弱内存模型 happens-before 设计思想 编译器和处理器的约束尽可能少-只要不改变程序的执行结果编译器和处理器怎么进行重排序都行*比如两个赋值语句*对改变程序执行结果的重排序编译器和处理器必须禁止*比如赋完值以后再用这个值* 和 JMM 的关系 并发编程三个重要特性 原子性 含义一次操作或者多次操作要么所有的操作全部都得到执行并且不会受到任何因素的干扰而中断要么都不执行。 Java实现synchronized 、各种 Lock 以及各种原子类 synchronized 和各种 Lock 可以保证任一时刻只有一个线程访问该代码块因此可以保障原子性。各种原子类是利用 CAS (compare and swap) 操作可能也会用到 volatile或者final关键字来保证原子操作。 可见性 含义一个线程对共享变量进行修改另外的线程立即可以看到修改后的最新值。 Java实现synchronized 、volatile 以及各种 Lock 有序性 含义代码的执行顺序未必就是编写代码时候的顺序。 Java实现volatile 关键字可以禁止指令进行重排序优化 线程 什么是线程 比进程更小的执行单位线程切换工作时负担比进程小得多 进程和线程的区别 进程可以有多个线程同类的多个线程共享进程的堆和方法区资源每个线程有自己的程序计数器、虚拟机栈和本地方法栈各进程是独立的同一进程中的线程可能会互相影响线程执行开销小但不利于资源的管理和保护进程相反 为啥程序计数器是私有的 为了线程切换后能恢复到正确的执行位置 为啥虚拟机栈和本地方法栈私有 为了保证线程中的局部变量不被别的线程访问到 堆和方法区了解 堆是进程中最大的一块内存主要用于存放新创建的对象 1. 进线程区别 进程是系统进行资源分配和调度的独立单位每一个进程都有它自己的内存空间和系统资源进程实现多处理机环境下的进程调度分派切换时都需要花费较大的时间和空间开销为了提高系统的执行效率减少处理机的空转时间和调度切换的时间以及便于系统管理所以有了线程线程取代了进程了调度的基本功能简单来说进程作为资源分配的基本单位线程作为资源调度的基本单位1.从资源角度进程是系统资源分配的最小单元线程是cpu分配的最小单元。 2.从从属关系来看一个进程可以包含一个或多个线程。 3.从切换的角度进程间切换的代价高线程间切换的代价低 4.从jvm的角度进程内的线程会共享进程的 堆 方法区线程独享 虚拟机栈 本地方法栈 程序计数器。 2. 多线程 为什么使用多线程 提高资源利用效率总体上 计算机底层线程切换调度成本小于进程多核CPU-多个线程可以同时运行减少上下文切换开销互连网发展趋势多线程是高并发系统的基础可以提高系统整体的并发能力和性能 计算机底层 单核时代提高单进程利用CPU和IO系统的效率一个线程IO阻塞其他线程还能用CPU多核时代提高进程利用多核CPU的能力 多线程带来的问题 内存泄露、死锁、线程不安全 实际应用 要跑一个定时任务该任务的链路执行时间和过程都非常长我这边就用一个线程池将该定时任务的请求进行处理。这样做的好处就是可以及时返回结果给调用方能够提高系统的吞吐量。 3. 线程安全 什么是线程安全 在Java世界里边所谓线程安全就是多个线程去执行某类这个类始终能表现出正确的行为那么这个类就是线程安全的。 怎么解决线程安全问题 其实大部分时间我们在代码里边都没有显式去处理线程安全问题因为这大部分都由框架所做了Tomcat、Druid、SpringMVC等等 解决线程安全问题的思路有以下 能不能保证操作的原子性考虑atomic包下的类够不够我们使用。能不能保证操作的可见性考虑volatile关键字够不够我们使用如果涉及到对线程的控制比如一次能使用多少个线程当前线程触发的条件是否依赖其他线程的结果考虑CountDownLatch/Semaphore等等。如果是集合考虑java.util.concurrent包下的集合类。如果synchronized无法满足考虑lock包下的类盲目使用会影响程序性能 总的来说就是先判断有没有线程安全问题如果存在则根据具体的情况去判断使用什么方式去处理线程安全的问题。 死锁 死锁原因当前线程拥有其他线程需要的资源当前线程等待其他线程已拥有的资源都不放弃自己拥有的资源。避免死锁 固定加锁的顺序比如我们可以使用Hash值的大小来确定加锁的先后尽可能缩减加锁的范围等到操作共享变量的时候才加锁。使用可释放的定时锁一段时间申请不到锁的权限了直接释放掉 4. 线程通信 共享内存线程之间共享程序的公共状态线程之间通过读-写内存中的公共状态来隐式通信。 volatile共享内存 消息传递线程之间没有公共的状态线程之间必须通过明确的发送信息来显示的进行通信。 wait/notify等待通知方式 join方式 管道流 管道输入/输出流的形式 5. 创建线程的三种方式 1、继承Thread类 重写run方法start()启动 class MyThread extends Thread{Overridepublic void run(){System.out.println(这是重写的run方法也叫执行体);System.out.println(线程号 currentThread().getName());} }public class Test{public static void main(String[] args) throws Exception{Thread t1 new MyThread();t1.start();} }优点简单,访问当前现线程直接使用currentThread()缺点继承Thread类无法继承其他类 2、实现Runable接口 class MyThread implements Runable{Overridepublic void run(){System.out.println(这是重写的run方法也叫执行体);System.out.println(线程号 Thread.currentThread().getName());} }public class Test{public static void main(String[] args) throws Exception{MyThread myThread new MyThread();Thread t1 new Thread(myThread);t1.start();} }优点可以继承别的类多个线程共享一个对象适合处理同一份资源的情况缺点访问当前线程需要使用Thread.currentThread() 3、Callable接口: 实现Callble接口重写call()方法作为执行体创建实现类的实例用FutureTask包装使用FutureTask对象作为Thread对象创建线程使用FutureTask对象的get()方法获得子线程执行结束后的返回值 class MyThread implements Callable{Overridepublic Object call() throws Exception{System.out.println(线程号 Thread.currentThread().getName());return 10;} }public class Test{public static void main(String[] args) throws Exception{Callable callable new MyThread();FutureTask task new FutureTask(callable);new Thread(task).start();System.out.println(task.get());Thread.sleep(10);//等待线程执行结束//task.get() 获取call()的返回值。若调用时call()方法未返回则阻塞线程等待返回值//get的传入参数为等待时间超时抛出超时异常传入参数为空时则不设超时一直等待System.out.println(task.get(100L, TimeUnit.MILLSECONDS));} }Runnable和Callable的区别 Callable规定的方法是call(),Runnable规定的方法是run().Callable的任务执行后可返回值而Runnable的任务是不能返回值得call方法可以抛出异常run方法不可以因为run方法本身没有抛出异常所以自定义的线程类在重写run的时候也无法抛出异常运行Callable任务可以拿到一个Future对象表示异步计算的结果。它提供了检查计算是否完成的方法以等待计算的完成并检索计算的结果。通过Future对象可以了解任务执行情况可取消任务的执行还可获取执行结果。 start()与run()的区别 start用于启动线程线程处于就绪状态直到得到CPU时间片才运行再自动执行run方法run方法只是类的普通方法直接调用相当于只有主线程一个线程 sleep() 与 wait() 与 join() 与 yield(): 作用sleep()与wait()都可以暂停线程执行 sleep线程让出CPU资源不会释放锁wait线程让出CPU 释放锁与notify(), notifyAll() 一起使用 基于哨兵思想检查特定条件直到满足才继续进行 需要监视器监视当前线程如synchronized Condition类 notify随机唤醒单个线程notifyAll唤醒所有线程 yield暂停当前线程给其他具有相同优先级的线程包括自己运行的机会join让主线程等待子线程结束之后在结束 比如需要子线程的运行结果的时候由子线程调用 sleep 和 yield 是Thread的静态方法 join是线程对象调用 wait, notify, notifyAll 是Object类的方法所有对象都可以调用。 6. 生命周期状态 六个状态 NEW: 初始状态线程被创建出来但没有被调用 start() 。 RUNNABLE: 运行状态线程被调用了 start()等待运行的状态。 BLOCKED 阻塞状态需要等待锁释放。 WAITING等待状态表示该线程需要等待其他线程做出一些特定动作通知或中断。 TIME_WAITING超时等待状态可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。 TERMINATED终止状态表示该线程已经运行完毕。 7. 上下文切换 什么是上下文 线程执行中自己的运行条件和状态比如程序计数器、栈信息 为什么会上下文切换 主动出让CPU时间片用完调用了阻塞类型的系统中断被终止或结束运行 什么是上下文切换 线程切换意味着要保存当前线程的上下文留着线程下次占用CPU的时候恢复现场并加载下一个将要占用CPU的线程上下文所以要上下文切换 切换的时候干啥 每次要保存信息恢复信息占用CPU所以不能频繁切换 8. sleep()和wait() 二者异同 为什么wait()不定义在Thread中sleep()定义在Thread中 wait() 是让获得对象锁的线程实现等待会自动释放当前线程占有的对象锁。 每个对象(Object)都拥有对象锁既然要释放当前线程占有的对象锁并让其进入WAITING状态操作的对象自然是Object而不是当前线程Thread sleep() 是让当前线程暂停执行不涉及到对象类也不需要获得对象锁 可以直接调用Theard类的run方法吗 调用 start() 方法方可启动线程并使线程进入就绪状态直接执行 run() 方法的话不会以多线程的方式执行。 volatile 如何保证变量的可见性 用 **volatile**关键字如果我们将变量声明为 volatile 这就指示 JVM这个变量是共享且不稳定的每次使用它都到主存中进行读取。volatile 关键字能保证数据的可见性但不能保证数据的原子性。synchronized 关键字两者都能保证。 如何禁止指令重排序 用 **volatile**关键字 如果我们将变量声明为 volatile 在对这个变量进行读写操作的时候会通过插入特定的 内存屏障 的方式来禁止指令重排序。 volatile使用场景双重校验锁实现对象单例线程安全 public class Singleton {private volatile static Singleton uniqueInstance;//volatile修饰private Singleton() {}public static Singleton getUniqueInstance() {if (uniqueInstance null) {//没有实例化过才进入加锁代码synchronized (Singleton.class) {//类对象加锁if (uniqueInstance null) {/*这句话分三步进行:1.为 uniqueInstance 分配内存空间2.初始化 uniqueInstance3.将 uniqueInstance 指向分配的内存地址JVM指令重排,可能变成1-3-2,因此需要volatile*/uniqueInstance new Singleton();}}}return uniqueInstance;} }volatile 可以保证原子性么 不能只能保证可见性和有序性怎么改进 synchronized、Lock或者AtomicInteger 乐观锁/悲观锁 1. 悲观锁 假设最坏的情况认为共享资源每次被访问的时候就会出现问题每次获取资源都会上锁共享资源每次只给一个线程使用其它线程阻塞用完后再把资源转让给其它线程。实现synchronized和ReentrantLock等独占锁就是悲观锁思想的实现导致的问题 高并发场景下锁竞争-线程阻塞-大量-系统上下文切换-增加系统开销死锁 2. 乐观锁 假设最好的情况认为共享资源每次被访问的时候都不会出现问题只有提交修改的时候验证对应资源是否被其他线程修改了版本号 or CAS实现java.util.concurrent.atomic包下面的原子变量类比如AtomicInteger、LongAdder实现方法 版本号机制 数据表中加上一个数据版本号 version 字段表示数据被修改的次数数据被修改时version值1当线程 A 要更新数据值时在读取数据的同时也会读取 version 值在提交更新时若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新否则重试更新操作直到更新成功。 CAS比较与交换 思想用一个预期值和要更新的变量值进行比较两值相等才会进行更新三个操作数 V 要更新的变量值(Var)E 预期值(Expected)N 拟写入的新值(New)当且仅当VE时CAS通过原子方式用N更新VV!E时说明有其它线程更新了V则当前线程放弃更新。 问题 ABA问题 描述变量V刚开始是A值最后也是A值能证明其它线程没改过它吗不能这就是ABA问题解决变量前面追加版本号或者时间戳 循环时间长开销大 描述CAS会用到自旋进行重试不成功就一直循环执行直到成功长时间不成功会给CPU带来非常大的时间开销解决JVM支持的pause命令作用有两个 延迟流水线执行指令使CPU不会消耗过多的执行资源避免退出循环时因内存顺序引起CPU流水线被清空提高CPU效率 只能保证一个共享变量的原子操作 描述CAS只对单个共享变量有效跨多个共享变量时无效解决JDK1.5之后提供了AtomicReference类来保证引用对象之间的原子性可以把多个变量放在一个对象里来进行 CAS 操作 synchronized synchronized主要解决的是多个线程之间访问资源的同步性可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 1. 使用场景 1. 修饰实例方法锁当前对象实例 给当前对象实例加锁进入同步代码前要获得 当前对象实例的锁 。 synchronized void method() {//业务代码 }2. 修饰静态方法锁当前类 给当前类加锁会作用于类的所有对象实例 进入同步代码前要获得 当前 class 的锁。 这是因为静态成员不属于任何一个实例对象归整个类所有不依赖于类的特定实例被类的所有实例共享。 synchronized static void method() {//业务代码 }3. 修饰代码块锁指定对象/类 synchronized(object) 表示进入同步代码库前要获得 给定对象的锁。 synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁 synchronized(this) {//业务代码 }注意构造方法不能用synchronized修饰构造方法本身就线程安全 2. 底层原理 底层依赖于JVM分为修饰方法和修饰代码块两个部分 synchronized 同步语句块(monitor两个指令) synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令其中 monitorenter 指令指向同步代码块的开始位置monitorexit 指令则指明同步代码块的结束位置。 在执行**monitorenter**时会尝试获取对象的锁如果锁的计数器为 0 则表示锁可以被获取获取后将锁计数器设为 1 也就是加 1。 对象锁的的拥有者线程才可以执行 monitorexit 指令来释放锁。在执行 monitorexit 指令后将锁计数器设为 0表明锁被释放其他线程可以尝试获取锁。 如果获取对象锁失败那当前线程就要阻塞等待直到锁被另外一个线程释放为止。 synchronized 修饰方法(ACC_SYNCHRONIZED 标识) synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令取得代之的确实是 ACC_SYNCHRONIZED 标识该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法从而执行相应的同步调用。 如果是实例方法JVM 会尝试获取实例对象的锁。如果是静态方法JVM 会尝试获取当前 class 的锁。 同步语句块用monitorenter 指令和 monitorexit 指令修饰方法用**ACC_SYNCHRONIZED** 标识不过两者的本质都是对对象监视器 monitor 的获取 monitor是啥 Monitor 可以理解为一个同步工具或一种同步机制通常被描述为一个对象。每一个 Java 对象就有一把看不见的锁称为内部锁或者 Monitor 锁。Monitor 是线程私有的数据结构每一个线程都有一个可用 monitor record 列表同时还有一个全局的可用列表。每一个被锁住的对象都会和一个 monitor 关联同时 monitor 中有一个 Owner 字段存放拥有该锁的线程的唯一标识表示该锁被这个线程占用。Synchronized是通过对象内部的一个叫做监视器锁monitor来实现的monitor锁本质又是依赖于底层的操作系统的 Mutex Lock互斥锁来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态这个成本非常高状态之间的转换需要相对比较长的时间这就是为什么 Synchronized 效率低的原因。因此这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为重量级锁。 3. 和volatile区别 synchronized和 volatile是互补的存在区别有四点 性能volatile 关键字是线程同步的轻量级实现性能比synchronized好使用volatile 只能用于变量synchronized 可以修饰方法以及代码块并发特性volatile能保证数据的可见性但不能保证数据的原子性。synchronized 关键字两者都能保证场景volatile主要用于解决变量在多个线程之间的可见性而 synchronized 关键字解决的是多个线程之间访问资源的同步性 ReentrantLock ReentrantLock 实现了 Lock 接口是一个可重入且独占式的锁底层AQSAbstractQueuedSynchronizer ReentrantLock 里面有一个内部类 SyncSync 继承 AQS添加锁和释放锁的大部分操作实际上都是在 Sync 中实现的Sync 有公平锁 FairSync 和非公平锁 NonfairSync 两个子类 公平锁先等待的线程先获得锁非公平锁随机线程获得锁性能更好但可能某些线程永远无法获得锁ReentrantLock 默认 和synchronized区别 都是可重入锁相同点 可重入锁也叫递归锁指的是线程可以再次获取自己的内部锁 JDK所有现成的Lock实现类包括synchronized 关键字锁都是可重入的 依赖对象不同 synchronized 依赖于 JVM ReentrantLock 依赖于 API需要lock()和unlock()方法配合try/finally语句块完成 ReentrantLock功能更多 ReentrantLock增加了三点高级功能 等待可中断正在等待的线程可以选择放弃等待处理其他事情 可中断锁获取锁的过程可以被中断进行其他逻辑处理【ReentrantLock】不可中断锁一旦线程申请了锁就只能等到拿到锁以后才能进行其他的逻辑处理【synchronized】 可实现公平锁 ReentrantLock可以指定是公平锁还是非公平锁默认非公平synchronized只能是非公平锁 可实现选择性通知锁可以绑定多个条件 synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制ReentrantLock类需要借助于Condition接口与newCondition()方法实现 AQS(抽象队列) AQS就是一个抽象类翻译过来就是抽象队列同步器 使用 AQS 能简单且高效地构造出应用广泛的大量的同步器比如我们提到的 ReentrantLockSemaphore public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { }1. 原理 核心思想 如果被请求的共享资源空闲则将当前请求资源的线程设置为有效的工作线程并且将共享资源设置为锁定状态。 如果被请求的共享资源被占用那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制 这个机制 AQS 是用 CLH 队列锁 实现的即将暂时获取不到锁的线程加入到队列中。 CLH队列是一个虚拟的双向队列AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点Node来实现锁的分配在 CLH 同步队列中一个节点表示一个线程 AQS 使用 int 成员变量 state 表示同步状态通过内置的 线程等待队列 来完成获取资源线程的排队工作 // 共享变量使用volatile修饰保证线程可见性 private volatile int state;2. Semaphore作用 作用Semaphore(信号量)可以用来控制同时访问特定资源的线程数量想OS中咋用的 // 初始共享资源数量 final Semaphore semaphore new Semaphore(5); // 获取1个许可 semaphore.acquire(); // 释放1个许可 semaphore.release();​ 当初始的资源个数为 1 的时候Semaphore 退化为排他锁。 Semaphore 两种模式 公平模式 调用 acquire() 方法的顺序就是获取许可证的顺序遵循 FIFO 非公平模式 抢占式的。 应用场景Semaphore 通常用于那些资源有明确访问数量限制的场景比如限流 3. Semaphore原理 Semaphore 是共享锁的一种实现它默认构造 AQS 的 state 值为 permits 调用semaphore.acquire()相当于OS中P(S)state0表示可以获取成功使用CAS操作state减1state0表示数量不足创建Node结点加入阻塞队列挂起当前线程 调用semaphore.release()相当于OS中V(S)使用CAS操作state1释放许可证成功后唤醒同步队列中的一个线程。被唤醒的线程重新尝试state-1如果state0则获取令牌成功否则重新进入阻塞队列挂起线程 4. CountDownLatch 作用允许count个线程阻塞在一个地方直到所有线程任务执行完毕一次性的计数器的值只能在构造方法中初始化一次使用完毕后不能再次使用 原理共享锁的一种实现默认构造AQS的state值为count 使用countDown()方法CAS操作减少state直到state为0使用await()方法 state不为0说明任务还没执行完毕await()一直阻塞之后的语句不会被执行CountDownLatch 会自旋 CAS 判断 state 0是的话释放所有线程await()之后的语句执行 场景多线程读取多个文件处理具体如下 读取处理6个文件没有执行顺序依赖但返回给用户时要将几个文件的处理结果进行统计整理定义线程池count为6的CountDownLatch对象线程池读取任务处理完后count-1调用CountDownLatch对象的 await()方法所有文件读取完后截止执行后面的逻辑 基于AQS管程的CountDownLatch工具类配合线程池模拟了一个高并发的场景对缓存进行了一个测试我先让主线程创建一个CountDownLatch然后把它的初始值设为一然后创建一个线程池在线程池中创建一百个线程让他们启动起来之后都调用CountDownLatch.await()方法进入条件变量等待队列等待然后主线程等待一秒等线程池中的线程都起动起来进入等待状态然后主线程调用CountDownLatch.countDown()将CountDownLatch减为一然后线程池中的大量线程就会唤醒这样就模拟出来了一个高并发的场景对缓存进行测试是这样的一个测试 ThreadLocal 1. 作用场景 作用本地线程变量让每个线程绑定自己的值可以将ThreadLocal类形象的比喻成存放数据的盒子盒子中可以存储每个线程的私有数据。 使用场景解决多线程中因为数据并发产生不一致的问题。 1、存储用户Session 2、线程间数据隔离 3、进行事务操作用于存储线程事务信息。 4、数据库连接Session会话管理。 5、数据跨层传递controller,service,dao 用 ThreadLocal 保存一些业务内容用户权限信息、从用户系统获取到的用户名、用户ID 等这些信息在同一个线程内相同但是不同的线程使用的业务内容是不相同的。在线程生命周期内都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象避免了将这个对象如 user 对象作为参数传递的麻烦。比如说我们是一个用户系统那么当一个请求进来的时候一个线程会负责执行这个请求然后这个请求就会依次调用service-1()、service-2()、service-3()、service-4()这4个方法可能是分布在不同的类中的。线程隔离表面上他们使用的是同一个ThreadLocal 但是实际上使用的值value却是自己独有的一份 package com.kong.threadlocal;public class ThreadLocalDemo05 {public static void main(String[] args) {User user new User(jack);new Service1().service1(user);}}class Service1 {public void service1(User user){//给ThreadLocal赋值后续的服务直接通过ThreadLocal获取就行了。UserContextHolder.holder.set(user);new Service2().service2();} }class Service2 {public void service2(){User user UserContextHolder.holder.get();System.out.println(service2拿到的用户:user.name);new Service3().service3();} }class Service3 {public void service3(){User user UserContextHolder.holder.get();System.out.println(service3拿到的用户:user.name);//在整个流程执行完毕后一定要执行removeUserContextHolder.holder.remove();} }class UserContextHolder {//创建ThreadLocal保存User对象public static ThreadLocalUser holder new ThreadLocal(); }class User {String name;public User(String name){this.name name;} }执行的结果service2拿到的用户:jack service3拿到的用户:jackThreadLocal 在项目中的使用 - 知乎 (zhihu.com) 问题用户认证中拦截器获取用户身份信息为了后续业务逻辑代码方便使用用redis做缓存但是value存到redis后用户的key值必须一步步往下传递丑陋解决ThreadLocal 作为一级缓存使用 redis 作为二级缓存使用同一个线程内 用户信息所有方法共享。任何地方只要需要使用用户信息 直接从threadlocal 取。 2. 原理实现 ThreadLocal 类原理 每个线程都有一个 ThreadLocalMapThreadLocal内部类 Map 中元素的键为 ThreadLocal而值对应线程的变量副本。 Map 是数组实现使用线性探测解决hash冲突需要手动调用set、get、remove防止内存泄漏。 Thread类源代码 public class Thread implements Runnable {//......//与此线程有关的ThreadLocal值。由ThreadLocal类维护ThreadLocal.ThreadLocalMap threadLocals null;//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护ThreadLocal.ThreadLocalMap inheritableThreadLocals null;//......}set()方法 最终的变量是放在了当前线程的 ThreadLocalMap 中并不是存在 ThreadLocal 上ThreadLocal 可以理解为只是ThreadLocalMap的封装传递了变量值。 public void set(T value) {//获取当前请求的线程Thread t Thread.currentThread();//取出 Thread 类内部的 threadLocals 变量(哈希表结构)ThreadLocalMap map getMap(t);if (map ! null)// 将需要存储的值放入到这个哈希表中map.set(this, value);elsecreateMap(t, value); } ThreadLocalMap getMap(Thread t) {return t.threadLocals; } get()方法 public T get() {//1、获取当前线程Thread t Thread.currentThread();//2、获取当前线程的ThreadLocalMapThreadLocalMap map getMap(t);//3、如果map数据不为空if (map ! null) {//3.1、获取threalLocalMap中存储的值ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {SuppressWarnings(unchecked)T result (T)e.value;return result;}}//如果是数据为null则初始化初始化的结果TheralLocalMap中存放key值为threadLocal值为nullreturn setInitialValue();}private T setInitialValue() {T value initialValue();Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null)map.set(this, value);elsecreateMap(t, value);return value;}常用方法还有**remove()**。 ThreadLocal 数据结构 每个Thread中都具备一个ThreadLocalMapThreadLocal的内部静态类而ThreadLocalMap可以存储以ThreadLocal为 key Object 对象为 value 的键值对。 3. 内存泄露问题 内存泄露 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用而 value 是强引用。所以如果 ThreadLocal 没有被外部强引用的情况下在垃圾回收的时候key 会被清理掉而 value 不会被清理掉。 这样一来ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话value 永远无法被 GC 回收产生内存泄露。 static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);//代表ThreadLocal对象是一个弱引用value v;} }实际私用ThreadLocal场景都是采用线程池而线程池中的线程都是复用的这样就可能导致非常多的entry(null,value)出现从而导致内存泄露 如何避免 每次使用完ThreadLocal都调用它的**remove()方法清除数据**尽可能不让它在线程存储值避免使用线程池的时候值一直在线程对象存储 为什么key不是强引用 key如果是强引用且没有手动remove会和value一样伴随线程的整个生命周期假设使用完ThreadLocal引用被回收但是由于ThreadLocalMap的Entry强引用了ThreadLocal, 造成ThreadLocal无法被回收也无法避免内存泄露。key弱引用原因事实上在 ThreadLocalMap 中的set/getEntry 方法中会对 key 为 null 进行判断如果为 null 的话那么会把 value 置为 null 的这就意味着使用threadLocal , CurrentThread 依然运行的前提下就算忘记调用 remove 方法弱引用比强引用可以多一层保障弱引用的 ThreadLocal 会被回收对应value在下一次 ThreadLocaI 调用 get()/set()/remove() 中的任一方法的时候会被清除从而避免内存泄漏 如何正确使用ThreadLocal 将ThreadLocal变量定义成**private static**的这样的话ThreadLocal的生命周期就更长由于一直存在ThreadLocal的强引用所以ThreadLocal也就不会被回收也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值然后remove它防止内存泄露每次使用完ThreadLocal都调用它的**remove()**方法清除数据。 线程池 线程池就是管理一系列线程的资源池有任务处理-线程池拿线程-处理完不销毁-线程等待下一个任务 1. 线程池作用 提供限制和管理资源的方式 维护一些基本统计信息 **降低资源消耗。**重复利用已创建的线程降低创建销毁的消耗。**提高响应速度。**任务到达时任务不需要等到线程创建就能立即执行。**提高线程可管理性。**线程池统一分配、调优和监控线程。 2. 创建线程池方法 ThreadPoolExecutor构造函数 源码在执行的时候重点就在于它维护了一个ctl参数这个ctl参数的用高3位表示线程池的状态低29位来表示线程的数量 Executor 框架的工具类 Executors 3. 线程池饱和策略 线程达到最大数量队列也被放满了任务ThreadPoolExecutor.定义的策略 .AbortPolicy默认抛出异常拒绝新任务的处理。.CallerRunsPolicy调用自己的线程运行任务。.DiscardPolicy不处理新任务直接丢弃。.DiscardOldestPolicy丢弃最老未处理的任务请求。 4. 常用阻塞队列 新任务到来-当前运行线程数核心线程数?-√-新任务放队列 不同线程池用不同的阻塞队列 LinkedBlockingQueue无界队列队列永远不会被放满SynchronousQueue同步队列没有容量不存储元素。DelayedWorkQueue延迟阻塞队列内部采用“堆” 5. 线程池处理任务流程 6. 线程池大小 太大-增加上下文切换成本 太小-大量请求/任务堆积 CPU密集型任务(N1)线程数设置为 N(CPU核心数)1 I/O密集型任务(2N)线程处理I/O不会占用CPU可以将CPU交出给其他线程所以可以多配点线程 7. 线程池参数 ThreadPoolExecutor 3 个最重要的参数 corePoolSize 核心线程池容量 任务队列未达到队列容量时最大可以同时运行的线程数量。maximumPoolSize 最大线程池容量 任务队列中存放的任务达到队列容量的时候当前可以同时运行的线程数量变为最大线程数。workQueue工作队列容量 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数如果达到的话新任务就会被存放在队列中。 ThreadPoolExecutor其他常见参数 : keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候如果这时没有新的任务提交核心线程外的线程不会立即销毁而是会等待直到等待的时间超过了 keepAliveTime才会被回收销毁unit : keepAliveTime 参数的时间单位。threadFactory :executor 创建新线程的时候会用到。handler :饱和策略。 Future 核心思想异步调用主要用在多线程领域作用用在执行耗时任务的场景避免程序一直原地等待耗时任务执行完成执行效率太低功能在java中是个泛型接口包含下面四个功能 取消任务判断任务是否被取消;判断任务是否已经执行完成;获取任务执行结果。 简单理解就是我有一个任务提交给了 Future 来处理。任务执行期间我自己可以去做任何想做的事情。并且在这期间我还可以取消任务以及获取任务的执行状态。一段时间之后我就可以 Future 那里直接取出任务执行结果。 Callable和Future的关系 FutureTask 提供了 Future 接口的基本实现常用来封装 Callable 和 Runnable //FutureTask 有两个构造函数可传入Callable或者Runnable对象 public FutureTask(CallableV callable) {if (callable null)throw new NullPointerException();this.callable callable;this.state NEW; } public FutureTask(Runnable runnable, V result) {// 通过适配器RunnableAdapter来将Runnable对象runnable转换成Callable对象this.callable Executors.callable(runnable, result);this.state NEW; }JVM 一、内存结构 运行时数据区域 JDK1.7运行时数据区域线程共享 堆、方法区、直接内存线程私有程序计数器、虚拟机栈、本地方法栈 JDK1.8运行时数据区域 线程私有程序计数器俩栈 程序计数器很小的内存空间当前线程的行号指示器 作用 字节码解释器通过改变程序计数器来依次读取指令从而实现代码的流程控制如顺序、选择、循环多线程下程序计数器用于记录当前线程执行的位置 注意程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域它的生命周期随着线程的创建而创建随着线程的结束而死亡。 虚拟机栈保存方法的局部变量、操作数、动态链接、方法返回地址 单位栈帧栈帧随着方法调用而创建随着方法结束而销毁。无论方法正常完成还是异常完成都算作方法结束。 程序运行中栈的两种错误 StackOverFlowError栈内存大小不允许动态扩展线程请求栈深度当前 JVM 栈最大深度抛出OutOfMemoryError栈内存大小允许动态扩展JVM 动态扩展栈时无法申请到足够的内存空间抛出 本地方法栈 和虚拟机栈的区别 虚拟机栈为虚拟机执行 Java 方法字节码服务本地方法栈为虚拟机使用到的 Native 方法 服务 本地方法被执行的时候在本地方法栈也会创建一个栈帧用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间也会出现 StackOverFlowError 和 OutOfMemoryError 两种错误。 线程共享堆方法区直接内存 堆运行时数据区域保存所有类的实例和数组 JVM管理内存中最大的一块所有线程共享的一块内存区域 垃圾收集器管理的主要区域因此也被称作 GC 堆 堆内存结构新生代(Eden,s0,s1)、老年代(tenured)、永久代/元空间(元空间使用本地内存) 对象都会首先在 Eden 区域分配在一次新生代垃圾回收后如果对象还存活则会进入 S0 或者 S1并且对象的年龄还会加 1当它的年龄增加到阈值默认为 15 岁-XX:MaxTenuringThreshold设置就会被晋升到老年代中 堆容易出现的 OutOfMemoryError 错误 java.lang.OutOfMemoryError: GC Overhead Limit ExceededJVM花太多时间垃圾回收只能回收很少的堆空间java.lang.OutOfMemoryError: Java heap space 堆内存空间不足以存放新创建的对象 字符串常量池 作用避免字符串重复创建是JVM为提升性能减少内存消耗 为String类专门开辟的一块区域 // 在堆中创建字符串对象”ab“ // 将字符串对象”ab“的引用保存在字符串常量池中 String aa ab; // 直接返回字符串常量池中字符串对象”ab“的引用 String bb ab; System.out.println(aabb);// true存放位置JDK1.7之前-永久代1.7之后-字符串常量池和静态变量移动到堆中 方法区存储加载的类信息、class/method/field等元数据、常量、静态变量 1.8之后拆成了 加载的类信息元数据区 和 运行时常量池堆 存放的是程序中永远唯一的元素感觉就是类、方法的定义结果以及静态变量 和永久代以及元空间的关系 类比永久代、元空间-类具体实现方法区-接口抽象概念 元空间溢出会得到OOM错误java.lang.OutOfMemoryError: MetaSpace 你可以使用 -XXMaxMetaspaceSize 标志设置最大元空间大小默认值为 unlimited元空间里面存放的是类的元数据这样加载多少类的元数据就不由 MaxPermSize 控制了, 而由系统的实际可用空间来控制这样能加载的类就更多了。 运行时常量池 .class文件中类的版本、字段、方法、接口等描述信息常量池表有啥各种字面量符号引号 字面量源代码中固定值的表示法包括整数、浮点数、字符串字面量通过字面就能知道含义符号引用包括类符号引用、字段符号引用、方法符号引用、接口方法引用… 直接内存非运行时数据区域 特殊的内存缓冲区并不在java堆或方法区中分配在本地内存上分配不是虚拟机运行时数据区域也不是虚拟机规范中定义的内存区域但也被频繁使用。也可能OOM。 HotSpot虚拟机对象 1. 对象创建默写 类加载检查 检查这个指令的参数是否能在常量池中定位到这个类的符号引用检查这个符号引用代表的类是否已被加载过、解析和初始化过如果没有那必须先执行相应的类加载过程。 分配内存 在类加载检查通过后接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种选择哪种分配方式由 Java 堆是否规整决定而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。内存分配的两种方式 指针碰撞 适用场合堆内存规整即没有内存碎片的情况下。原理用过的内存全部整合到一边没有用过的内存放在另一边中间有一个分界指针只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。使用该分配方式的 GC 收集器Serial, ParNew 空闲列表 适用场合堆内存不规整的情况下。原理虚拟机会维护一个列表该列表中会记录哪些内存块是可用的在分配的时候找一块儿足够大的内存块儿来划分给对象实例最后更新列表记录。使用该分配方式的 GC 收集器CMS 内存分配并发问题 创建对象时要保证线程安全虚拟机采用两种方式来保证 CAS失败重试CAS 是乐观锁的一种实现方式。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。TLAB 为每一个线程预先在 Eden 区分配一块儿内存JVM 在给线程中的对象分配内存时首先在 TLAB 分配当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时再采用上述的 CAS 进行内存分配 初始化零值内存分配完后虚拟机需要将分配到的内存空间都初始化为零值不包括对象头这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用程序能访问到这些字段的数据类型所对应的零值。设置对象头初始化零值完成之后虚拟机要对对象进行必要的设置例如这个对象是哪个类的实例对象的哈希码这些信息存放在对象头中。执行init方法虚拟机视角对象已经产生但从java视角对象创建才刚开始init 方法还没有执行所有的字段都还为零。一般来说执行 new 指令之后会接着执行 init 方法把对象按照程序员的意愿进行初始化这样一个真正可用的对象才算完全产生出来。 2. 对象的内存布局 Object对象占用16字节对象在内存中的布局可以分为 3 块区域对象头8个字节、实例数据和对齐填充。 对象头具体包含哪些内容 Mark Word存储对象自身的运行时数据如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等 Klass pointer虚拟机通过这个指针来确定这个对象是哪个类的实例 数组长度仅针对数组对象 JVM是怎么升级锁的 无锁-偏向锁-轻量级锁-重量级锁。随着竞争的激烈而逐渐升级只能升级不能降级 无锁 没有对资源进行锁定所有的线程都能访问并修改同一个资源但同时只有一个线程能修改成功 偏向锁 指当一段同步代码一直被同一个线程所访问时即不存在多个线程的竞争时那么该线程在后续访问时便会自动获得锁从而降低获取锁带来的消耗即提高性能。 轻量级锁自旋锁 当锁是偏向锁的时候却被另外的线程所访问此时偏向锁就会升级为轻量级锁其他线程会通过自旋关于自旋的介绍见文末的形式尝试获取锁线程不会阻塞从而提高性能。 重量级锁 当有一个线程获取锁之后其余所有等待获取该锁的线程都会处于阻塞状态。 3. 对象的访问定位 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。访问对象的两种主流方式 使用句柄Java 堆中将会划分出一块内存来作为句柄池reference 中存储的就是对象的句柄地址而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息。 好处reference 中存储的是稳定的句柄地址在对象被移动时只会改变句柄中的实例数据指针而 reference 本身不需要修改。 直接指针reference 中存储的直接就是对象的地址。速度快节省一次指针定位开销 二、类加载机制 类加载器通过类的全限定性类名带包路径如java.lang.String获取该类的二进制字节流 Bootstrap(启动类加载器)加载java核心库(如$JAVA_HOME/jre/lib/rt.jar) 好处不需要太多的安全检查提升性能 Extension(扩展类加载器)加载拓展库$JAVA_HOME/jre/lib/ext/*.jarAppClass(应用程序类加载器)根据java的类路径加载类一般的java应用的类$CLASSPATH用户自定义 加载方式双亲委派机制 过程 收到类加载请求不会自己加载而是逐级向上委托最终到达顶层的Bootstrap如果父类加载器完成则返回否则交给子类尝试。 好处 避免类的重复加载java类由于加载机制形成了优先级的层次关系防止核心类被篡改防止下层的同名类篡改核心API库 打破这个机制 意义由于类加载器受到加载范围的限制某些情况下父类加载器无法加载需要的类不属于其加载范围但根据机制只能其加载所以需要子类加载器加载。方法使用自定义加载器如何重写loadClass()方法 or 重写findClass()方法 1、找到ClassLoader类的loadClass()方法拷贝2、写自定义加载器类粘贴3、删去loadClass()中的双亲委派机制的部分4、判断类是否为系统类是的话使用双亲委派机制否则自己的方法加载。 应用 TomcatwebappClassLoader加载自己目录下的class文件目的1、隔离每个webapp下的class和lib2、使用单独的加载器以免被恶意破坏3、热部署修改文件不用重启自动重新装载 三、垃圾回收GC 1. 引用类型 2. 垃圾收集 垃圾不再被使用的对象死亡的对象 哪些垃圾需要回收 引用计数法每个对象添加一个引用计数器当为0时就表示死亡 问题循环引用不可回收造成内存泄露空间占用 可达性分析算法以根对象集合GC Roots为起点分析GC Roots连接的对象是否可达解决了循环引用问题。 GC Roots就是对象 1、虚拟机栈中的栈帧中的局部变量所引用的对象2、java本地方法栈中java本地接口引用的对象3、方法区中静态变量引用的对象全局对象4、方法区中常量引用的对象全局对象5、所有被同步锁持有的对象 问题多线程下更新了访问过的对象的引用 误报原被引用的对象不再被引用。影响较小顶多减少GC次数。 漏报将已被访问过的对象设置为未被访问过。影响较大可能会使引用对象被GC导致jvm崩溃。 解决方法Stop-the-world等所有线程到达安全点再进行Stop-the-world。 Stop-the-world用户运行至安全点safe point或者安全区域之后就会挂起进入暂停状态。 什么时候回收 Minor GCyoung gc发生在年轻代的gc 大多数情况下对象直接在年轻代中的Eden区进行分配如果Eden区域没有足够的空间那么就会触发YGCMinor GC Major GCold gc发生在老年代的gc**Full GC**回收整个堆和方法区 触发条件 System.gc()通过Minor GC进入老年代的平均大小 老年代的可用内存老年代空间不足 3. 垃圾回收算法内存回收方法论 4. 垃圾收集器内存回收具体实现 Serial单 新 复制 单线程所有线程stw 新生代标记-复制老年代标记-整理 缺点需要停止所有工作线程效率低 场景对应用的实时性要求不高的client级别桌面应用的默认方式单核服务器 ParNew多 新 复制 Serial的多线程版本stw 复制算法 新生代标记-复制老年代标记-整理 实际线程默认和cpu数量相同 优点有效利用cpu 缺点和Serial一样 场景Sever模式下的新生代和CMS配合 Parallel Scavenge多 新 复制 新生代收集器、复制算法多线程 与ParNew不同点追求和精确控制高吞吐量而ParNew尽可能缩短用户线程的停顿时间 高吞吐线程运行时间/线程运行时间 垃圾回收时间例子PS就是100秒回收一次垃圾持续10秒吞吐量为90%PN就是50秒回收一次垃圾持续7秒吞吐量为86%。 场景注重高效利用CPU Serial Old单 老 整理 Serial的老年代版本标记整理算法 场景client、单核。与PS收集器搭配 Parallel Old多 老 整理 Parallel Scavenge的老年代版本多线程标记整理算法 JDK78默认老年代收集器 CMS多 老 清除 Concurrent Mark Sweep 多线程、标记清除算法 特点获取最短回收停顿时间 流程 初始标记GC Roots直接关联的对象需要Stw并发标记与用户线程并发不需要Stw所以对象可能发生变化执行Gc Roots Tracing遍历整个对象图重新标记修正并发标记期间发生变动的一些标记需要Stw并发清除标记清除不会stw并发重置移除标记避免影响下次gc 三色标记算法用于并发标记阶段 黑色对象不再扫描第一次扫描的时候已经标记完成并且孩子也标记自己已经标记fields都标记完成 灰色自己标记完成还没来得及标记fields则继续扫描其孩子就可以了 白色没有遍历到的结点 问题 错标标记过不是垃圾的变成了垃圾也叫浮动垃圾 如下图遍历到E变灰此时执行objD.fieldE nullDE断开EFG变成不可达应该回收但因为E灰仍然会被当做存活对象遍历下去EFG不会被回收 漏标如下图GC 线程已经遍历到EE变灰应用进程执行1. G objE.fieldG; 2. objE.fieldG null; 3. objD.fieldG G;EG断开此时切回到GC因为EG断G不会灰尽管D重新引用G但D已黑所以不会重新遍历G就会一直白被当做垃圾处理影响程序正确性 解决方法CMS用写屏障增量更新处理G1用写屏障SATB原始快照处理 写屏障指给某个对象的成员变量赋值操作前后加入一些处理增量更新当对象 D 的成员变量的引用发生变化时objD.fieldG G;我们可以利用写屏障将 D 新的成员变量引用对象 G 记录下来。即不要求保留原始快照而是针对新增的引用将其记录下来等待遍历。SATB当对象 E 的成员变量的引用发生变化时objE.fieldG null;我们可以利用写屏障将 E 原来成员变量的引用对象 G 记录下来。即当原来成员变量的引用发生变化之前记录下原来的引用对象。 优点并发收集低停顿 缺点 对CPU资源敏感标记清除产生空间碎片并发清除过程中会产生新的垃圾只能等下一次 G1多 新/老 整理复制 标记整理整体 复制局部 特点 并行与并发分代收集自己采用不同的收集方式去处理不同代的垃圾不需要和其他收集器合作空间整合无空间碎片可预测的停顿可预测时间的停顿模型 原理 将java堆分为大小相同的独立区域Region新生代和老年代区域混合 并发标记估计哪些region基本是垃圾就会从这些块下手所以叫G1停顿预测模型 根据之前垃圾回收的数据统计估计出用户指定停顿时间内的回收块个数 “尽力”满足指定的目标停顿时间基于目标选择回收的区块数量 步骤 Minor GC * 复制算法、并行、stw* 动态调整年轻区大小根据历史数据和用户指定的停顿时间目标老年代收集 初始标记stw伴随young gc对Gc Roots直接关联对象进行标记扫描根引用区即survivor区的对象直接到老年代对象的引用。因为进行了YGC新生代中只有survivor区存在对象并发标记寻找整个堆的存活对象并发执行可以被YGC中断 如果整个region都是垃圾直接被回收标记的同时计算region活性比例 最终标记stw完成最后的标记使用了STAB 算法 由于用户线程同时进行需要修正之前的标记采用了比CMS更快的初始快照法SATB算法 筛选回收stw复制算法。计算各个region的存活对象和垃圾比例排序识别可以混合回收的区域 。并发清理识别并清理全是垃圾的老年代region 混合式收集 除了整个年轻代收集 还有上个阶段标记出的老年代的垃圾最多的区块持续回收所有被标记的分区恢复到年轻代收集进行新的周期必要时的Full GC G1的初衷就是避免Full GC 总结 7种收集器关系 如果两个收集器之间存在连线就说明它们可以搭配使用 大致思路串行Serial不够用了-并行PS-想和用户线程并发-CMS但需要和新生代配合PS不行-设计ParNew和CMS配合 收集器流程 收集器总结 收集器串/并行/并发新/老收集算法目标场景Serial串行新复制响应速度优先单CPU环境下的Client模式Serial Old串行老标记-整理响应速度优先单CPU环境下的Client模式、CMS的后备预案ParNew并行新复制响应速度优先多CPU环境时在Server模式下与CMS配合Parallel Scavenge并行新复制吞吐量优先在后台运算而不需要太多交互的任务Parallel Old并行老标记-整理吞吐量优先在后台运算而不需要太多交互的任务CMS并发老标记-清除响应速度优先集中在互联网站或B/S系统服务端上的Java应用G1并发both标记-整理复制算法响应速度优先面向服务端应用将来替换CMS 5. GC调优 常用工具 jstat 首先我们使用 jstat 查看了 GC 的情况。又通过查看GC log分析了GC 的详细状况。使用 jstat -gcutil ${pid} 1000 每隔一秒打印一次 GC 统计信息。直接查看 GC log 不太直观可以借助一些可视化JVM分析工具来帮助我们分析 在线分析工具GCeasy 我们把 GC log 上传到https://gceasy.io后 GCeasy 会根据GC log生成各个维度的图表让我们更直观的分析JVM问题。 什么是heap dump Heap dump文件是一个二进制文件它保存了某一时刻JVM堆中对象使用情况。Heap Dump文件是指定时刻的Java堆栈的快照是一种镜像文件。Heap Dump一般都包含了一个堆中的Java Objects, Class等基本信息。同时当你在执行一个转储操作时往往会触发一次GC所以你转储得到的文件里包含的信息通常是有效的内容包含比较少或没有垃圾对象了 。我们可以这么理解heap dump记录内存信息的thread dump是记录CPU信息的。 怎么得到heap dump 一般来说两种方式 事先开启HeadDumpOnOutOfMemoryError这样出现OOM的时候能自动留下Dump留好第一现场。这是最推荐的方式。 JVM的启动参数中加入如下的一些参数 -XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/usr/local/oom//第一个参数意思是在OOM的时候自动dump内存快照出来第二个参数是说把内存快照存放在哪里手工得到heap dump这个方式就很多了命令或者一些第三方工具都可以获得指定java进程的heap dump。比如jdk自带的jvisualvm.exe这个是图形化的也不用额外下载。其他方式可以自己去有很多。通过jvisualvm.exe获取heap jump. GC问题判断常见场景分析与解决 JVM频繁full gc问题排查 常见原因full gc 触发条件是 老年代空间不足 所以追因的方向就是导致 老年代空间不足的原因大量对象频繁进入老年代 老年代空间释放不掉 系统并发高、执行耗时过长或者数据量过大导致 young gc频繁且gc后存活对象太多但是survivor 区存放不下太小 或 动态年龄判断 导致对象快速进入老年代 老年代迅速堆满发程序一次性加载过多对象到内存 大对象导致频繁有大对象进入老年代 造成full gc存在内存溢出的情况老年代驻留了大量释放不掉的对象 只要有一点点对象进入老年代 就达到 full gc的水位了元数据区加载了太多类 满了 也会发生 full gc堆外内存 direct buffer memory 使用不当导致也许 你看到老年代内存不高 重启也没用 还在频繁发生full gc 那么可能有人作妖在代码里搞执行了 System.gc(); 定位思路如果有监控那么通过图形能比较直观、快速的了解gc情况 如果没有监控那么只能看gc日志或jstat来分析 这是基本技能 一定要熟练 观察年轻代 gc的情况多久执行一次、每次gc后存活对象有多少 survivor区多大 存活对象比较多 超过survivor区大小或触发动态年龄判断 调整内存分配比例观察老年代的内存情况 水位情况多久执行一次、执行耗时多少、回收掉多少内存 如果在持续的上涨而且full gc后回收效果不好那么很有可能是内存溢出了 dump 排查具体是什么玩意如果年轻代和老年代的内存都比较低而且频率低 那么又可能是元数据区加载太多东西了其实如果是自己负责的系统可能要看是不是发版改了什么配置、代码 频繁young gc问题排查 分析方法 分析 GC 日志可以通过启用 GC 日志并分析日志来了解 Young GC 的发生情况和原因。可以使用工具如 GCEasy 和 GCViewer 等来分析 GC 日志。使用 JDK 工具JDK 提供了一些工具来分析和调试垃圾回收问题如 jstat、jmap、jstack 等。可以使用这些工具来分析 Young GC 的情况。增加内存监控和报警如果 Young GC 次数频繁可以考虑增加内存监控和报警机制及时发现和解决问题。 解决方法 增加新生代大小如果频繁发生 Young GC可以尝试增加新生代大小让更多的对象存活于新生代中减少垃圾回收次数。增大Young gc触发阙值调整堆内存大小如果堆内存太小可能会导致频繁进行 Young GC因此可以尝试增加堆内存大小。检查代码中的内存泄漏频繁进行 Young GC 可能是由于程序存在内存泄漏导致的。可以通过分析堆转储文件来检查是否存在内存泄漏。调整 GC 策略Java 提供了多种 GC 策略可以尝试调整 GC 策略来优化 Young GC 的性能。比如采用g1回收器 四、JVM调优 JVM参数总结 堆内存相关 显式指定堆内存–Xms和-Xmx -Xmsheap size[unit] # 最小堆内存如-Xms2G -Xmxheap size[unit] # 最大堆内存如-Xmx5G显式新生代内存(Young Generation) 方式一 -XX:NewSizeyoung size[unit] # 最小新生代内存如-XX:NewSize256m -XX:MaxNewSizeyoung size[unit] # 最大新生代内存如-XX:NewSize1024m方式二 -Xmn256m # 新生代分配256m内存最小、最大相同Full GC 的成本远高于 Minor GC因此尽可能将对象分配在新生代是明智的做法 方式三通过 -XX:NewRatioint 来设置老年代与新生代内存的比值。 -XX:NewRatio1 # 设置老年代和新生代所占比值为1:1显式指定永久代/元空间的大小 jdk1.8之前永久代还没有被移除 -XX:PermSizeN #方法区 (永久代) 初始大小 -XX:MaxPermSizeN #方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGenjdk1.8元空间取代永久代 -XX:MetaspaceSizeN # Metaspace使用过程中触发Full GC的阈值只对触发起作用元空间初始大小对64位JVM来说是固定的所以这句话不是设置元空间初始大小的 -XX:MaxMetaspaceSizeN #设置 Metaspace 的最大大小垃圾回收相关 垃圾回收器JVM的四种GC实现 -XX:UseSerialGC #串行 -XX:UseParallelGC #并行 -XX:UseParNewGC #CMS -XX:UseG1GC #G1GC日志记录一定会配置打印GC日志的参数便于分析GC相关问题 # 必选 # 打印基本 GC 信息 -XX:PrintGCDetails -XX:PrintGCDateStamps # 打印对象分布 -XX:PrintTenuringDistribution # 打印堆数据 -XX:PrintHeapAtGC # 打印Reference处理信息 # 强引用/弱引用/软引用/虚引用/finalize 相关的方法 -XX:PrintReferenceGC # 打印STW时间 -XX:PrintGCApplicationStoppedTime# 可选 # 打印safepoint信息进入 STW 阶段之前需要要找到一个合适的 safepoint -XX:PrintSafepointStatistics -XX:PrintSafepointStatisticsCount1# GC日志输出的文件路径 -Xloggc:/path/to/gc-%t.log # 开启日志文件分割 -XX:UseGCLogFileRotation # 最多分割几个文件超过之后从头文件开始写 -XX:NumberOfGCLogFiles14 # 每个文件上限大小超过就触发分割 -XX:GCLogFileSize50M处理OOM JVM 提供了一些参数这些参数将堆内存转储到一个物理文件中以后可以用来查找泄漏 # 指示JVM在遇到OOM错误时将heap转储到物理文件中 -XX:HeapDumpOnOutOfMemoryError # HeapDumpPath表示要写入文件的路径; 可以给出任何文件名; 但是如果 JVM 在名称中找到一个 pid 标记则当前进程的进程 id 将附加到文件名中并使用.hprof格式 -XX:HeapDumpPath./java_pidpid.hprof # 发出紧急命令以便在内存不足的情况下执行。 # 内存不足时重启服务器可以设置参数: -XX:OnOutOfMemoryErrorshutdown -r -XX:OnOutOfMemoryError cmd args ; cmd args # 一种策略限制在抛出OOM错误之前在 GC 中花费的 VM 时间的比例 -XX:UseGCOverheadLimit其他 -server : 启用“ Server Hotspot VM”; 此参数默认用于 64 位 JVM-XX:UseStringDeduplication : Java 8u20 引入了这个 JVM 参数通过创建太多相同 String 的实例来减少不必要的内存使用; 这通过将重复 String 值减少为单个全局 char [] 数组来优化堆内存。-XX:UseLWPSynchronization: 设置基于 LWP (轻量级进程)的同步策略而不是基于线程的同步。-XX:LargePageSizeInBytes : 设置用于 Java 堆的较大页面大小; 它采用 GB/MB/KB 的参数; 页面大小越大我们可以更好地利用虚拟内存硬件资源; 然而这可能会导致 PermGen 的空间大小更大这反过来又会迫使 Java 堆空间的大小减小。-XX:MaxHeapFreeRatio : 设置 GC 后, 堆空闲的最大百分比以避免收缩。-XX:SurvivorRatio : eden/survivor 空间的比例, 例如-XX:SurvivorRatio6 设置每个 survivor 和 eden 之间的比例为 1:6。-XX:UseLargePages : 如果系统支持则使用大页面内存; 请注意如果使用这个 JVM 参数OpenJDK 7 可能会崩溃。-XX:UseStringCache : 启用 String 池中可用的常用分配字符串的缓存。-XX:UseCompressedStrings : 对 String 对象使用 byte [] 类型该类型可以用纯 ASCII 格式表示。-XX:OptimizeStringConcat : 它尽可能优化字符串串联操作 OOM排查 为什么有gc还会出现内存泄露 通过这种有向图的内存管理方式当一个内存对象失去了所有的引用之后GC 就可以将其回收。反过来说如果这个对象还存在引用那么它将不会被 GC 回收哪怕是 Java 虚拟机抛出 OOM。 java内存泄露的根本原因 内存对象明明已经不需要的时候还仍然保留着这块内存和它的访问方式引用。 1、堆内存不足 java.lang.OutOfMemoryError: Java heap space原因 1、代码中可能存在大对象分配 2、可能存在内存泄露导致在多次GC之后还是无法找到一块足够大的内存容纳当前对象。 解决方法 1、检查是否存在大对象的分配最有可能的是大数组分配 2、通过jmap命令把堆内存dump下来使用mat工具分析一下检查是否存在内存泄露的问题 3、如果没有找到明显的内存泄露使用 -Xmx 加大堆内存 4、还有一点容易被忽略检查是否有大量的自定义的 Finalizable 对象也有可能是框架内部提供的考虑其存在的必要性 2、永久代/元空间溢出 java.lang.OutOfMemoryError: PermGen space java.lang.OutOfMemoryError: Metaspace原因 永久代是 HotSot 虚拟机对方法区的具体实现存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。 JDK8后元空间替换了永久代元空间使用的是本地内存还有其它细节变化 字符串常量由永久代转移到堆中和永久代相关的JVM参数已移除 出现永久代或元空间的溢出的原因可能有如下几种 1、在Java7之前频繁的错误使用String.intern方法 2、生成了大量的代理类导致方法区被撑爆无法卸载 3、应用长时间运行没有重启 解决方法 1、检查是否永久代空间或者元空间设置的过小 2、检查代码中是否存在大量的反射操作 3、dump之后通过mat检查是否存在大量由于反射生成的代理类 4、放大招重启JVM 3、方法栈溢出 java.lang.OutOfMemoryError : unable to create new native Thread原因 出现这种异常基本上都是创建的了大量的线程导致的以前碰到过一次通过jstack出来一共8000多个线程。 解决方法 1、通过 -Xss 降低的每个线程栈大小的容量 2、线程总数也受到系统空闲内存和操作系统的限制检查是否该系统下有此限制 /proc/sys/kernel/pid_max/proc/sys/kernel/thread-maxmax_user_processulimit -u/proc/sys/vm/max_map_count 4、GC overhead limit exceeded java.lang.OutOfMemoryErrorGC overhead limit exceeded原因 这个是JDK6新加的错误类型一般都是堆太小导致的。 Sun 官方对此的定义超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。 解决方法 1、检查项目中是否有大量的死循环或有使用大内存的代码优化代码。 2、添加参数-XX:-UseGCOverheadLimit 禁用这个检查其实这个参数解决不了内存问题只是把错误的信息延后最终出现 java.lang.OutOfMemoryError: Java heap space。 3、dump内存检查是否存在内存泄露如果没有加大内存。 设计模式 单例模式 什么是单例模式 在整个运行时域一个类只有一个实例对象 单例模式有什么好处 对于有一些对象其实例我们只需要一个比方说线程池、缓存cache、日志对象等如果创建多个实例就会导致许多问题产生比如资源使用过量、程序行为不可控或者导致不一致的结果。 单例模式注意的三个问题 线程安全懒加载通过反射破坏 懒汉式 - 线程不安全 最基本的实现方式这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显不要求线程安全在多线程不能正常工作。 public class Singleton{private static Singleton instance;private Singleton(){}public static Singleton getInstance(){if(instance null){instance new Singleton();}return instance;} }懒汉式 - 线程安全在方法声明时加锁 这种方式具备很好的 lazy loading能够在多线程中很好的工作但是效率很低99% 情况下不需要同步。优点第一次调用才初始化避免内存浪费。缺点必须加锁 synchronized 才能保证单例但加锁会影响效率。getInstance() 的性能对应用程序不是很关键该方法使用不太频繁。 public class Singleton{private static Singleton instance;private Singleton(){}public static synchronized Singleton getInstance(){if(instance null){instance new Singleton();}return instance;} }双重检验锁(DCL) 这种方式采用双锁机制安全且在多线程情况下能保持高性能。 第一次判断是否为 null 第一次判断是在Synchronized同步代码块外理由是单例模式只会创建一个实例并通过 getUniqueInstance 方法返回 Singleton 对象所以如果已经创建了 Singleton 对象就不用进入同步代码块不用竞争锁直接返回前面创建的实例即可这样大大提升效率。 第二次判断 uniqueInstance 是否为 null 第二次判断原因是为了保证同步假若线程A通过了第一次判断进入了同步代码块但是还未执行线程B就进来了线程B获得CPU时间片线程B也通过了第一次判断线程A并未创建实例所以B通过了第一次判断准备进入同步代码块假若这个时候不判断就会存在这种情况线程B创建了实例此时恰好A也获得执行时间片如果不加以判断那么线程A也会创建一个实例就会造成多实例的情况。 public class Singleton {private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getUniqueInstance() {//先判断对象是否已经实例过没有实例化过才进⼊加锁代码if (uniqueInstance null){//类对象加锁synchronized (Singleton.class) {if (uniqueInstance null){uniqueInstance new Singleton();}}}return uniqueInstance;} } 饿汉式 - static final field 这种方式比较常用但容易产生垃圾对象。优点没有加锁执行效率会提高。缺点类加载时就初始化浪费内存。它基于 classloader 机制避免了多线程的同步问题不过instance 在类装载时就实例化虽然导致类装载的原因有很多种在单例模式中大多数都是调用 getInstance 方法 但是也不能确定有其他的方式或者其他的静态方法导致类装载这时候初始化 instance 显然没有达到 lazy loading 的效果。 public class Singleton{//类加载时就初始化private static final Singleton instance new Singleton();private Singleton(){}public static Singleton getInstance(){return instance;} }静态内部类 - static nested class 这种方式能达到双检锁方式一样的功效但实现更简单。对静态域使用延迟初始化应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况双检锁方式可在实例域需要延迟初始化时使用。 public class Singleton{private static class SingletonHolder(){private static final Singleton instance new Singleton();}private Singleton(){}public static final Singleton getInstance(){return SingletonHolder.instance;} }项目中一般我们项目里用静态内部类的方式实现单例会比较多如果没有Spring的环境下代码简洁易读 如果有Spring环境那还是直接交由Spring容器管理会比较方便Spring默认就是单例的 枚举 - Enum 这种实现方式还没有被广泛采用但这是实现单例模式的最佳方法。它更简洁自动支持序列化机制绝对防止多次实例化。 public class Singleton{private Singleton(){}/***枚举类型是线程安全的并且只会装载一次*/public enum SingletonEnum{INSTANCE;private final Singleton instance;SingletonEnum(){instance new Singleton();}private Singleton getInstance(){return instance;}}public static Singleton getInstance(){return SingletonEnum.INSTANCE.getInstance();} } 工厂模式 工厂模式最主要解决的问题就是创建者和调用者的耦合那么代码层面其实就是取消对new的使用。 Spring IOC容器可以理解为应用了「工厂模式」通过ApplicationContext或者BeanFactory去获取对象 简单工厂模式 也叫静态工厂模式 你要去买一台手机你不用关心手机是怎么生产出来的里面的零件具体又是怎么制造的这些通通都交给工厂去处理你尽管去买手机就好了。 问题 随着手机品牌增多工厂生产也需要对应的增加工厂内部就需要不断的调整。 从代码层面——对内部代码需要增加(也就是需要修改内部代码那么就会违反OOP原则—开闭原则一个软件实体应当对扩展开放对修改关闭 工厂方法模式 当新的手机品牌出现不是放在同一个工厂生产而是自己拥有独立工厂生产。那么就解决了上面静态工厂模式违反关闭原则的问题。 工厂方法模式解决简单工厂模式是需要付出代价的 看到上图工厂方法模式图里新增用虚线画的Huawei品牌每新增一个品牌就需要增加对应新的工厂会发现需要花费很大的成本现在才三个新的品牌那么等到十个、一百个的时候就会变得更加的复杂和难以维护。 抽象方法模式 在工厂方法模式中一个具体的工厂负责生产一类具体的产品即一对一的关系但是如果需要一个具体的工厂生产多种产品对象那么就需要用到抽象工厂模式了。 简单工厂 使用一个工厂对象用来生产同一等级结构中的任意产品。不支持拓展增加产品工厂方法 使用多个工厂对象用来生产同一等级结构中对应的固定产品。支持拓展增加产品抽象工厂 使用多个工厂对象用来生产不同产品族的全部产品。不支持拓展增加产品支持增加产品族 责任链模式 比如说我这边在处理请求的时候会用到责任链模式进行处理减免if else 并且让项目结构更加清晰 Jdk中的设计模式 JDK中涉及的设计模式总结-CSDN博客 MySQL 一、存储引擎 MySQL 支持哪些存储引擎 MySQL 5.5.5 前默认MyISAM后默认InnoDB只有 InnoDB 是事务性存储引擎也就是说只有 InnoDB 支持事务。 存储引擎架构 插件式架构支持多种存储引擎不同数据库表可以设置不同搜索引擎存储引擎是基于表的而不是数据库 MyISAM 和 InnoDB 区别7点 MyISAM 性能还行特性不错但不支持事务和行级锁最大的缺陷就是崩溃后无法安全恢复。 为什么InnoDB性能更强大 InnoDB 的性能比 MyISAM 更强大不管是在读写混合模式下还是只读模式下随着 CPU 核数的增加InnoDB 的读写能力呈线性增长InnoDB是支持高并发。MyISAM 因为读写不能并发是串行的它的处理能力跟核数没关系。 InnoDB是怎样支持高并发的 Innodb控制并发的手段有两个锁共享锁和排它锁读读并行 和 MVCC读写并行 二、索引 常见的索引结构有: B 树 B树 和 Hash、红黑树。在 MySQL 中无论是 Innodb 还是 MyIsam都使用了 B树作为索引结构。相当于数据的目录 1. 优缺点 优点 使用索引可以大大加快 数据的检索速度大大减少检索的数据量, 这也是创建索引的最主要的原因。通过创建唯一性索引可以保证数据库表中每一行数据的唯一性。 缺点 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候如果数据有索引那么索引也需要动态的修改会降低 SQL 执行效率。索引需要使用物理文件存储也会耗费一定空间。 2. 底层数据结构 BB树 为啥不用哈希表做MySQL索引数据结构 Hash 索引不支持顺序和范围查询并且每次 IO 只能取一个。试想SELECT * FROM tb1 WHERE id 500;树直接遍历比 500 小的叶子节点就够了哈希还要把1-499数据hash计算一遍来定位 B树和B树区别3点 B树比B树的优势 1.单一节点存储更多的元素使得查询的IO次数更少   2.所有查询都要查找到叶子节点查询性能稳定   3.所有叶子节点形成有序链表便于范围查询。 3. 索引类型 主键索引一张数据表只能有一个主键且不能为null不能重复 二级索引 二级索引的叶子节点存储的数据是主键而不是实际数据。也就是说通过二级索引可以定位主键的位置。分类 唯一索引属性列不能出现重复数据是一种约束不是为了查询效率普通索引唯一作用就是为了快速查询数据前缀索引只适用于字符串对文本前几个字符创建索引全文索引为了检索大文本数据中的关键字 聚簇索引非聚簇索引 聚簇索引data放数据非聚簇索引data放指向数据的指针示意图如下 聚簇索引 优点 查询速度非常快因为整个 B树本身就是一颗多叉平衡树叶子节点也都是有序的定位到索引的节点就相当于定位到了数据。相比于非聚簇索引 聚簇索引少了一次读取数据的 IO 操作。对排序查找和范围查找优化聚簇索引对于主键的排序查找和范围查找速度非常快。 缺点 依赖于有序的数据因为 B树是多路平衡树如果索引的数据不是有序的那么就需要在插入时排序如果数据是整型还好否则类似于字符串或 UUID 这种又长又难比较的数据插入或查找的速度肯定比较慢。更新代价大如果对索引列的数据被修改时那么对应的索引也将会被修改而且聚簇索引的叶子节点还存放着数据修改代价肯定是较大的所以对于主键索引来说主键一般都是不可被修改的。 非聚簇索引 优点 更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了非聚簇索引的叶子节点是不存放数据的 缺点 依赖于有序的数据跟聚簇索引一样非聚簇索引也依赖于有序的数据可能会二次查询(回表)这应该是非聚簇索引最大的缺点了。 当查到索引对应的指针或主键后可能还需要根据指针或主键再到数据文件或表中查询。 覆盖索引 如果一个索引包含或者说覆盖所有需要查询的字段的值我们就称之为 覆盖索引 InnoDB 存储引擎中如果不是主键索引叶子节点存储的是主键列值。最终还是要“回表”也就是要通过主键再查找一次这样就会比较慢。而覆盖索引就是把要查询出的列和索引是对应的不做回表操作 如下图查询的name字段就是索引查到name直接返回就行不用再找name对应的key [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uDgIIvmr-1686815973685)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20230601222840313.png)] 联合索引 使用表中的多个字段创建索引就是 联合索引比如将商品表中的 product_no 和 name 字段组合成联合索引(product_no, name)创建联合索引的方式如下 CREATE INDEX index_product_no_name ON product(product_no, name);最左前缀匹配原则在使用联合索引时MySQL会根据联合索引中的字段顺序从左到右依次到查询条件中去匹配。所以我们在使用联合索引时可以将区分度高的字段放在最左边这也可以过滤更多数据。 如果不遵循最左匹配原则联合索引会失效比如如果创建了一个 (a, b, c) 联合索引如果查询条件是以下这几种就可以匹配上联合索引 where a1where a1 and b2 and c3where a1 and b2 需要注意的是因为有查询优化器所以 a 字段在 where 子句的顺序并不重要。但是如果查询条件是以下这几种因为不符合最左匹配原则联合索引就会失效 where b2where c3where b2 and c3 上面这些查询条件之所以会失效是因为(a, b, c) 联合索引是先按 a 排序在 a 相同的情况再按 b 排序在 b 相同的情况再按 c 排序。所以b 和 c 是全局无序局部相对有序的这样在没有遵循最左匹配原则的情况下是无法利用到索引的。 以联合索引a,b为例B树叶子节点是双向链表如下 可以看到a 是全局有序的1, 2, 2, 3, 4, 5, 6, 7 ,8而 b 是全局是无序的12782381052。因此直接执行where b 2这种查询条件没有办法利用联合索引的利用索引的前提是索引里的 key 是有序的。 联合索引范围查询 并不是查询过程使用了联合索引查询就代表联合索引中的所有字段都用到了联合索引进行索引查询也就是可能存在部分字段用到联合索引的 BTree部分字段没有用到联合索引的 BTree 的情况。这种特殊情况就发生在范围查询 联合索引的最左匹配原则会一直向右匹配直到遇到「范围查询」就会停止匹配。也就是范围查询的字段可以用到联合索引但是在范围查询字段的后面的字段无法用到联合索引。 比如 select * from t_table where a 1 and b 2 a字段在联合索引的 BTree 中进行索引查询但是在符合 a 1 条件的二级索引记录的范围里b 字段的值是无序的a5,b8;a6,b10;a7,b5有 a 字段用到了联合索引进行索引查询而 b 字段并没有使用到联合索引 select * from t_table where a 1 and b 2 a字段变成大于等于a字段用联合索引虽然在a1条件的二级索引范围里b字段无序但是对于a1的二级索引范围里b字段有序因此可以通过a1时ab的联合索引减少需要扫描的二级索引范围a 和 b 字段都用到了联合索引进行索引查询 SELECT * FROM t_table WHERE a BETWEEN 2 AND 8 AND b 2 由于 MySQL 的 BETWEEN 包含 value1 和 value2 边界值所以类似于第2条查询语句a 和 b 字段都用到了联合索引进行索引查询 SELECT * FROM t_user WHERE name like j% and age 22联合索引name, age a 字段可以在联合索引的 BTree 中进行索引查询形成的扫描区间是[‘j’,‘k’)。 虽然在符合前缀为 ‘j’ 的 name 字段的二级索引记录的范围里age 字段的值是「无序」的但是对于符合 name j 的二级索引记录的范围里**age字段的值是「有序」**的 所以当二级索引记录的 name 字段值为 ‘j’ 时可以通过 age 22 条件减少需要扫描的二级索引记录范围因此age可以利用联合索引查询 a 和 b 字段都用到了联合索引进行索引查询 索引下推 索引下推是 MySQL 5.6 版本中提供的一项索引优化功能可以在非聚簇索引遍历过程中对索引中包含的字段先做判断过滤掉不符合条件的记录减少回表次数。 三、事务 1. ACID 原子性Atomicity 事务是最小的执行单位不允许分割。事务的原子性确保动作要么全部完成要么完全不起作用【undo log】 一致性Consistency 执行事务前后数据保持一致例如转账业务中无论事务是否成功转账者和收款人的总额应该是不变的【最终目的】 隔离性Isolation 并发访问数据库时一个用户的事务不被其他事务所干扰各并发事务之间数据库是独立的【锁MVCC】 持久性Durability 一个事务被提交之后。它对数据库中数据的改变是持久的即使数据库发生故障也不应该对其有任何影响。【redo log】 只有保证了事务的持久性、原子性、隔离性之后一致性才能得到保障。也就是说 A、I、D 是手段C 是目的 InnoDB 引擎通过什么技术来保证事务的这四个特性的呢 持久性是通过 redo log 重做日志来保证的原子性是通过 undo log回滚日志 来保证的隔离性是通过 MVCC 或锁机制来保证的一致性则是通过持久性原子性隔离性来保证 2. 并发事务问题 脏读Dirty read 某个事务已更新一份数据另一个事务在此时读取了同一份数据由于某些原因前一个RollBack了操作则后一个事务所读取的数据就会是不正确的 丢失修改Lost to modify 在一个事务读取一个数据时另外一个事务也访问了该数据那么在第一个事务中修改了这个数据后第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失因此称为丢失修改。 不可重复读Unrepeatable read 在一个事务的两次查询之中数据不一致这可能是两次查询过程中间插入了一个事务更新的原有的数据。 幻读Phantom read 在一个事务的两次查询中数据笔数不一致例如有一个事务查询了几列(Row)数据而另一个事务却在此时插入了新的几列数据先前的事务在接下来的查询中就会发现有几列数据是它先前所没有的。 3. 并发事务控制方式 锁 悲观控制模式 共享锁S 锁 又称读锁事务在读取记录的时候获取共享锁允许多个事务同时获取锁兼容。排他锁X 锁 又称写锁/独占锁事务在修改记录的时候获取排他锁不允许多个事务同时获取。如果一个记录已经被加了排他锁那其他事务不能再对这条记录加任何类型的锁锁不兼容。 MVCC 乐观控制模式 多版本并发控制MVCCMultiversion concurrency control 在 MySQL 中实现所依赖的手段主要是: 隐藏字段、read view、undo log。 undo log : undo log 用于记录某行数据的多个版本的数据。 read view 和 隐藏字段 : 用来判断当前版本数据的可见性。 RC和RR下 MVCC 的差异 在事务隔离级别 RC 和 RR InnoDB 存储引擎的默认事务隔离级别下InnoDB 存储引擎使用 MVCC非锁定一致性读但它们生成 Read View 的时机却不同 在 RC 隔离级别下的 每次select 查询前都生成一个Read View (m_ids 列表) 在 RR 隔离级别下只在事务开始后 第一次select 数据前生成一个Read Viewm_ids 列表 4. 隔离级别 读取未提交(READ-UNCOMMITTED) 最低的隔离级别允许读取尚未提交的数据变更可能会导致脏读、幻读或不可重复读。 读取已提交(READ-COMMITTED) 允许读取并发事务已经提交的数据可以阻止脏读但是幻读或不可重复读仍有可能发生。 可重复读(REPEATABLE-READ) 对同一字段的多次读取结果都是一致的除非数据是被本身事务自己所修改可以阻止脏读和不可重复读但幻读仍有可能发生。InnoDB 存储引擎默认隔离级别 可串行化(SERIALIZABLE) 最高的隔离级别完全服从 ACID 的隔离级别。所有的事务依次逐个执行这样事务之间就完全不可能产生干扰也就是说该级别可以防止脏读、不可重复读以及幻读。 隔离级别是怎么实现的 读取已提交 和 可重复读 隔离级别基于 MVCC可串行化 隔离级别锁 四、锁机制 1. 行级锁、表级锁 表级锁针对非索引字段对当前操作的整张表加锁 优点实现简单、资源消耗少、加锁快、不会出现死锁缺点触发锁冲突的概率最高高并发下效率极低 行级锁针对索引字段加的锁只针对当前操作的行记录进行加锁 优点大大减少db操作的冲突加锁粒度最小并发度高缺点加锁开销大加锁慢会出现死锁注意事项行锁针对索引字段加锁当我们执行 UPDATE、DELETE 语句时如果 WHERE条件中字段没有命中唯一索引或者索引失效的话就会导致扫描全表对表中的所有行记录进行加锁。有哪几类行锁 记录锁Record Lock单个行记录上的锁间隙锁Gap Lock锁定一个范围不包括记录本身临键锁Next-Key Lock Record LockGap Lock锁定一个范围包含记录本身主要目的是为了解决幻读问题MySQL 事务部分提到过。记录锁只能锁住已经存在的记录为了避免插入新记录需要依赖间隙锁。InnoDB 默认的隔离级别 可重复读 下行锁默认使用的是 Next-Key Lock。但是如果操作的索引是唯一索引或主键InnoDB 会对 Next-Key Lock 进行优化将其降级为 Record Lock即仅锁住索引本身而不是范围。 2. 共享锁、排它锁 共享锁S 锁 又称读锁事务在读取记录的时候获取共享锁允许多个事务同时获取锁兼容。 排他锁X 锁 又称写锁/独占锁事务在修改记录的时候获取排他锁不允许多个事务同时获取。如果一个记录已经被加了排他锁那其他事务不能再对这条记录加任何类型的锁锁不兼容。 S锁X锁S锁不冲突冲突X锁冲突冲突 3. 意向锁 作用如何判断表中的记录没有行锁呢一行一行遍历肯定是不行性能太差。用意向锁可以快速判断是否可以对某个表使用表锁 分类 意向共享锁Intention Shared LockIS 锁事务有意向对表中的某些记录加共享锁S 锁加共享锁前必须先取得该表的 IS 锁。意向排他锁Intention Exclusive LockIX 锁事务有意向对表中的某些记录加排他锁X 锁加排他锁之前必须先取得该表的 IX 锁。 意向锁是由数据引擎自己维护的用户无法手动操作意向锁在为数据行加共享/排他锁之前InooDB 会先获取该数据行所在在数据表的对应意向锁。 意向锁之间是兼容的 IS 锁IX 锁IS 锁兼容兼容IX 锁兼容兼容 意向锁和表级共享锁、表级排它锁互斥 IS 锁IX 锁S 锁兼容互斥X 锁互斥互斥 4. 当前读、快照读 快照读一致性非锁定锁就是单纯的 SELECT 语句但不包括下面这两类 SELECT 语句 SELECT ... FOR UPDATE SELECT ... LOCK IN SHARE MODE快照即记录的历史版本每行记录可能存在多个历史版本快照读的情况下如果读取的记录正在执行 UPDATE/DELETE 操作读取操作不会因此去等待记录上 X 锁的释放而是会去读取行的一个快照。什么时候用 只有在事务隔离级别 RC(读取已提交) 和 RR可重读下InnoDB 才会使用当前读 在 RC 级别下对于快照数据一致性非锁定读总是读取被锁定行的最新一份快照数据。在 RR 级别下对于快照数据一致性非锁定读总是读取本事务开始时的行数据版本。 适用的业务场景对于数据一致性要求不是特别高且追求极致性能 当前读 一致性锁定读就是给行记录加 X 锁或 S 锁 常见sql语句类型 # 对读的记录加一个X锁 SELECT...FOR UPDATE # 对读的记录加一个S锁 SELECT...LOCK IN SHARE MODE # 对修改的记录加一个X锁 INSERT... UPDATE... DELETE...5. 自增锁(了解) InnoDB 中的自增主键会涉及一种比较特殊的表级锁— 自增锁AUTO-INC Locks 五、日志 undo log回滚日志是 Innodb 存储引擎层生成的日志实现了事务中的原子性主要用于事务回滚和 MVCC。redo log重做日志是 Innodb 存储引擎层生成的日志实现了事务中的持久性主要用于掉电等故障恢复binlog 归档日志是 Server 层生成的日志主要用于数据备份和主从复制 1. undo log回滚 一个事务在执行过程中在还没有提交事务之前如果 MySQL 发生了崩溃要怎么回滚到事务之前的数据呢 怎么用undo log实现回滚 在插入一条记录时要把这条记录的主键值记下来这样之后回滚时只需要把这个主键值对应的记录删掉就好了在删除一条记录时要把这条记录中的内容都记下来这样之后回滚时再把由这些内容组成的记录插入到表中就好了在更新一条记录时要把被更新的列的旧值记下来这样之后回滚时再把这些列更新为旧值就好了。 undo log两大作用 实现事务回滚保障事务的原子性。 事务处理过程中如果出现了错误或者用户执行了 ROLLBACK 语句MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态。 实现 MVCC。 MVCC 是通过 ReadView undo log 实现的。undo log 为每条记录保存多份历史数据MySQL 在执行快照读普通 select 语句的时候会根据事务的 Read View 里的信息顺着 undo log 的版本链找到满足其可见性的记录。 RC隔离级别每个select生成一个Read View也意味着事务期间的多次读取同一条数据前后两次读的数据可能会出现不一致因为可能这期间另外一个事务修改了该记录并提交了事务。RR隔离级别启动事务时生成一个Read View整个事务期间都在用这个 Read View这样就保证了在事务期间读到的数据都是事务启动前的记录。 undo log如何刷盘持久化到磁盘 和数据页刷盘策略一样需要redo log保证持久化buffer pool中有undo页对undo页的修改会记录到redo log。redo log每秒刷盘提交事务也会刷盘undo log和数据页靠这个机制保证持久化 为什么需要Buffer pool Innodb 存储引擎设计了一个缓冲池Buffer Pool来提高数据库的读写性能 有了Buffer Pool以后 读取数据先找BP再找磁盘找到就读取修改数据BP中找到就直接修改BP中数据所在的页然后将其页设置为脏页为了减少磁盘IO不会立即将脏页写入磁盘后续由后台线程选择一个合适的时机将脏页写入到磁盘 BP缓存什么 以“页”为单位的原因InnoDB 会把存储的数据划分为若干个页以页作为磁盘和内存交互的基本单位一个页的默认大小为 16KB。因此Buffer Pool 同样按「页」来划分。在 MySQL 启动的时候InnoDB 会为 Buffer Pool 申请一片连续的内存空间然后按照默认的16KB的大小划分出一个个的页 Buffer Pool 中的页就叫做缓存页。BP存储内容Buffer Pool 除了缓存「索引页」和「数据页」还包括了 Undo 页插入缓存、自适应哈希索引、锁信息等等。 undo页记录什么 开启事务后InnoDB 层更新记录前首先要记录相应的 undo log如果是更新操作需要把被更新的列的旧值记下来也就是要生成一条 undo logundo log 会写入 Buffer Pool 中的 Undo 页面。 查询一条记录只需要缓冲一条记录吗 查询一条记录时InnoDB 是会把整个页的数据加载到 Buffer Pool 中将页加载到 Buffer Pool 后再通过页里的「页目录」去定位到某条具体的记录。 2. redo log重做 为什么需要redo log buffer pool提高读写效率 -- 但是BP基于内存 -- 内存不可靠断电重启没来得及落盘的脏页数据会丢失 -- 为了防止断电数据丢失的问题 -- 当一条记录需要更新时InnoDB引擎先更新内存标记脏页然后将对这个页的修改以redo log形式记录下来 -- 更新完成 redo log重做日志是InnoDB存储引擎独有的它让MySQL拥有了崩溃恢复能力。【持久性完整性】 什么是redo log 物理日志记录某个数据也做了什么修改每执行一个事务就会产生一条或者多条物理日志事务提交时先将redo log持久化到磁盘可以不用等buffer pool里的脏页数据持久化到磁盘系统崩溃时虽然脏页数据没持久化但是redo log持久化了因此mysql重启后根据redo log将所有数据恢复到最新状态 redo log 和 undo log 区别在哪 redo log 记录了此次事务「完成后」的数据状态记录的是更新之后的值undo log 记录了此次事务「开始前」的数据状态记录的是更新之前的值事务提交之前发生崩溃重启后通过undo log撤销事务事务提交之后发生崩溃重启后通过redo log重做事务。 刷盘时机 刷盘就是持久化到磁盘的意思… InnoDB 存储引擎为 redo log 的刷盘策略提供了 innodb_flush_log_at_trx_commit 参数它支持三种策略 0设置为 0 的时候表示每次事务提交时不进行刷盘操作1设置为 1 的时候表示每次事务提交时都将进行刷盘操作默认值2设置为 2 的时候表示每次事务提交时都只把 redo log buffer 内容写入 page cache 3. binlog归档 什么是binlog mysql完成一条更新语句之后server层还会生成一条binlog之后事务提交的时候会将该事务执行过程中产生的所有binlog统一写入binlog文件binlog文件记录了所有db 表结构变更 表数据修改的日志不会记录查询类的操作比如SELECT和SHOW操作 redo log 和 binlog 有什么区别 不小心删库能用redo log恢复数据吗 不能只能用binlog恢复因为redo log文件是循环写边写边擦除只记录未被刷入磁盘的物理日志刷过的会从redo log中删除binlog文件保存的是全量的日志即所有数据变更的情况理论上只要在binlog上的数据都可以恢复 Redis 为什么用redis做mysql缓存 1、高性能 MySQL从硬盘读取慢。将用户缓存数据存在redis中下次直接从缓存获取相当于直接操作内存。为啥这么快 基于内存访问速度是磁盘的上千倍基于reactor模式设计开发了一套高效事件处理模型主要是单线程事件循环和io多路复用内置多种优化后的数据结构实现 2、 高并发 单台设备的Redis的QPSQuery Per Second每秒钟处理完请求的次数 是 MySQL 的10倍直接访问 Redis 能够承受的请求远远大于直接访问 MySQL 数据结构 5 种基础数据结构 String字符串、List列表、Set集合、Hash散列、Zset有序集合。 3 种特殊数据结构 HyperLogLogs基数统计、Bitmap 位存储、Geospatial (地理位置)。 常见数据结构 底层数据结构 跳表 跳表skiplists是一种有序的数据结构它通过在每个节点中维持多个指向其他的节点指针从而达到快速访问队尾目的。 当数据量很大时跳表的查找复杂度就是 O(logN)。 压缩列表 压缩列表实际上类似于一个数组数组中的每一个元素都对应保存一个数据。和数组不同的是压缩列表在表头有三个字段 zlbytes、zltail 和 zllen分别表示列表长度、列表尾的偏移量和列表中的 entry 个数压缩列表在表尾还有一个 zlend表示列表结束。压缩列表将一些必要的偏移量信息记录在了每一个节点里使之能跳到上一个节点或下一个节点。在压缩列表中如果我们要查找定位第一个元素和最后一个元素可以通过表头三个字段的长度直接定位复杂度是 O(1)。而查找其他元素时就没有这么高效了只能逐个查找此时的复杂度就是 O(N) 了。 特殊数据结构 bitmap统计活跃用户 Bitmap 存储的是连续的二进制数字0 和 1通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte所以 Bitmap 本身会极大的节省储存空间。 HyperLogLog 统计页面 UV 线程模型 1. 单线程模式 2. 单线程 v.s 多线程 IO多路复用 IO多路复用是啥 IO 多路复用是一种同步IO模型实现一个线程可以监视多个文件句柄一旦某个文件句柄就绪就能够通知应用程序进行相应的读写操作没有文件句柄就绪就会阻塞应用程序交出CPU。多路是指网络连接复用指的是同一个线程 IO多路复用的三种机制都是系统调用 selectpollepoll poll 事件回调机制惊群现象 缓存设计 缓存读写策略 1. 旁路缓存模式 适用读请求比较多的场景服务端同时维系 db 和 cache以 db 的结果为准缓存读写步骤 写 先更新 db然后直接删除 cache 读 从 cache 中读取数据读取到就直接返回cache 中读取不到的话就从 db 中读取数据返回再把数据放到 cache 中 2. 读写穿透 适用读请求比较多的场景 原则应用程序只和缓存交互不再和数据库交互 读写穿透策略 3. 写回策略 适用写请求比较多的场景策略在更新数据的时候只更新缓存同时将缓存数据设置为脏的然后立马返回并不会更新数据库。对于数据库的更新会通过批量异步更新的方式进行。场景不能应用到我们常用的数据库和缓存的场景中是计算机体系结构中的设计比如 CPU 的缓存问题数据不是强一致性的而且会有数据丢失的风险 数据库和缓存一致性 1. 先更db还是先更缓存 先更db再更缓存 如下图更新顺序最终db存2缓存为1不一致 先更缓存再更db 如下图更新顺序最终db存1缓存为2不一致 无论是「先更新数据库再更新缓存」还是「先更新缓存再更新数据库」这两个方案都存在并发问题当两个请求并发更新同一条数据的时候可能会出现缓存和数据库中的数据不一致的现象。 2. 先更db还是先删缓存 阿旺定位出问题后思考了一番后决定在更新数据时不更新缓存而是删除缓存中的数据。然后到读取数据时发现缓存中没了数据之后再从数据库中读取数据更新到缓存中。 先更db再删缓存cache aside 旁路缓存策略 适用读请求比较多的场景服务端同时维系 db 和 缓存以 db 的结果为准缓存读写步骤 写策略 先更新 db然后直接删除 cache 读策略 从 cache 中读取数据读取到就直接返回cache 中读取不到的话就从 db 中读取数据返回再把数据放到 cache 中 先删缓存再更db 可以看到先删除缓存再更新数据库在「读 写」并发的时候还是会出现缓存和数据库的数据不一致的问题。 先更db再删缓存 「先更新数据库 再删除缓存」的方案是可以保证数据一致性的。 持久化机制 redis内存数据库但会把缓存数据存到硬盘 -自带两种持久化技术AOF日志RDB快照 -redis默认开启RDB快照重启redis之前的缓存数据会被重新加载 RDB快照 通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本AOF 文件的内容是操作命令RDB 文件的内容是二进制数据。 AOF日志 AOF工作流程 命令追加append写命令追加到AOF缓冲区文件写入writeAOF缓冲区-AOF文件文件同步fsync根据fsync策略同步硬盘文件重写rewriteAOF文件越来越大-定期重写达到压缩目的重启加载loadredis重启时加载AOF文件数据恢复 三种AOF持久化方式 这 3 种持久化方式的主要区别在于 fsync 同步 AOF 文件的时机刷盘。 appendfsync always主线程调用 write 执行写操作后后台线程 aof_fsync 线程立即会调用 fsync 函数同步 AOF 文件刷盘fsync 完成后线程返回这样会严重降低 Redis 的性能write fsync。appendfsync everysec主线程调用 write 执行写操作后立即返回由后台线程 aof_fsync 线程每秒钟调用 fsync 函数系统调用同步一次 AOF 文件writefsyncfsync间隔为 1 秒appendfsync no主线程调用 write 执行写操作后立即返回让操作系统决定何时进行同步Linux 下一般为 30 秒一次write但不fsyncfsync 的时机由操作系统决定。 怎么选择RDB还是AOF Redis 保存的数据丢失一些也没什么影响的话可以选择使用 RDB。 不建议单独使用 AOF因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。 如果保存的数据要求安全性比较高的话建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。 生产问题 缓存穿透 大量请求的 key 是不合理的根本不存在于缓存中也不存在于数据库中 。这就导致这些请求直接到了数据库上根本没有经过缓存这一层对数据库造成了巨大的压力可能直接就被这么多请求弄宕机了。 解决 非法请求的限制 业务层判断请求是否合法 缓存无效 key 如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间 布隆过滤器 把所有可能存在的请求的值都存放在布隆过滤器中当用户请求过来先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话直接返回请求参数错误信息给客户端存在的话才会走下面的流程。 缓存击穿 缓存击穿中请求的 key 对应的是 热点数据频繁被访问的数据 该数据 存在于数据库中但不存在于缓存中通常是因为缓存中的那份数据已经过期 。这就可能会导致瞬时大量的请求直接打到了数据库上对数据库造成了巨大的压力可能直接就被这么多请求弄宕机了。 解决 设置热点数据永不过期或者过期时间比较长由后台异步更新缓存。针对热点数据提前预热将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。互斥锁方案请求数据库写数据到缓存之前先获取互斥锁保证只有一个请求会落到数据库上减少数据库的压力。 缓存穿透和缓存击穿有什么区别 缓存穿透中请求的 key 既不存在于缓存中也不存在于数据库中。缓存击穿中请求的 key 对应的是 热点数据 该数据 存在于数据库中但不存在于缓存中通常是因为缓存中的那份数据已经过期 缓存雪崩 缓存在同一时间大面积的失效 or Redis故障宕机导致大量的请求都直接落到了数据库上对数据库造成了巨大的压力。 这就好比雪崩一样摧枯拉朽之势数据库的压力可想而知可能直接就被这么多请求弄宕机了。 解决 针对 Redis 服务不可用的情况 采用 Redis 集群避免单机出现问题整个缓存服务都没办法使用。限流避免同时处理大量的请求。 针对热点缓存失效的情况 设置不同的失效时间比如随机设置缓存的失效时间。缓存永不失效不太推荐实用性太差。设置二级缓存。 缓存雪崩和缓存击穿有什么区别 缓存雪崩和缓存击穿比较像但缓存雪崩导致的原因是缓存中的大量或者所有数据失效缓存击穿导致的原因主要是某个热点数据不存在与缓存中通常是因为缓存中的那份数据已经过期。 过期删除内存淘汰策略 过期删除 每当我们对一个 key 设置了过期时间时Redis 会把该 key 带上过期时间存储到一个过期字典expires dict中也就是说「过期字典」保存了数据库中所有 key 的过期时间。 当我们查询一个 key 时Redis 首先检查该 key 是否存在于过期字典中 如果不在则正常读取键值 如果存在则会获取该 key 的过期时间然后与当前系统时间进行比对如果比系统时间大那就没有过期否则判定该 key 已过期。 redis使用的过期删除策略是「惰性删除定期删除」这两种策略配和使用。 惰性删除不主动删除过期键每次从数据库访问 key 时都检测 key 是否过期如果过期则删除该 key。 优点因为每次访问时才会检查 key 是否过期所以此策略只会使用很少的系统资源因此惰性删除策略对 CPU 时间最友好。缺点如果一个 key 已经过期而这个 key 又仍然保留在数据库中那么只要这个过期 key 一直没有被访问它所占用的内存就不会释放造成了一定的内存空间浪费。所以惰性删除策略对内存不友好。 定期删除每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查并删除其中的过期key。 优点通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响同时也能删除一部分过期的数据减少了过期键对空间的无效占用。缺点难以确定删除操作执行的时长和频率。如果执行的太频繁就会对 CPU 不友好如果执行的太少那又和惰性删除一样了过期 key 占用的内存不会及时得到释放。 内存淘汰策略 相关问题MySQL 里有 2000w 数据Redis 中只存 20w 的数据如何保证 Redis 中的数据都是热点数据? 在设置了过期时间的数据中进行淘汰 volatile-lruleast recently used从已设置过期时间的数据集server.db[i].expires中挑选最近最少使用的数据淘汰。Redis3.0 之前默认的内存淘汰策略 volatile-lfuRedis 4.0 后新增的内存淘汰策略淘汰所有设置了过期时间的键值中最少使用的键值 volatile-ttl从已设置过期时间的数据集server.db[i].expires中挑选将要过期的数据淘汰。 volatile-random从已设置过期时间的数据集server.db[i].expires中任意选择数据淘汰。 所有数据范围内淘汰 allkeys-lruleast recently used当内存不足以容纳新写入数据时在键空间中移除最近最少使用的 key这个是最常用的。 allkeys-lfuRedis 4.0 后新增的内存淘汰策略淘汰整个键值中最少使用的键值。 allkeys-random从数据集server.db[i].dict中任意选择数据淘汰。 不进行数据淘汰 no-eviction禁止驱逐数据也就是说当内存不足以容纳新写入数据时新写入操作会报错。Redis3.0 之后默认的内存淘汰策略 数据结构 排序算法 快速排序 思想 用**“轴”**将序列划分成两部分轴左小于轴轴右大于轴再分别对左半部分和右半部分递归进行每一轮确定一个**“轴”**的最终位置 实现如果找第k大的就countnum.length-k时返回num[i]即第k大的 public class QuickSort {private static int count;//public static void main(String[] args) {int[] num {3,45,78,64,52,11,64,55,99,11,18};System.out.println(arrayToString(num,未排序));QuickSort(num,0,num.length-1);System.out.println(arrayToString(num,排序));System.out.println(数组个数num.length);System.out.println(循环次数count);}//快排private static void QuickSort(int[] num, int left, int right) {//如果left等于right即数组只有一个元素直接返回if(leftright) {return;}//设置最左边的元素为基准值int keynum[left];//数组中比key小的放在左边比key大的放在右边key值下标为iint ileft;int jright;while(ij){//j向左移直到遇到比key小的值while(num[j]key ij){j--;}//i向右移直到遇到比key大的值while(num[i]key ij){i;}//i和j指向的元素交换if(ij){int tempnum[i];num[i]num[j];num[j]temp;}}num[left]num[i];num[i]key;count;QuickSort(num,left,i-1);QuickSort(num,i1,right);}//数组转字符串private static String arrayToString(int[] arr,String flag) {String str 数组为(flag);for(int a : arr) {str a \t;}return str;} }计算机网络 输入url到页面展示 基础版本 解析URL生成 HTTP 请求消息DNS 域名解析将 HTTP 的传输工作交给操作系统的协议栈TCP 可靠传输IP 远程定位MAC 两点传输网卡将数字信号转化为电信号交换机转发包路由器转发网络包到下一个路由器或目标设备服务器接收数据响应数据包 HTTP(应用层) 基本概念 是什么超文本传输协议 超文本 超越普通文本的文本可能是文字、图片、视频等混合体HTML是最常见的超文本 传输 双向协议数据在A,B之间传输但允许中间有中转或接力AB可能是浏览器和百度网站HTTP 是一个在计算机世界里专门用来在两点之间传输数据的约定和规范。 协议 所以HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。 状态码 2xx 200OK请求被成功处理。204No Content服务端已经成功处理了请求但是没有返回任何内容。206Partial Content服务器已经成功处理了部分 GET 请求。 3xx 301永久重定向说明请求的资源已经不存在了需改用新的 URL 再次访问。302临时重定向说明请求的资源还在但暂时需要用另一个 URL 来访问。304未修改所请求的资源未修改服务器返回此状态码时不会返回任何资源。客户端通常会缓存访问过的资源通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源305使用代理所请求的资源必须通过代理访问307临时重定向与302类似。使用GET请求重定向 4xx 400Bad Request客户端请求的语法错误服务器无法理解401Unauthorized请求要求用户的身份认证403Forbidden服务器理解请求客户端的请求但是拒绝执行此请求404Not Found服务器无法根据客户端的请求找到资源网页405Method Not Allowed客户端请求中的方法被禁止 5xx 500Internal Server Error服务器内部错误无法完成请求501Not Implemented服务器不支持请求的功能无法完成请求502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时从远程服务器接收到了一个无效的响应503Service Unavailable由于超载或系统维护服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中504Gateway Time-out充当网关或代理的服务器未及时从远端服务器获取请求505HTTP Version not supported服务器不支持请求的HTTP协议的版本无法完成处理 常见字段 Host 字段客户端发送请求时用来指定服务器的域名Content-Length 字段服务器返回的数据长度Connection 字段长链接Keep-Aliveor notContent-Type 字段数据格式Content-Encoding 字段服务器返回数据的压缩格式gzip… GET和POST 区别 语义 GET 从服务器获取指定资源 POST 根据报文body处理指定资源 安全性不破坏服务器上资源 GET√因为GET只读 POST×因为POST会修改服务器资源 幂等执行多次操作结果相同 GET√只读所以服务器上数据每次结果都相同POST×多次提交数据就会创建多个资源 可被缓存 GET√POST× HTTP缓存技术 HTTPS 1.0 1.1 2.0 短连接浏览器和服务器每进行一次HTTP操作就建立一次连接但任务结束就中断连接 长连接建立SOCKET连接后不管是否使用都保持连接直到一方关闭连接 TCP保活机制通过在服务器端设置一个保活定时器当定时器开始工作后就定时的向网络通信的另一端发出保活探测的TCP报文如果接收到了ACK报文那么就证明对方存活可以继续保有连接否则就证明网络存在故障。TCP长连接是通过保活机制来实现的 HTTPS HTTPS加密过程 HTTPS加密过程 - 简书 (jianshu.com) 客户端发起HTTPS请求 用户在浏览器里输入一个HTTPS网址然后连接到服务端的443端口。 服务端的配置 采用HTTPS协议的服务器必须要有一套数字证书可以自己制作也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过才可以继续访问而使用受信任的公司申请的证书则不会弹出提示页面。证书其实就是一对公钥和私钥。 如果对公钥不太理解可以想象成一把钥匙和一个锁头只是世界上只有你一个人有这把钥匙你可以把锁头给别人别人可以用这个锁把重要的东西锁起来然后发给你因为只有你一个人有这把钥匙所以只有你才能看到被这把锁锁起来的东西。 给客户端传送证书 这个证书其实就是公钥只是包含了很多信息如证书的颁发机构过期时间等等。 【避免篡改】用哈希算法对报文提取定长摘要用私钥对摘要进行加密作为数字签名将数字签名附加到报文末尾发送给客户端 客户端解析证书 【避免篡改】用公钥对服务器的数字签名进行解密用同样的算法重新计算出报文的数字签名比较解密后的签名与自己计算的签名是否一致如果不一致说明数据被篡改过如果发现异常则会弹出一个警示框提示证书存在的问题。 如果证书没有问题那么就生成一个随机值。然后用证书也就是【公钥】对这个【随机值进行加密】。就好像上面说的把随机值用锁头锁起来这样除非有钥匙不然看不到被锁住的内容。 给服务端传送加密信息 传送的是用证书加密后的随机值目的是让服务端得到这个随机值以后客户端和服务端的通信就可以通过这个【随机值来进行加密解密】了。 服务端解密信息 服务端用私钥解密后得到了客户端传过来的随机值然后把内容通过该随机值进行对称加密将信息和私钥通过某种算法混合在一起这样除非知道私钥不然无法获取内容而正好客户端和服务端都知道这个私钥所以只要加密算法够彪悍私钥够复杂数据就够安全。 传输给客户端加密后的信息 服务端用私钥加密后的信息可以在客户端用随机值解密还原。 客户端解密信息 客户端用之前生产的私钥解密服务端传过来的信息于是获取了解密后的内容。整个过程第三方即使监听到了数据也束手无策。 「安全」的HTTP协议客户端与服务端的传输链路中进行加密 认证问题 保密问题 HTTPS的优缺点 优点相比于httphttps可以提供更加优质保密的信息保证了用户数据的安全性此外https同时也一定程度上保护了服务端使用恶意攻击和伪装数据的成本大大提高。缺点 https的技术门槛较高多数个人或者私人网站难以支撑CA机构颁发的证书都是需要年费的此外对接Https协议也需要额外的技术支持目前来说大多数网站并不关心数据的安全性和保密性其https最大的优点对它来说并不适用https加重了服务端的负担相比于http其需要更多的资源来支撑同时也降低了用户的访问速度目前来说Http网站仍然大规模使用在浏览器侧也没有特别大的差别很多用户不关心的话根本不感知。 TCP v.s UDP(传输层) 七点区别 UDP 一般用于即时通信比如 语音、 视频 、直播等等。【DNS】 TCP 用于对传输准确性要求特别高的场景比如文件传输、发送和接收邮件、远程登录等等。【FTP文件传输、HTTP/HTTPS】 TCP为什么可靠 校验和通过检验和的方式接收端可以检测出来数据是否有差错和异常假如有差错就会直接丢弃TCP段重新发送。序列号每一个连接都拥有不同的序列号。序列号的作用不仅可以应答有了序列号能够将接收到的数据根据序列号排序并且去掉重复序列号的数据。确认应答TCP 传输的过程中每次接收方收到数据后都会对传输方进行确认应答。也就是发送 ACK 报文。这个 ACK 报文当中带有对应的确认序列号告诉发送方接收到了哪些数据下一次的数据从哪里发。超时重传发送方在发送完数据后等待一个时间时间到达没有接收到 ACK 报文那么对刚才发送的数据进行重新发送。拥塞控制当网络拥塞时减少数据的发送。慢启动机制在启动初期以指数增长方式增长设置一个慢启动的阈值当以指数增长达到阈值时就停止指数增长按照线性增长方式增加至拥塞窗口线性增长达到网络拥塞时立即把拥塞窗口置回1进行新一轮的“慢启动”同时新一轮的阈值变为原来的一半。滑动窗口控制 三次握手建立TCP连接 三次握手干了啥 在最开始的时候客户端和服务端都处于 CLOSE 状态一次握手:客户端发送带有 SYNSEQx 标志的数据包 - 服务端然后客户端进入 SYN_SENT 状态等待服务器的确认二次握手:服务端发送带有 SYNACK(SEQy,ACKx1) 标志的数据包 – 客户端,然后服务端进入 SYN_RECV 状态三次握手:客户端发送带有 ACK(ACKy1) 标志的数据包 – 服务端然后客户端和服务器端都进入ESTABLISHED 状态完成TCP三次握手。 Linux怎么查看TCP状态 netstat -napt 命令 三报文而不是两报文四报文 阻止重复历史连接的初始化为了防止已失效的连接请求报文段历史连接突然又传送到了TCP服务器因而导致错误 不使用两次握手的原因在两次握手的情况下服务端没有中间状态给客户端来阻止历史连接导致服务端可能建立一个历史连接造成资源浪费 同步双方初始序列号 序列号的作用 接收方可以去除重复的数据接收方可以根据数据包的序列号按序接收可以标识发送出去的数据包中 哪些是已经被对方收到的通过 ACK 报文中的序列号知道 SYNACK一来一回才能保证双方的初始序列号能被可靠的同步四次握手SYN客-ACK服-SYN服-ACK客二三步可以合并为一步同时发送ACK和SYN所以三次握手就够了 避免资源浪费【不使用四次握手的原因】 为什么序列号是随机的 一方面为了安全性随机ISN能避免非同一网络的攻击另一方面可以让通信双方能够根据序号将「不属于」本连接的报文段丢弃 四次挥手断开TCP连接 四次挥手干了啥 在建立完连接之后客户端和服务端双方都处于 ESTABLISHED 状态第一次挥手 客户端发送一个 FINSEQX 标志的数据包-服务端用来关闭客户端到服务器的数据传送。然后客户端进入 FIN-WAIT-1 状态。【我没啥要说的了】第二次挥手 服务器收到这个 FINSEQX 标志的数据包它发送一个 ACK SEQX1标志的数据包-客户端 。然后此时服务端进入CLOSE-WAIT状态客户端进入FIN-WAIT-2状态。【我知道了可能还会有要说的话】第三次挥手 服务端关闭与客户端的连接并发送一个 FIN (SEQy)标志的数据包-客户端请求关闭连接然后服务端进入LAST-ACK状态。【B 可能又balabala最后说完了】第四次挥手 客户端发送 ACK (SEQy1)标志的数据包-服务端并且进入TIME-WAIT状态服务端在收到 ACK (SEQy1)标志的数据包后进入 CLOSE 状态。此时如果客户端等待 2MSL 后依然没有收到回复就证明服务端已正常关闭随后客户端也可以关闭连接了。【我知道了】 为啥需要四次 关闭连接时客户端向服务端发送 FIN 时仅仅表示客户端不再发送数据了但是还能接收数据。服务端收到客户端的 FIN 报文时先回一个 ACK 应答报文而服务端可能还有数据需要处理和发送等服务端不再发送数据时才发送 FIN 报文给客户端来表示同意现在关闭连接。服务端通常需要等待完成数据的发送和处理所以服务端的 ACK 和 FIN 一般都会分开发送因此是需要四次挥手。 第一次挥手丢失会发生什么 如果第一次挥手丢失了那么客户端迟迟收不到被动方的 ACK 的话也就会触发超时重传机制重传 FIN 报文重发次数由 tcp_orphan_retries 参数控制。 当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后就不再发送 FIN 报文则会在等待一段时间时间为上一次超时时间的 2 倍如果还是没能收到第二次挥手那么直接进入到 close 状态。 第二次挥手丢失会发生什么 ACK 报文是不会重传的所以如果服务端的第二次挥手丢失了客户端就会触发超时重传机制重传 FIN 报文直到收到服务端的第二次挥手或者达到最大的重传次数。达到最大重传次数后的处理和“第一次挥手丢失”相同。 第三次挥手丢失会发生什么 服务端收到第一次挥手后内核会自动回复ACK同时处于CLOSE_WAIT 状态即等待应用进程调用close函数关闭连接 服务端处于 CLOSE_WAIT 状态时调用了 close 函数内核就会第三次挥手同时连接进入 LAST_ACK 状态等待客户端返回 ACK 来确认连接关闭。 这时第三次挥手丢了迟迟收不到这个 ACK服务端就会重发 FIN 报文重发次数仍然由 tcp_orphan_retries 参数控制这与客户端重发 FIN 报文的重传次数控制方式是一样的。 第四次挥手丢失会发生什么 如果第四次挥手的 ACK 报文没有到达服务端服务端就会重发 FIN 报文重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。 举个例子假设 tcp_orphan_retries 为 2当第四次挥手一直丢失时发生的过程如下 客户端在收到第三次挥手后就会进入 TIME_WAIT 状态开启时长为 2MSL 的定时器如果途中再次收到第三次挥手FIN 报文后就会重置定时器当等待 2MSL 时长后客户端就会断开连接。 为什么 TIME_WAIT 等待的时间是 2MSL MSL 是 Maximum Segment Lifetime报文最大生存时间如果超过这个时间这个TCP报文就会被丢弃。TIME_WAIT状态为什么是2MSL的时长因为客户端不知道服务端是否能收到ACK应答数据包服务端如果没有收到ACK会进行重传FIN考虑最坏的一种情况第四次挥手的ACK包的最大生存时长(MSL)服务端重传的FIN包的最大生存时长(MSL)2MSL在 Linux 系统里 2MSL 默认是 60 秒那么一个 MSL 也就是 30 秒。 为什么需要 TIME_WAIT 状态 谁有这个状态主动发起关闭连接的一方所以客户端和服务端都有可能 为什么需要两点原因 保证让迟来的TCP报文有足够的时间被识别并丢弃 如下图SEQ 301 报文被网络延迟了接着服务端以相同的四元组重新打开了新连接前面被延迟的 SEQ 301 这时抵达了客户端会发生数据错乱等问题 保证「被动关闭连接」的一方能被正确的关闭 如下图四次挥手的最后⼀个 ACK 报文如果在网络中被丢失了此时如果客户端 TIME-WAIT 过短或没有则就直接进入了 CLOSED 状态了 按照 TCP 可靠性原则服务端会重发 FIN 报文发现对方关了客户端收到FIN返回RST报文解释为一个错误。 TIME_WAIT过多有什么危害 两种危害 占用系统资源比如文件描述符、内存资源、CPU 资源、线程资源等占用端口资源端口资源也是有限的一般可以开启的端口为 3276861000也可以通过 net.ipv4.ip_local_port_range参数指定范围。 客户端和服务端 TIME_WAIT 过多造成的影响不同 客户端TW过多占用端口资源占满所有端口资源就无法对【目的IP目的端口】都一样的服务端发起连接了服务端TW过多占用系统资源不会导致端口资源受限因为服务端只监听一个端口 服务器出现大量TIME_WAIT状态的原因 为什么会出现大量TWTIME_WAIT 状态是主动关闭连接方才会出现的状态所以如果服务器出现大量的 TIME_WAIT 状态的 TCP 连接就是说明服务器主动断开了很多 TCP 连接。什么场景下服务端会主动断开连接 HTTP 没有使用长连接HTTP 长连接超时HTTP 长连接的请求数量达到上限 TCP Keepalive 和 HTTP Keep-Alive HTTP 的 Keep-Alive是由应用层用户态 实现的称为 HTTP 长连接TCP 的 Keepalive是由 TCP 层内核态 实现的称为 TCP 保活机制 DNS域名-IP映射 应用层协议基于UDP协议之上 两种查询解析模式 迭代 主机-本地DNS-根服务器-TLD DNS-权威DNS-本地DNS-主机 递归 DNS在进行区域传输的时候使用TCP协议其它时候则使用UDP协议 一些DNS事务比如区域传输或其他附加查询可能会产生大于512字节的数据包因此使用TCP更加可靠使用TCP会减少丢包和重新发包的情况因此更加可靠与高效。DNS的规范规定了2种类型的DNS服务器一个叫主DNS服务器一个叫辅助DNS服务器。在一个区中主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时它需要与主DNS服务器通信并加载数据信息这就叫做区传送zone transfer。 操作系统 内核态和用户态 什么是内核态和用户态 用户态(User Mode) 用户态运行的进程可以直接读取用户程序的数据拥有较低的权限。啥时候进入当应用程序需要执行某些需要特殊权限的操作例如读写磁盘、网络通信等就需要向操作系统发起系统调用请求进入用户态。 内核态(Kernel Mode) 内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等不受限制拥有非常高的权限。啥时候进入当操作系统接收到进程的系统调用请求时就会从用户态切换到内核态执行相应的系统调用并将结果返回给进程最后再从内核态切换回用户态。 **为什么要设置这两个核态**只有内核态不行吗 在 CPU 的所有指令中有一些指令是比较危险的比如内存分配、设置时钟、IO 处理等如果所有的程序都能使用这些指令的话会对系统的正常运行造成灾难性地影响。因此我们需要限制这些危险指令只能内核态运行。这些只能由操作系统内核态执行的指令也被叫做 特权指令 。如果计算机系统中只有一个内核态那么所有程序或进程都必须共享系统资源例如内存、CPU、硬盘等这将导致系统资源的竞争和冲突从而影响系统性能和效率。并且这样也会让系统的安全性降低毕竟所有程序或进程都具有相同的特权级别和访问权限。同时具有用户态和内核态主要是为了保证计算机系统的安全性、稳定性和性能。 内核态和用户态如何切换 系统调用用户态主动切换到内核态的方式比如读取磁盘资源时。中断外围设备完成用户请求–(向CPU发出中断信号)–中断处理程序–假如之前是用户态程序则用户态转为内核态 比如硬盘读写操作完成异常用户态程序–(发生不可预知的异常比如缺页异常)–内核态 如何降低两个核态切换开销 为什么切换开销大用户态转化为内核态时需要执行系统调用保存现场也就是保存用户态的寄存器等 然后去系统调用并在内核态执行最后恢复现场。并且由于内核对于用户的不信任 因此内核需要对用户进行一些额外的检查这就需要耗费更多的工作了。(保存现场安全检查开销大) 降低进程通信的开销可以用共享内存方式进行进程通信 mmap函数使用 mmap 对文件进行读写操作时可以减少内存拷贝的次数并且可以减少系统调用的次数从而提高对读写文件操作的效率。 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);减少系统调用System Calls 系统调用是用户态和内核态之间的切换触发点。减少系统调用的次数可以减少内核态和用户态之间的切换开销。可以通过使用更高效的系统调用、批量操作和缓存数据等方式来减少系统调用次数。 使用轻量级的同步机制 同步机制如锁、信号量等会导致内核态和用户态之间的切换。使用轻量级的同步机制可以减少内核态和用户态之间的切换次数。例如使用非阻塞的自旋锁或无锁数据结构避免频繁的阻塞和唤醒操作。 使用事件驱动模型 事件驱动模型可以避免线程的阻塞和切换从而减少内核态和用户态之间的切换开销。通过使用异步IO、回调函数或事件循环机制可以使程序在一个线程内处理多个事件减少线程的创建和销毁从而降低切换开销。 使用线程池和协程 使用线程池和协程可以减少线程的创建和销毁从而减少内核态和用户态之间的切换次数。线程池可以复用线程减少线程创建的开销而协程则可以在一个线程内实现多个协程的切换避免线程的切换开销。 进程和线程 进程通信7种方式 常用的ipc划横线了 管道/匿名管道(Pipes)用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。 有名管道(Named Pipes) : 匿名管道由于没有名字只能用于亲缘关系的进程间通信。为了克服这个缺点提出了有名管道。有名管道严格遵循 先进先出(First In First Out) 。有名管道以磁盘文件的方式存在可以实现本机任意两个进程通信。 信号(Signal)信号是一种比较复杂的通信方式用于通知接收进程某个事件已经发生 消息队列(Message Queuing)消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道无名管道只存在于内存中的文件命名管道存在于实际的磁盘介质或者文件系统不同的是消息队列存放在内核中只有在内核重启(即操作系统重启)或者显式地删除一个消息队列时该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少管道只能承载无格式字 节流以及缓冲区大小受限等缺点。 信号量(Semaphores)信号量是一个计数器用于多进程对共享数据的访问信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。 共享内存(Shared memory)使得多个进程可以访问同一块内存空间不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作如互斥锁和信号量等。可以说这是最有用的进程间通信方式。【开销最小】 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元可以看做是不同主机之间的进程进行双向通信的端点简单的说就是通信的两方的一种约定用套接字中的相关函数来完成通信过程。 线程同步的4种方式 线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。 互斥锁Mutex采用互斥对象机制只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。读写锁reader-writer lock允许多个线程同时读取共享资源但只有一个线程可以对共享资源进行写操作。信号量semaphore它允许同一时刻多个线程访问同一资源但是需要控制同一时刻访问此资源的最大线程数量。屏障Barrier屏障是一种同步原语用于等待多个线程到达某个点再一起继续执行。当一个线程到达屏障时它会停止执行并等待其他线程到达屏障直到所有线程都到达屏障后它们才会一起继续执行。比如 Java 中的 CyclicBarrier 是这种机制。事件(Event) :Wait/Notify通过通知操作的方式来保持多线程同步还可以方便的实现多线程优先级的比较操作。 内存管理 虚拟内存管理方式 页式 段式 段页式 死锁 死锁多个进程/线程同时被阻塞它们中的一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞因此程序不可能正常终止。 产生死锁的四个必要条件 互斥资源处于非共享模式请求并保持请求别的资源得不到被阻塞自己获得的资源也不放手非抢占资源不能被抢占。我自己的资源只能用完我自己释放循环等待 解决死锁的方法 预防破坏四个必要条件之一通常是请求并保持or循环等待 破坏请求并保持预先静态分配一次申请完它需要的全部资源破坏循环等待顺序资源分配法规定每个进程必须按编号递增的顺序请求资源 避免银行家算法试探性分配资源安全性算法判断系统是否处于安全状态不安全则作废安全就分配检测用资源分配图有环路不一定死锁解除 结束所有进程重启OS撤销 all 死锁进程解除后继续运行逐个撤销死锁进程回收资源直到解除抢占资源 Spring 概述 Spring4.x主要模块 最重要Spring-Core主要提供 IoC 依赖注入功能的支持 模块Data Access/Integration中spring-orm 提供对 Hibernate、JPA 、iBatis 等 ORM 框架的支持。 Spring,Spring MVC,Spring Boot 之间什么关系? Spring MVC是Spring中很重要的模块核心思想是将业务逻辑、数据、显示分离来组织代码 MModel模型VView视图CController控制器 Spring Boot 简化了Spring配置如果需要构建 MVC 架构的 Web 程序还是需要使用 Spring MVC 作为 MVC 框架只是说 Spring Boot 帮你简化了 Spring MVC 的很多配置真正做到开箱即用 IoC(控制反转) IoCInversion of Control:控制反转 是一种设计思想而不是一个具体的技术实现。 为什么叫控制反转 控制 指的是对象创建实例化、管理的权力 反转 控制权交给外部环境Spring 框架、IoC 容器 SpringIOC有两个核心思想就是IOC控制反转和DI依赖注入 IOC 控制反转的基本思想是将原来的对象控制从使用者有了spring之后可以把整个对象交给spring来帮我们进行管理。 DI 依赖注入就是把对应的属性的值注入到具体的对象中。spring提供标签和Autowired和Resource注解等方式注入注入方式本质上是AbstractAutowireCapableBeanFactory的populateBean() 方法先从beanDefinition 中取得设置的property值*例如autowireByName方法会根据bean的名字注入autowireByType方法根据bean的类型注入完成属性值的注入涉及bean初始化过程。*对象会存储在map结构中在spring使用Map结构的singletonObjects存放完整的bean对象涉及三级缓存和循环依赖。整个bean的生命周期从创建到使用到销毁的过程全部都是由容器来管理涉及bean的生命周期。 IOC 容器底层就是对象工厂BeanFactory 接口。IOC的原理是基于xml解析、工厂设计模式、反射实现的。使用IOC可以降低代码的耦合度。 Component 和 Bean 的区别是什么 Component 注解作用于类而Bean注解作用于方法。Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中我们可以使用 ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中。Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,Bean告诉了 Spring 这是某个类的实例当我需要用它的时候还给我。Bean 注解比 Component 注解的自定义性更强而且很多地方我们只能通过 Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时则只能通过 Bean来实现。 Autowired 和 Resource 的区别是什么 Autowired 是 Spring 提供的注解Resource 是 JDK 提供的注解。Autowired 默认的注入方式为byType根据类型进行匹配Resource默认注入方式为 byName根据名称进行匹配。当一个接口存在多个实现类的情况下Autowired 和Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 Qualifier 注解来显式指定名称Resource可以通过 name 属性来显式指定名称。 Bean的生命周期 Bean 容器找到配置文件中 Spring Bean 的定义。Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。如果涉及到一些属性值 利用 set()方法设置一些属性值。如果 Bean 实现了 BeanNameAware 接口调用 setBeanName()方法传入 Bean 的名字。如果 Bean 实现了 BeanClassLoaderAware 接口调用 setBeanClassLoader()方法传入 ClassLoader对象的实例。如果 Bean 实现了 BeanFactoryAware 接口调用 setBeanFactory()方法传入 BeanFactory对象的实例。与上面的类似如果实现了其他 *.Aware接口就调用相应的方法。如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象执行postProcessBeforeInitialization() 方法如果 Bean 实现了InitializingBean接口执行afterPropertiesSet()方法。如果 Bean 在配置文件中的定义包含 init-method 属性执行指定的方法。如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象执行postProcessAfterInitialization() 方法当要销毁 Bean 的时候如果 Bean 实现了 DisposableBean 接口执行 destroy() 方法。当要销毁 Bean 的时候如果 Bean 在配置文件中的定义包含 destroy-method 属性执行指定的方法。 AoP(面向切面编程) AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关却为业务模块所共同调用的逻辑或责任例如事务处理、日志管理、权限控制等封装起来便于减少系统的重复代码降低模块间的耦合度并有利于未来的可拓展性和可维护性。 通俗描述不通过修改源代码方式在主干功能里面添加新功能 项目中怎么使用AOP 【Spring5框架学习】AOP权限判断模块 AOP的三种实现方式 通过Spring API实现AOP 通过编写增强类来继承Spring API提供的接口 通过自定义类来实现推荐 使用注解实现 自动装配原理 自动装配可以简单理解为通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。 1、main方法中SpringApplication.run(HelloBoot.class,args)的执行流程中有refreshContext(context)。 2、而这个refreshContext(context)内部会解析配置类上自动装配功能的注解EnableAutoConfiguration中的EnableAutoConfiguration中的引入类AutoConfigurationImportSelector。 3、AutoConfigurationImportSelector这个类中的方法SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()会读取jar包中的/项目中的META-INF/spring.factories文件。 4、spring.factories配置了自动装配的类最后根据配置类的条件自动装配Bean。 Sping中的设计模式 Spring使用工厂模式可以通过BeanFactory或ApplicationContext创建bean对象。单例模式Spring中bean的默认作用域就是singleton。代理设计模式Spring AOP就是基于动态代理的如果要代理的对象实现了某个接口那么Spring AOP会使用JDK Proxy去创建代理对象而对于没有实现接口的对象就无法使用JDK Proxy去进行代理了这时候Spring AOP会使用Cglib这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理。模板方法设计模式Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类它们就使用到模板模式。适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。 注解 常用注解 Spring Boot 常用注解汇总 - 云天 - 博客园 (cnblogs.com) 事务 Transactional 1. Spring事务介绍 我们系统的每个业务方法可能包括了多个原子性的数据库操作比如下面的 savePerson() 方法中就有两个原子性的数据库操作。这些原子性的数据库操作是有依赖的它们要么都执行要不就都不执行。 public void savePerson() {personDao.save(person);personDetailDao.save(personDetail); }事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元这个转账会涉及到两个关键操作就是 将小明的余额减少 1000 元。将小红的余额增加 1000 元。 万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障导致小明余额减少而小红的余额没有增加这样就不对了。事务就是保证这两个关键操作要么都成功要么都要失败。 2. spring事务管理方式 编程式事务管理少用 通过 TransactionTemplate或者TransactionManager手动管理事务实际应用中很少使用但是对于你理解 Spring 事务管理原理有帮助。 声明式事务管理推荐 推荐使用代码侵入性最小实际是通过 AOP 实现基于Transactional 的全注解方式使用最多。 使用 Transactional注解进行事务管理的示例代码如下 Transactional(propagation Propagation.REQUIRED) public void aMethod {//do somethingB b new B();C c new C();b.bMethod();c.cMethod(); }3. Spring事务管理接口 Spring 框架中事务管理相关最重要的 3 个接口如下 PlatformTransactionManager平台事务管理器Spring 事务策略的核心。事务上层的管理者 TransactionDefinition事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。 TransactionStatus事务运行状态。 PlatformTransactionManager事务管理接口 Spring 并不直接管理事务而是提供了多种事务管理器。 接口定义的三个方法 package org.springframework.transaction;import org.springframework.lang.Nullable;public interface PlatformTransactionManager {//获得事务TransactionStatus getTransaction(Nullable TransactionDefinition var1) throws TransactionException;//提交事务void commit(TransactionStatus var1) throws TransactionException;//回滚事务void rollback(TransactionStatus var1) throws TransactionException; }TransactionDefinition:事务属性 事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务这个方法里面的参数是 TransactionDefinition 类 这个类就定义了一些基本的事务属性。 事务属性事务的一些基本配置描述了事务策略如何应用到方法上。 事务属性包含5个方面 隔离级别传播行为回滚规则是否只读事务超时 在TransactionDefinition 接口中的实现 package org.springframework.transaction;import org.springframework.lang.Nullable;public interface TransactionDefinition {int PROPAGATION_REQUIRED 0;int PROPAGATION_SUPPORTS 1;int PROPAGATION_MANDATORY 2;int PROPAGATION_REQUIRES_NEW 3;int PROPAGATION_NOT_SUPPORTED 4;int PROPAGATION_NEVER 5;int PROPAGATION_NESTED 6;int ISOLATION_DEFAULT -1;int ISOLATION_READ_UNCOMMITTED 1;int ISOLATION_READ_COMMITTED 2;int ISOLATION_REPEATABLE_READ 4;int ISOLATION_SERIALIZABLE 8;int TIMEOUT_DEFAULT -1;// 返回事务的传播行为默认值为 REQUIRED。int getPropagationBehavior();//返回事务的隔离级别默认值是 DEFAULTint getIsolationLevel();// 返回事务的超时时间默认值为-1。如果超过该时间限制但事务还没有完成则自动回滚事务。int getTimeout();// 返回是否为只读事务默认值为 falseboolean isReadOnly();NullableString getName();}TransactionStatus:事务状态 TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。 PlatformTransactionManager.getTransaction(…)方法返回一个 TransactionStatus 对象。 public interface TransactionStatus{boolean isNewTransaction(); // 是否是新的事务boolean hasSavepoint(); // 是否有恢复点void setRollbackOnly(); // 设置为只回滚boolean isRollbackOnly(); // 是否为只回滚boolean isCompleted; // 是否已完成 }4. 事务属性 传播行为 事务传播行为是为了解决业务层方法之间互相调用的事务问题。 当事务方法被另一个事务方法调用时必须指定事务应该如何传播。例如方法可能继续在现有事务中运行也可能开启一个新事务并在自己的事务中运行。 事务传播行为种类 PROPAGATION_REQUIRED默认的使用最多。 如果当前存在事务则加入该事务如果当前没有事务则创建一个新的事务。 举个例子如果我们上面的aMethod()和bMethod()使用的都是PROPAGATION_REQUIRED传播行为的话两者使用的就是同一个事务只要其中一个方法回滚整个事务均回滚。 Service Class A {AutowiredB b;Transactional(propagation Propagation.REQUIRED)public void aMethod {//do somethingb.bMethod();} } Service Class B {Transactional(propagation Propagation.REQUIRED)public void bMethod {//do something} }PROPAGATION_REQUIRES_NEW创建一个新的事务如果当前存在事务则把当前事务挂起。也就是说不管外部方法是否开启事务Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务且开启的事务相互独立互不干扰。 举个例子如果我们上面的bMethod()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰aMethod还是用PROPAGATION_REQUIRED修饰的话。如果aMethod()发生异常回滚bMethod()不会跟着回滚因为 bMethod()开启了独立的事务。但是如果 bMethod()抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod()同样也会回滚因为这个异常被 aMethod()的事务管理机制检测到了。 Service Class A {AutowiredB b;Transactional(propagation Propagation.REQUIRED)public void aMethod {//do somethingb.bMethod();} }Service Class B {Transactional(propagation Propagation.REQUIRES_NEW)public void bMethod {//do something} }PROPAGATION_NESTED:如果当前存在事务就在嵌套事务内执行如果当前没有事务就执行与TransactionDefinition.PROPAGATION_REQUIRED类似的操作。 简单举个例子如果 bMethod() 回滚的话aMethod()不会回滚。如果 aMethod() 回滚的话bMethod()会回滚。 Service Class A {AutowiredB b;Transactional(propagation Propagation.REQUIRED)public void aMethod {//do somethingb.bMethod();} }Service Class B {Transactional(propagation Propagation.NESTED)public void bMethod {//do something} }.PROPAGATION_MANDATORY很少用。 如果当前存在事务则加入该事务如果当前没有事务则抛出异常。mandatory强制性 其他三种传播行为很少用。 项目中怎么用事务传播的 注册方法中有添加积分的方法添加积分执行失败回滚-不能使注册方法也回滚注册方法回滚-添加积分方法也要回滚注册方法Transactional添加积分Transactional(propagation Propagation.NESTED)太难了~面试官让我结合案例讲讲自己对Spring事务传播行为的理解。 (qq.com) 隔离级别 同mysql 超时属性 一个事务所允许执行的最长时间如果超过该时间限制但事务还没有完成则自动回滚事务。 只读属性 只读事务不涉及数据的修改数据库会提供一些优化手段适合用在有多条数据库查询操作的方法中。 回滚规则 这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下事务只有遇到运行期异常RuntimeException 的子类时才会回滚Error 也会导致事务回滚但是在遇到检查型Checked异常时不会回滚。 Transactional(rollbackFor MyException.class) 5. Transactional注解使用 作用范围 方法推荐将注解使用于方法上不过需要注意的是该注解只能应用到 public 方法上否则不生效。 类如果这个注解使用在类上的话表明该注解对该类中所有的 public 方法都生效。 接口不推荐在接口上使用。 常用配置参数 原理 面试中在问 AOP 的时候可能会被问到的一个问题。 Transactional 的工作机制是基于 AOP 实现的AOP 又是使用动态代理实现的。如果目标对象实现了接口默认情况下会采用 JDK 的动态代理如果目标对象没有实现了接口,会使用 CGLIB 动态代理。如果一个类或者一个类中的 public 方法上被标注Transactional 注解的话Spring 容器就会在启动的时候为其创建一个代理类在调用被Transactional 注解的 public 方法的时候实际调用的是TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务方法执行过程中如果遇到异常的时候回滚事务方法调用完成之后提交事务。 Transactional什么时候会失效 非public方法注解标注方法修饰符为非public时Transactional注解将会不起作用 在类内部调用调用类内部Transactional标注的方法若同一类中的其他没有 Transactional 注解的方法内部调用有 Transactional 注解的方法有Transactional 注解的方法的事务会失效。【AOP自调用问题】 这是由于Spring AOP代理的原因造成的因为只有当 Transactional 注解的方法在类以外被调用的时候Spring 事务管理才生效。 //MyService类中的method1()调用method2()就会导致method2()的事务失效。 Service public class MyService {private void method1() {method2();//...... } Transactionalpublic void method2() {//......} }3. **异常处理不当**事务方法内部捕捉了异常没有抛出新的异常导致事务操作不会进行回滚。4. **数据库不支持**如 MySql 的 MyISAM 引擎分布式 RPC RPCRemote Procedure Call 即远程过程调用用于解决分布式系统中服务之间的调用问题。 为什么要 RPC 因为两个不同的服务器上的服务提供的方法不在一个内存空间所以需要通过网络编程才能传递方法调用所需要的参数。并且方法调用的结果也需要通过网络编程来接收。 一言蔽之RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。 RPC 框架包含三个最重要的组件分别是客户端、服务端和注册中心。在一次 RPC 调用流程中这三个组件是这样交互的 服务端在启动后会将它提供的服务列表发布到注册中心客户端向注册中心订阅服务地址 客户端会通过本地代理模块 Proxy 调用服务端Proxy 模块收到负责将方法、参数等数据转化成网络字节流 客户端从服务列表中选取其中一个的服务地址并将数据通过网络发送给服务端 服务端接收到数据后进行解码得到请求信息 服务端根据解码后的请求信息调用对应的服务然后将调用结果返回给客户端。 注册中心 目前成熟的注册中心有ZookeeperNacosConsulEureka这里使用ZK作为注册中心没有提供切换以及用户自定义注册中心的功能。 Zookeeper分布式协调服务 ZooKeeper 可以被用作注册中心、分布式锁ZooKeeper 是 Hadoop 生态系统的一员构建 ZooKeeper 集群的时候使用的服务器最好是奇数台。 ZooKeeper 特点 顺序一致性 从同一客户端发起的事务请求最终将会严格地按照顺序被应用到 ZooKeeper 中去。原子性 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的也就是说要么整个集群中所有的机器都成功应用了某一个事务要么都没有应用。单一系统映像 无论客户端连到哪一个 ZooKeeper 服务器上其看到的服务端数据模型都是一致的。可靠性 一旦一次更改请求被应用更改的结果就会被持久化直到被下一次更改覆盖。 常见RPC框架 Dubbo Apache Dubbo 是一款微服务框架为大规模微服务实践提供高性能 RPC 通信、流量治理、可观测性等解决方案 涵盖 Java、Golang 等多种语言 SDK 实现。 分布式锁 分布式系统下不同的服务/客户端通常运行在独立的 JVM 进程上。如果多个 JVM 进程共享同一份资源的话使用本地锁就没办法实现资源的互斥访问了。于是分布式锁 就诞生了。 一个最基本的分布式锁需要满足 互斥任意一个时刻锁只能被一个线程持有高可用锁服务是高可用的。并且即使客户端的释放锁的代码逻辑出现问题锁最终一定还是会被释放不会影响其他线程对共享资源的访问。可重入一个节点获取了锁之后还可以再次获取锁。 基于Redis实现分布式锁 不论是本地锁还是分布式锁核心都在于“互斥”。 在 Redis 中 SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中的 setIfAbsent 方法)如果 key 不存在的话才会设置 key 的值。如果 key 已经存在 SETNX 啥也不做。 SETNX lockKey uniqueValue (integer) 1 SETNX lockKey uniqueValue (integer) 0释放锁的话直接通过 DEL 命令删除对应的 key 即可。 DEL lockKey (integer) 1为了防止误删到其他的锁这里我们建议使用 Lua 脚本通过 key 对应的 value唯一值来判断。 选用 Lua 脚本是为了保证解锁操作的原子性。因为 Redis 在执行 Lua 脚本时可以以原子性的方式执行从而保证了锁释放操作的原子性。 // 释放锁时先比较锁对应的 value 值是否相等避免锁的误释放 if redis.call(get,KEYS[1]) ARGV[1] thenreturn redis.call(del,KEYS[1]) elsereturn 0 end这是一种最简易的 Redis 分布式锁实现实现方式比较简单性能也很高效。不过这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉可能会导致锁无法被释放进而造成共享资源无法再被其他线程/进程访问。 基于ZooKeeper实现分布式锁 Redis 实现分布式锁性能较高ZooKeeper 实现分布式锁可靠性更高。 ZooKeeper 分布式锁是基于 临时顺序节点 和 Watcher事件监听器 实现的。 Linux 常用命令 ls   显示文件或目录-l 列出文件详细信息l(list)-a 列出当前目录下所有文件及目录包括隐藏的a(all)mkdir 创建目录-p 创建目录若无父目录则创建p(parent)cd 切换目录touch 创建空文件echo 创建带有内容的文件。cat 查看文件内容cp 拷贝mv 移动或重命名rm 删除文件-r 递归删除可删除子目录及文件-f 强制删除find 在文件系统中搜索某文件wc 统计文本中行数、字数、字符数grep 在文本文件中查找某个字符串rmdir 删除空目录tree 树形结构显示目录需要安装tree包pwd 显示当前目录ln 创建链接文件more、less 分页显示文本文件内容head、tail 显示文件头、尾内容ctrlaltF1 命令行全屏模式 系统管理命令 stat 显示指定文件的详细信息比ls更详细who 显示在线登陆用户whoami 显示当前操作用户hostname 显示主机名uname 显示系统信息top 动态显示当前耗费资源最多进程信息ps 显示瞬间进程状态 ps -auxdu 查看目录大小 du -h /home带有单位显示目录信息df 查看磁盘大小 df -h 带有单位显示磁盘信息ifconfig 查看网络情况ping 测试网络连通netstat 显示网络状态信息man 命令不会用了找男人 如man lsclear 清屏alias 对命令重命名 如alias showmeitps -aux 另外解除使用unaliax showmeitkill 杀死进程可以先用ps 或 top命令查看进程的id然后再用kill命令杀死进程。 gzipbzip2tar: 打包压缩-c 归档文件-x 压缩文件-z gzip压缩文件-j bzip2压缩文件-v 显示压缩或解压缩过程 v(view)-f 使用档名例tar -cvf /home/abc.tar /home/abc 只打包不压缩tar -zcvf /home/abc.tar.gz /home/abc 打包并用gzip压缩tar -jcvf /home/abc.tar.bz2 /home/abc 打包并用bzip2压缩当然如果想解压缩就直接替换上面的命令 tar -cvf / tar -zcvf / tar -jcvf 中的“c” 换成“x” 就可以了。查询进程占用内存 ps 和 top 命令常用来查看Linux系统进程相关信息。 **ps命令**可以查看进程的瞬间信息。 ps命令用于报告当前系统的进程状态。可以搭配kill指令随时中断、删除不必要的程序。ps命令是最基本同时也是非常强大的进程查看命令使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等总之大部分信息都是可以通过执行该命令得到的。 使用场景 ps -ef #显示所有当前进程 ps aux #显示所有当前进程 ps -ax #显示所有当前进程 ps -u pungki #根据用户过滤进程 ps -aux --sort -pcpu | less #根据 CPU 使用来升序排序 ps -aux --sort -pmem | less #根据用户过滤进程 ps -aux --sort -pcpu,pmem | head -n 10 #查询全10个使用cpu和内存最高的应用 ps -C getty #通过进程名和PID过滤 ps -f -C getty #带格式显示的通过进程名和PID过滤 ps -L 1213 #根据线程来过滤进程 ps -axjf或pstree #树形显示进程 ps -eo pid,user,args # 显示安全信息 ps -U root -u root u #格式化输出 root 用户真实的或有效的UID创建的进程**top命令**可以持续的监视进程的信息。 top命令用来显示执行中的程序进程使用权限是所有用户。 第一行表示的项目依次为当前时间、系统启动时间、当前系统登录用户数目、平均负载。 第二行显示的是所有启动的进程、目前运行的、挂起(Sleeping)的和无用(Zombie)的进程。 第三行显示的是目前CPU的使用情况包括系统占用的比例、用户使用比例、闲置(Idle)比例。 第四行显示物理内存的使用情况包括总的可以使用的内存、已用内存、空闲内存、缓冲区占用的内存。 第五行显示交换分区使用情况包括总的交换分区、使用的、空闲的和用于高速缓存的大小。 第六行显示的项目最多下面列出了详细解释 PID 进程id USER 进程所有者 PR 进程优先级 NI nice值。负值表示高优先级正值表示低优先级 VIRT 进程使用的虚拟内存总量单位kb。VIRTSWAPRES RES 进程使用的、未被换出的物理内存大小单位kb。RESCODEDATA SHR 共享内存大小单位kb S 进程状态。D不可中断的睡眠状态 R运行 S睡眠 T跟踪/停止 Z僵尸进程N表示该进程优先值是负数。 %CPU 上次更新到现在的CPU时间占用百分比 %MEM 进程使用的物理内存百分比 TIME 进程使用的CPU时间总计单位1/100秒 COMMAND 进程名称命令名/命令行银行 为什么选择来银行 银行是一个有稳定发展前景的行业而且在当今的经济环境下银行的角色非常重要。选择银行是因为希望能够在一个稳定的行业中工作同时为社会做出一些贡献。银行是一个注重团队合作和个人成长的行业。我希望能够在一个团结、相互协作的环境中工作学习和发展自己的能力不断提高自己的技能和经验为银行和自己的未来发展做出贡献。 我对银行的核心业务和金融知识有浓厚的兴趣。希望通过在银行的工作中深入了解银行的业务和金融知识并将其应用于日常工作中以提高自己的专业技能和知识水平。 call(“get”,KEYS[1]) ARGV[1] then return redis.call(“del”,KEYS[1]) else return 0 end img srchttps://img-blog.csdnimg.cn/020d53f90ce74a76b0e1638cfdc1b49d.png alt在这里插入图片描述 stylezoom:80%; /这是一种**最简易的 Redis 分布式锁实现**实现方式比较简单性能也很高效。不过这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉可能会导致锁无法被释放进而造成共享资源无法再被其他线程/进程访问。### 基于ZooKeeper实现分布式锁Redis 实现分布式锁性能较高ZooKeeper 实现分布式锁可靠性更高。ZooKeeper 分布式锁是基于 **临时顺序节点** 和 **Watcher事件监听器** 实现的。# Linux## 常用命令shell ls   显示文件或目录-l 列出文件详细信息l(list)-a 列出当前目录下所有文件及目录包括隐藏的a(all)mkdir 创建目录-p 创建目录若无父目录则创建p(parent)cd 切换目录touch 创建空文件echo 创建带有内容的文件。cat 查看文件内容cp 拷贝mv 移动或重命名rm 删除文件-r 递归删除可删除子目录及文件-f 强制删除find 在文件系统中搜索某文件wc 统计文本中行数、字数、字符数grep 在文本文件中查找某个字符串rmdir 删除空目录tree 树形结构显示目录需要安装tree包pwd 显示当前目录ln 创建链接文件more、less 分页显示文本文件内容head、tail 显示文件头、尾内容ctrlaltF1 命令行全屏模式 系统管理命令 stat 显示指定文件的详细信息比ls更详细who 显示在线登陆用户whoami 显示当前操作用户hostname 显示主机名uname 显示系统信息top 动态显示当前耗费资源最多进程信息ps 显示瞬间进程状态 ps -auxdu 查看目录大小 du -h /home带有单位显示目录信息df 查看磁盘大小 df -h 带有单位显示磁盘信息ifconfig 查看网络情况ping 测试网络连通netstat 显示网络状态信息man 命令不会用了找男人 如man lsclear 清屏alias 对命令重命名 如alias showmeitps -aux 另外解除使用unaliax showmeitkill 杀死进程可以先用ps 或 top命令查看进程的id然后再用kill命令杀死进程。 gzipbzip2tar: 打包压缩-c 归档文件-x 压缩文件-z gzip压缩文件-j bzip2压缩文件-v 显示压缩或解压缩过程 v(view)-f 使用档名例tar -cvf /home/abc.tar /home/abc 只打包不压缩tar -zcvf /home/abc.tar.gz /home/abc 打包并用gzip压缩tar -jcvf /home/abc.tar.bz2 /home/abc 打包并用bzip2压缩当然如果想解压缩就直接替换上面的命令 tar -cvf / tar -zcvf / tar -jcvf 中的“c” 换成“x” 就可以了。查询进程占用内存 ps 和 top 命令常用来查看Linux系统进程相关信息。 **ps命令**可以查看进程的瞬间信息。 ps命令用于报告当前系统的进程状态。可以搭配kill指令随时中断、删除不必要的程序。ps命令是最基本同时也是非常强大的进程查看命令使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等总之大部分信息都是可以通过执行该命令得到的。 使用场景 ps -ef #显示所有当前进程 ps aux #显示所有当前进程 ps -ax #显示所有当前进程 ps -u pungki #根据用户过滤进程 ps -aux --sort -pcpu | less #根据 CPU 使用来升序排序 ps -aux --sort -pmem | less #根据用户过滤进程 ps -aux --sort -pcpu,pmem | head -n 10 #查询全10个使用cpu和内存最高的应用 ps -C getty #通过进程名和PID过滤 ps -f -C getty #带格式显示的通过进程名和PID过滤 ps -L 1213 #根据线程来过滤进程 ps -axjf或pstree #树形显示进程 ps -eo pid,user,args # 显示安全信息 ps -U root -u root u #格式化输出 root 用户真实的或有效的UID创建的进程**top命令**可以持续的监视进程的信息。 top命令用来显示执行中的程序进程使用权限是所有用户。 第一行表示的项目依次为当前时间、系统启动时间、当前系统登录用户数目、平均负载。 第二行显示的是所有启动的进程、目前运行的、挂起(Sleeping)的和无用(Zombie)的进程。 第三行显示的是目前CPU的使用情况包括系统占用的比例、用户使用比例、闲置(Idle)比例。 第四行显示物理内存的使用情况包括总的可以使用的内存、已用内存、空闲内存、缓冲区占用的内存。 第五行显示交换分区使用情况包括总的交换分区、使用的、空闲的和用于高速缓存的大小。 第六行显示的项目最多下面列出了详细解释 PID 进程id USER 进程所有者 PR 进程优先级 NI nice值。负值表示高优先级正值表示低优先级 VIRT 进程使用的虚拟内存总量单位kb。VIRTSWAPRES RES 进程使用的、未被换出的物理内存大小单位kb。RESCODEDATA SHR 共享内存大小单位kb S 进程状态。D不可中断的睡眠状态 R运行 S睡眠 T跟踪/停止 Z僵尸进程N表示该进程优先值是负数。 %CPU 上次更新到现在的CPU时间占用百分比 %MEM 进程使用的物理内存百分比 TIME 进程使用的CPU时间总计单位1/100秒 COMMAND 进程名称命令名/命令行银行 为什么选择来银行 银行是一个有稳定发展前景的行业而且在当今的经济环境下银行的角色非常重要。选择银行是因为希望能够在一个稳定的行业中工作同时为社会做出一些贡献。银行是一个注重团队合作和个人成长的行业。我希望能够在一个团结、相互协作的环境中工作学习和发展自己的能力不断提高自己的技能和经验为银行和自己的未来发展做出贡献。 我对银行的核心业务和金融知识有浓厚的兴趣。希望通过在银行的工作中深入了解银行的业务和金融知识并将其应用于日常工作中以提高自己的专业技能和知识水平。
http://www.ho-use.cn/article/10820582.html

相关文章:

  • 自动化系统网站建设ui界面设计作品模板
  • 保洁公司网站源码自己建网站做那个模块好
  • 邯郸市住房和建设官方网站计算机网页设计实训报告
  • 服装 东莞网站建设WordPress文章摘要如何设置
  • 企智网络网站建设公司怎么利用网站做淘宝客
  • 网站怎么做分类聚合网站建设咨询有客诚信
  • 校园门户网站系统建设方案汕尾网站seo
  • 企业营销系统和网站建设wordpress首页文件
  • 做运营必看的网站视频网站应该怎么做
  • 怎么查网站是哪个建站公司做的外贸推广平台
  • iis7.5搭建网站Wordpress使用ldap
  • 找别人做网站房地产销售好做吗
  • 新建门户网站的建设自查郑州网站定制
  • 网站怎么吸引用户成都微信小程序开发
  • dedecms织梦和wordpress网站seo快速排名优化
  • 写小说的网站自己做封面做网站遇上麻烦客
  • 网站内容描述官方查企业信息的网站
  • 网站设计标杆企业零基础学习做网站
  • 泰州做网站淘宝长沙百度网站排名优化
  • 学校学院网站建设目标专业旅游网站建设
  • 安庆市网站建设公司百度开店怎么收费
  • 企业网站建设中在方案设计上深圳宝安区区号
  • 惠州网站建设设计北流网站建设制作
  • 诸暨做网站虚拟主机装wordpress
  • 贵阳企业自助建站系统成都企业网站制作
  • 网站seo和sem是什么意思建设网站的平台
  • 开网站建设公司心得wordpress获取用户昵称
  • 什么网站的页面好看酒水包装设计公司
  • 专业网站建设在哪里大连高新园区教育局
  • 网站制作 天津小偷程序做的网站能用吗