Java总结 - 问题总结

文章目录

- - -数据库- - -

1 MySQL知识拓扑

a

2 自定义数据库

  程序实例   存储管理 缓存机制 SQL解析 日志管理
         权限划分 容灾机制 索引管理 锁管理
  存储(文件系统)

  各模块功能 存储管理:即文件转内存
        缓存机制:数据缓存与淘汰机制
        SQL解析:解析SQL指令
        日志管理:操作日志,方便回滚
        权限划分:不同用户数据权限不同
        索引管理:提高查询效率
        锁管理:支持并发

3 索引

3.1 为啥使用索引

  提高查询效率

3.2 什么信息可作为索引

  尽量能表征数据的唯一性的信息,如主键

3.3 索引数据结构

  二叉排序树、B树、B+树、Hash结构、位图
  B+树:① 磁盘读写代价低;② 效率稳定;③ 有利于数据扫描(叶子节点指针)

3.4 密集索引和稀疏索引

  密集索引,每个数据均有索引
  稀疏索引,部分数据有索引

3.5 聚簇索引和非聚簇索引

  聚簇索引,叶子节点存数据,InnoDB主键索引为聚簇索引
  非聚簇索引,叶子节点存数据指针,InnoDB非主键索引为非聚簇索引

3.6 慢SQL定位与优化

  慢日志 -> explain -> 修改sql尽量走索引
  慢日志参数:show_query_log、show_query_log_file、long_query_time
  explain关键字段:type(…range>index>all)、Extra、rows、key
  select count(id) from table1 不一定走主键索引

3.7 联合索引最左匹配原则

  key(name,sex,age)
  最左匹配原则:select * from table where name = … and sex = … and age = …

3.8 索引建立越多越好吗

  数据量小无需索引
  维护索引有成本

4 锁

4.1 MyISAM和InnoDB关于锁方面的区别

  MyISAM支持表锁
  InnoDB支持行锁,走索引时锁表中某一行,不走索引时锁表

  MyISAM适合场景:频繁执行全表、无事务、增删改少、查询多
  InnoDB适合场景:增删改查均频繁、有事务、可靠性高

  锁划分 按粒度划分:行锁、表锁、页锁
      按级别划分:共享锁、排他锁
      按使用方式划分:乐观锁、悲观锁
      按加锁方式划分:自动锁、显式锁

4.2 数据库事务四大特性

  事务特性:原子性、一致性、隔离性、持久性(原一隔持/ACID)

4.3 事务的隔离级别以及各级别下并发访问问题

  隔离级别:RU(未提交读)、RC(已提交读)、RR(可重复读)、S(串行化)
  访问问题:脏读、不可重复读、幻读(简称脏 不 幻)
  脏  读:事务1读取到事务2未提交的数据
  不可重复读:事务1多次读取某个数据不一致
  幻  读:事务1按一定条件读取数据,返回数据条数不同
  隔离级别与问题:RU(脏不幻)、RC(不幻)、RR(幻)、S(无)

4.4 InnoDB中RR隔离级别下如何避免幻读

  表象:快照读(MVCC)
  内在:当前读中是范围锁(行锁+间隙锁) gap锁(间隙锁) next-key锁(范围锁)

  当前读:select … lock in share mode
      select … for update
      update …
      delete …
  快照读:select … from table

  快照读使用情况
    ① 快照读存在于RC、RR级别下
    ② RC级别下每次select均重新建立快照
    ③ RR级别下首次select建立快照,update会重新建立快照

  Gap锁使用情况:Gap锁存在于RR级别下

  问题:对主键索引、唯一索引会使用间隙锁吗?
     ① 若where条件未全部命中,则使用间隙锁,锁区间段
     ② 若查询走非唯一索引或不走索引,则使用间隙锁,锁全表
     ③ 参见"next-key个人总结"

4.5 RC、RR级别下InnoDB非阻塞读如何实现

  数据有唯一行号、事务ID、回滚指针3个隐藏字段
  undo日志
  快照(可见性算法,根据事务ID)

4.6 next-key个人总结

  next-key加锁范围是一个左开右闭/( ]的区间
  主键索引、唯一索引、普通索引情况如下:
    ① 范围查询时,边界包含的区间会被加next-key锁
    ② 等值查询时,主键索引和唯一索引命中则退化为行锁,未命中则退化为间隙锁
    ③ 等值查询时,普通索引命中或未命中均退化为间隙锁
  无索引则锁全表

5 关键语法

6 问题收集

1. InnoDB存储引擎,三层B+树,单表能存储多少数据
  MySQl存储单元为1页,1页为16K,即16384Byte
  非叶子节点存储单元为主键+指针,大小为8+6=14Byte
  单页可存储16384/14 ≈ 1170个单元,即可存储1170个子节点
  每个存储单元可标记1页,则前两层可标记1170x1170页
  第三层叶子节点存储数据,每条数据约1K,单页则可存储16/1=16个数据
  三层 B+树,单表可存数据约为1170x1170x16=21902400条数据
2. between and查询,MySQL如何优化查询速度
  假设针对where条件列已建立索引
  扫描索引树时,获取最大值和最小值
  然后从叶子结点的链表读取数据
3. 覆盖索引与回表
  假设有表t(id PK, name KEY, sex, flag),其中,id是聚集索引,name是普通索引,表中有4条数据(1, shenjian, m, A)、(3, zhangsan, m, A)、(5, lisi, m, A)、(9, wangwu, f, B),若有查询操作“select * from t where name=‘lisi’”,则执行如下图
a
  如上图粉红色路径,先定位主键值,再定位行记录,即回表操作,性能相较扫一遍索引树更低;覆盖索引将被查询的字段,建立到联合索引里去,不需要回表,一次扫描索引即可获得数据。
4. SQL执行慢,如何优化
  见MySQL知识拓扑图
5. 索引下推
  
6. SQL优化
  不使用前导模糊
  不使用负向条件
  between放最后
  索引列不做操作
  联合索引遵循最左匹配原则
  …

- - -Redis- - -

1 Redis简介

1.1 系统常用存储模型

           穿透->
  客户端<-----> 缓存<-----------> 存储
           <-回种

1.2 Redis特点

  数据类型丰富
  支持数据持久化存储到磁盘
  支持主从
  支持分片集群

1.3 为什么Redis能这么快

  速度:100000+QPS[QPS,秒查询次数]
  原因:绝大部分为内存操作,无IO操作,执行效率高
     数据结构简单,数据操作简单
     采用单线程处理高并发请求,多核也可启动多实例(主线程为单线程,包括IO处理、IO请求、过期键等,无并发问题)
     使用多路复用IO模型,非阻塞IO

1.4 Redis快个人总结

  纯内存操作,速度快
  单线程处理主业务,避免线程切换与并发产生的开销
  IO多路复用(Linux底层)实现非阻塞IO
  注:多进程/线程可提高系统稳定性,不一定提高系统响应速度(比如单一业务场景和单核机器)
    Redis主业务:接收客户端请求 -> 解析请求 -> 数据读写 -> 返回给客户端

1.5 多路IO复用模型

  FD文件描述符
  阻塞IO、多路复用IO模型、select系统调用
  多路复用函数:epoll、kqueue、evport、select
  多路复用函数采用策略: 因地制宜
              优先选择时间复杂度为O(1)的函数
              以时间复杂度为O(n)的select保底
              基于react设计模式监听IO事件

2 Redis数据类型

  String,字符串
  Hash,String类型的字典,适合存储对象
  List,String类型的列表,保持插入顺序
  Set,String类型的哈希表,去重
  Sort Set(ZSet),通过分数为成员进行排序(增序),score(double)
  HyperLogLog,用于计数
  Geo,存储地理信息

  简单动态字符串、链表、字典、跳跃表、整数集合、存储列表、对象

3 海量数据查询某一固定前缀的key

  KEYS pattern 查找所存符合给定模式pattern的key
  keys指令一次性返回所有的key
  键数量大会使服务卡顿

  SCAN cursor [MATCH pattern] [Count count]
  基于游标的迭代器
  以0作为游标开始新一轮迭代
  不保证每次执行都返回给定模式的元素,支持模糊查询
  每次返回数量不可控
  返回key可能重复

4 如何实现分布式锁

  分布式锁特点:互斥性、安全性、死锁与容错
  分布式锁实现:
    ① SETNX key value,如果key不存在则创建显式锁
    ② EXPIRE key seconds,解决key长期有效问题,过期自动删除,缺乏原子性
    ③ SET key value [EX seconds] [Px milliseconds] [NX|XX],返回为OK/nil
    ④ 大量key同时过期,则会出现短时间内卡顿,设置随机数作为有效期

5 如何实现异步队列

  List做队列,RPUSH生产消息,LPOP消费消息
  缺点:没有等待队列,有则直接消费
  措施:在应用程序中,引入sleep,调用LPOP重试

  BLPOP key[key …] timeout:阻塞到队列有消息或超时
  只供一个消费者消费

  pub/sub: 主题订阅模式
       发送者发生消息,订阅者接收消息
       订阅者可订阅多个频道(Topic)
       Subscribe myTopic
       Publish myTopic “hello”

6 RDB持久化

  RDB持久化:保存某个时间点的全量数据快照
  SAVE:阻塞Redis,创建RDB文件完毕
  BGSAVE:fork子进程来创建RDB,不阻塞服务
  BGSAVE原理:系统调用fork()创建子进程,实现copy-on-write
  RDB持久化缺点:大量数据全量同步会影响性能
          挂死会丢失最近一次快照之后的数据

  RDB持久化配置:save 900 1
          save 300 10
          save 60 1000
          stop-wriles-on-bgsave-error yes
          rdbcompresson yes

  自动触发RDB持久化方式: 根据redis.conf中save * * 定时触发(用BGSAVE)
               主从复制时,主节点自动触发
               执行Debug Reload
               执行shutdown 且没有开启AOF持久化

7 AOF持久化及混合持久化

7.1 AOF持久化

  AOF持久化(保存写指令): 记录除查询外的所有变更数据库的指令
              以append形式追加到AOF文件中
  AOF相关配置:appendonly yes/on
         appendfilename
         appendfsyre everysec/aways/no

  RDB优缺点:全量数据快照,文件小,恢复快
        无法保存最近一次快照之后的数据
  AOF优缺点:可靠性好,适合保存增量数据,不易丢失
        文件体积大,恢复慢

7.2 混合持久化

  BGSAVE保存全量持久化数据,AOF做增量持久化
  Redis数据恢复,先恢复RDB,然后重放AOF文件

8 Pipeline及主从同步

8.1 Pipeline

  忽略

8.2 主从同步

  架构:一主多从,主(Master)负责写,从(Slave)负责读
  过程:主从同步由Slave节点发起,Master生成RDB文件发送到Slave,生成RDB文件之后的操作记为AOF,发送到Slave
  特点:弱一致性

  全量同步:Slave发送sync命令到Master
       Master使用BGSAVE命令生成RDB文件
       Master使用AOF文件记录BGSAVE之后的写指令
       BGSAVE完成后,发送到Slave,Slave加载RDB
       Slave继续加载AOF

  增量同步:Master收到增量同步
       将写指令记录到AOF文件
       将AOF文件发生到Slave,Slave加载

9 Sentinel(哨兵)

  解决主从同步Master宕机后主从切换问题
  Sentinel进程的作用: 监控主服务器、从服务器是否正常
            通过API向管理员发送故障通知
            自动故障迁移,主从切换(Slave升级为Master)

10 Redis集群

  从海量数据快速找到所需
  分片:将数据按某种规则划分,实现存储在多个节点
  常规Hash算法无法实现节点动态增删

一致性Hash算法
  一致性Hash算法:对232取模,对Hash值空间组织圆环
a
Hash槽
  Redis集群未使用一致性哈希,而是引入哈希槽(hash slot)来实现使用数据分片

- - -Java底层-JVM- - -

1 谈谈对Java的理解

  平台无关性(一次编译、到处运行)
  GC(垃圾回收机制)
  语言特性(泛型、反射、Lambada表达式)
  面向对象(封装、继承、多态)
  类库(集合、并发库、网络库、IO库等)
  异常处理

2 平台无关性

  编译:源码 -> 字节码
  运行:字节码 -> 机器码
  源码 -----> 字节码 -----> 机器码
     编译   虚拟机适配不同平台

为啥JVM不直接将源码解析为机器码
  每次执行需要检查
  也可解析执行其他语言编译的字节码

3 JVM如何加载class文件

a
  Runtime Data Area:JVM内存模型
  Class Loader:类加载器
  Execution Engine:解析命令
  Native Interface:融合不同开发语言的原生库,为Java所用

4 什么是反射

  定义:运行状态中,对于任意类、任意对象均可获取其方法、属性,并动态调用
  获取字节码对象:Class.forName(“全限定类名”)…
  获取方法:getMethods()、getDeclaredMethod()…
  获取属性:getFields()、 getDeclaredFields()…
  调用方法:invoke(实例,参数数据)

5 谈谈ClassLoader

  ClassLoader:类加载器,将二进制字节码加载到JVM
  ClassLoader.loadClass()方法
  ClassLoader种类:
    ① Bootstrap Class Loader(启动类加载器)
    ② Extension Class Loader(扩展类加载器)
    ③ Application Class Loader(应用程序类加载器)
    ④ User Class Loader(自定义类加载器)

  自定义类加载器实现关键方法:findClass()、defineClass()

6 ClassLoader的双亲委派机制

      ↑  Bootstrap Class Loader    ↓
      ↑  Extension Class Loader    ↓
      ↑  Application Class Loader  ↓
      ↑  User Class Loader       ↓
自下而上检查类是否已加载    自上而下尝试加载类

为什么使用双亲委派机制加载类
  运行过程中,保证字节码的唯一性

7 loadClass和Class.forName的区别

  类加载方式
    ① 隐式加载:new
    ② 显示加载:loadClass、Class.forName等
  类装载过程
    1 加载: 通过ClassLoader加载Class字节码,生成Class对象
    2 链接: 校验:检查加载Class文件正确性和安全性
        准备:为类变量分配存储空间并初始化
        解析:JVM将常量池内的符号引用转为直接引用
    3 初始化: 执行类变量赋值和静态代码块

  Class.forName得到的Class已经完成初始化
  classLoader.loadClass得到的CLass是没有链接的

8 Java内存模型之线程独占部分

  JDK8 内存布局,私有包括虚拟机栈、本地方法栈、程序计数器,公有包括元空间、堆
a
  程序计数器:当前线程执行字节码的行号
        改变计数器的值,选取下一条待执行的字节码
        和线程一一对应,即线程私有
        若为Native方法,计数器值为undifined
        不会发送内存溢出
  虚拟机栈:Java方法执行的内存模型
       包含多个栈帧,局部变量表、操作数栈
  本地方法栈:与虚拟机栈类似,主要作用于Native方法

9 Java内存模型之线程共享部分

元空间(MetaSpace)和永久代(PermGen Space)的区别
  元空间使用本地内存,永久代使用JVM堆内存

元空间相比永久代的优势
  字符串常量池存储在永久代中,容易出现性能问题和内存溢出
  类信息和方法信息大小难以确定,不利于永久代空间大小指定
  永久代会给GC带来复杂
  方便HotSpot与JVM(Jrockit)的集成

  Java堆:对象实例分配的区域
      GC管理的主要区域
      新生代分为 Eden:From:To = 8:1:1

10 Java内存模型之常见题

JVM三大性能调优参数-Xms、-Xmx、-Xss的含义
  -Xss:规定线程虚拟机堆栈的大小(256K足够),该数可影响并发线程数
  -Xms:堆初始值(进程创建时堆的大小)
  -Xmx:堆最大值,Xms和Xms可设置为相同(内存扩大时会发生内存抖动,影响程序稳定)
Java内存模型中、堆和栈的区别-内存分配策略
  静态存储:编译时即可确定运行时存储空间
  栈式存储:存储空间需求编译时未知,运行时进入模块前可确定
  堆式存储:编译或运行时进入模块前均不可确定,动态分配
Java内存模型中、堆和栈的区别
  管理方式:栈自动释放、堆需要GC
  空间大小:栈比堆小
  碎片相关:栈产生碎片远小于堆
  分配方式:栈支持静态和动态分配,堆只支持动态分配
  效率:栈效率比堆高
元空间、堆、线程独占部分之间的联系-内存角度
  元空间:存储类型信息、类对象方法、属性
  堆:存储对象实例
  栈:存储局部变量、引用变量、行号
不同JDK版本之间的intern方法的区别-JDK6&JDK6以上
  略

- - -Java底层-GC- - -

1 Java垃圾回收之标记算法

  垃圾判定标准:没有被其他对象引用
  垃圾判定算法 引用计数法:判断对象的引用数量,数量为0可被回收
         可达性分析算法:判断对象的引用链是否可达,不可达则可被回收
  引用计数法 优点:执行效率高,不影响程序运行
        缺点:循环引用,导致内存泄漏
  可达性分析法 可做GCRoot的对象:虚拟机引用对象
                  方法区中常量引用的对象
                  方法区中静态变量引用的对象
                  本地方法栈中JNI引用的对象
                  活跃线程的引用对象

2 Java垃圾回收之回收算法

  标记清除
    标记:扫描所有对象,标记存活的对象
    清除:从头到尾遍历堆内存,回收垃圾对象
    缺点:碎片化问题
  半区复制
    分为对象面和可用面
    对象在对象面上创建
    存活对象从对象面复制到可用面
    清除对象面上垃圾对象
  标记整理
    标记:扫描所有对象,标记存活的对象
    整理:移动所有存活对象,按内存地址排列,清除垃圾对象
    优点:解决空间碎片化问题、避免内存的不连续性、适用于存活率较高的对象
  分代收集算法
    垃圾回收算法组合拳
    按对象生命周期不同划分区域,不同区域采用不同算法
  JDK中垃圾收集算法:JDK1.8中,新生代为半区复制,老年代采用标记清除或标记整理

  GC分类: Minor GC/Young GC,半区复制,回收新生代
       Major GC/Old GC,回收老年代(有异议)
       Full GC,标记清除或标记整理,回收新生代+老年代
       由于虚拟机发展,关于Major GC概念已混乱不清,一般指Full GC,有时特指Old GC
  内存分区比例:新生代中,Eden:From:To = 8:1:1
         新生代:老年代=1:2
  对象进入老年代: 对象年龄超过15(通过-XX:MaxTenuringThreshold设置)
          From/To区无法存放的大对象
          新生大对象

  常用调优参数:
    ① -XX:NewRatio:设置新生代和老年代的比例老年代/新生代
    ② -XX:SurvivorRatio:设置eden区和survivor区大小的比例
    ③ -XX:MaxTenuringThreshold:设置对象进入老年代的最大年龄

  Full GC比Minor GC执行慢,频率低
  触发Minor GC,Eden空间不足
  触发Full GC : 老年代空间不足
         调用Systern.gc()
         Minor GC时,晋升到老年代对象的平均大小大于老年代剩余空间
         JDK1.7中,永久代空间不足

3 Java垃圾回收之新生代垃圾收集器

  stop-the-world
  JVM执行GC,停止应用执行
  任何GC算法都会发生
  GC优化,即减少stop-the-world发生次数,提高程序性能

  SafePoint安全点
  分析过程中对象引用关系不会发生的点
  产生SafePoint的地方:方法调用、循环跳转、异常跳转
  安全点数量得适中

  JVM运行模式:Server、Client

  新生代垃圾收集器:Serial、ParNew、Parallel scavenge

4 Java垃圾回收之老年代垃圾收集器

  老年代垃圾收集器:Serial Old、Parallel Old、CMS、G1

5 Java垃圾回收之常见面试题

Object的finalize方法的作用和C++中析构函数是否相同
  与C++中析构函数不同,析构函数调用确定,finalize调用不确定
  将未被引用的对象放到F-Queue队列
  方法执行随时可能会被终止
  给予对象最后一次重生的机会
Java中强引用、轻引用、弱引用、虚引用有什么作用
  强引用
    最普通的引用 Object obj = new Object()
    抛出OutOfMemoryError终止程序,但不会回收具有强引用的对象
    将对象设置为null,可转为弱引用,使其被回收
  轻引用
    对象处在有用但非必须的状态
    当内存空间不足时,GC会回收该引用对象的内存
    可用来实现高速缓存
    SoftRerference
  弱引用
    非必须的对象
    GC时会被回收
    WeakReference
  虚引用
    不会决定对象的生命周期
    任何时候都可能触发GC回收
    必须和引用队列ReferenceQueue联合使用

6 Java垃圾回收之总结

  略

- - -多线程与并发- - -

1 进程和线程的区别

  进程和线程的由来:串行 -> 批处理 -> 进程 -> 线程
  进程和线程的区别:
    ① 进程是资源分配最小单位,线程是CPU调度最小单位
    ② 进程是独立应用,线程不是独立应用
    ③ 进程有独立地址空间,线程没有独立地址空间,线程是进程内一个执行路径
    ④ 进程切换开销大,线程切换开销小

  Java进程和线程的关系:
    ① Java对操作系统功能进行了封装,包括进程和线程
    ② 运行应用会产生一个进程,进程至少包含一个线程
    ③ 每个进程对应一个JVM实例,多个线程共享JVM堆空间
    ④ Java采用单线程编程模型,应用自动创建主线程
    ⑤ 主线程可创建子线程,原则上要后于子线程完成执行

2 线程start()和run()的区别

  调用start()方法会创建一个子线程并启动
  run()方法是Thread的一个普通方法调用
  Thread.start() -> JVM_StartThread -> thread_entry -> Thread.run()

3 Thread和Runnable是什么关系

  Thread是实现了Runnable接口的类,使run支持多线程
  因类的单一继承原则,推荐使用Runnable接口

4 如何实现处理线程的返回值

  如何给run方法传参?
    ① 构造函数传参
    ② 成员变量传参
    ③ 回调函数传参

  如何实现处理线程的返回值?
    ① 主线程等待法
    ② 使用Thread类的join方法阻塞当前线程以等待子线程处理完毕
    ③ 通过callable接口实现(即通过FutureTask或则线程池获取)

5 线程的状态

  6个状态   New(新建):创建后尚未启动的线程状态
       Runnable(运行):包含Running和Ready
       Waitting(无限期等待):不会被分配CPU执行时间,需要被显示唤醒
       Timed Waitting(限期等待):在一定时间后由系统唤醒
       Blocked(等待):等待获取排他锁
       Terminated(结束):已终止线程的状态,结束执行

6 sleep和wait的区别

  基本区别:sleep是Thread的方法,wait是Object的方法
       sleep可在任何地方调用,wait只能在synchronized代码块中使用
  本质区别:sleep会出让CPU,不出让锁,wait出让CPU和锁

7 notify和notifyall的区别

  两个概念:锁池(EntryList)、等待池(WaitSet)
  notifyall唤醒所有等待状态的线程,全部竞争锁
  notify随机唤醒一个等待状态的线程,并竞争锁

8 yield方法

  若调用Thread.yield方法,即表示当前线程可出让CPU的信号,但线程调度器可能会忽略该信号
  yield不影响锁行为

9 interrupt方法

  如何中断线程?
    ① 调用stop方法停止线程(弃用)
    ② 调用suspend方法和resume方法(弃用)
    ③ 调用interrupt方法,通知线程应该中断,需要被调用线程响应中断

  通知线程中断:
    ① 若线程阻塞,则该线程退出阻塞,并抛出InterruptException异常
    ② 若线程为运行状态,则设置线程可中断标志为true,但线程继续运行
  线程响应中断:
    ① 正常运行任务时,经常检查本线程中断标志,若为true则中断
    ② 正常运行任务时,则将线程中断标志置为true,但线程继续运行

10 前述方法及线程状态总结

- - -多线程与并发底层原理- - -

1 synchronized

  线程安全的诱因:存在共享数据(也称临界资源)
          存在多线程共同操作共享数据
  解决方法:同一时刻只有一个线程操作共享数据,其他线程需等待

  互斥锁特性:互斥性,同一时间只有一个线程持有锁和操作同步代码块
        可见性,锁释放之前,修改共享变量,随后获取锁的线程是可见的
  锁分类:
    ① 对象锁:同步代码块(synchronized(this),synchronized(对象))
          同步非静态方法(synchronized method)
    ② 类锁:同步代码块(synchronized(类.class))
         同步静态方法(synchronized static method)
    对象锁锁当前对象,类锁锁当前对象的类对象

  JVM中,对象实例有多个,类对象只有一个,不同对象实例的类对象是同一个,类锁和对象锁互不影响
  类锁和对象锁总结:
    ① 某线程访问同步代码块,其他线程可访问非同步代码块
    ② 若锁住同一个对象,某线程访问同步代码块,其他线程访问同步代码块会阻塞
    ③ 若锁住同一个对象,某线程访问同步方法,其他线程访问同步方法会阻塞
    ④ 若锁住同一个对象,某线程访问同步代码块,其他线程访问同步方法会阻塞,反之亦然
    ⑤ 同一个类的不同对象,其对象锁互不干扰
    ⑥ 类锁是一把特殊对象锁,其表现与①、②、③、④相同,一个类只有一把类锁,同一个类的不同对象使用的类锁是同一个
    ⑦ 类锁和对象锁互不干扰

2 synchronized底层原理

  实现synchronized的基础:Java对象头 + Monitor
  对象内存布局:对象头 + 实例数据 + 对齐填充
  对象头结构:Mark Word(MW) + Class Metadata Address(CMA)
        MW存对象HashCode、分代年龄、锁类型、锁标志位
        CMA存类型指针执行对象的类型数据,通过该指针可知对象对应类

  Mark Word存储信息与锁类型
a
  Monitor:对象自带锁(ObjectMonitor)、Monitor竞争与释放
  源码:https://hg.openjdk.java.net/jdk8u/…
  monitorEnter、monitorExit

  锁重入:线程再次请求自身持有的锁,即为锁重入

  为什么synchronized效率差?
    ① 早期版本中,synchronized属于重量级,依赖于Muter Lock实现
    ② 线程之间切换需要从用户态转换到核心态,开销大
    ③ Java6后,synchronized性能较好,自旋、锁消除、锁粗化、偏向锁、轻量级锁等
  自旋锁:
    ① 许多情况下,共享数据锁定状态持续时间短,切换线程效益差
    ② 让线程执行忙循环,等待锁、不出让CPU
    ③ 缺点:若锁被其他线程长时间占用,则产生大量性能开销
  自适应自旋锁:
    ① 自旋次数不固定
    ② 由上一次获取同一个锁的自旋时间和锁的占有者状态来决定
  锁消除:JVM编译时,扫描上下文,去除不存在竞争的锁
  锁粗化:通过扩大加锁范围,避免反复加锁和解锁

  synchronized四种状态:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
  锁膨胀方向:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
  偏 向 锁 :减少同一线程获取锁的代价,大多数情况下,锁不存在多线程竞争,而由同一线程获取,偏向锁运行在只有一个线程进入同步代码块的情况下
  轻量级锁:由偏向锁升级而来,当第二个线程竞争锁时,则升级为轻量级锁,适应场景为线程交替执行同步代码块
  重量级锁:若存在多个线程同时竞争锁,则轻量级锁升级为重量级锁,Monitor实现

  偏向锁、轻量级锁、重量级锁总结

锁分类优点缺点使用场景
偏向锁加解锁无CAS操作和性能开销,和执行非同步代码块有纳秒级差别若存在多线程锁竞争,有锁撤销的消耗只有一个线程访问同步代码块或同步方法
轻量级锁线程竞争不会阻塞,提高了响应速度若线程竞争长时间未获取到锁,自旋会消耗性能线程交替执行同步代码块或同步方法
重量级锁线程竞争不使用自旋,不会消耗CPU线程阻塞,响应慢、多线程频繁加解锁时会有性能消耗追求吞吐量,同步代码块或同步方法执行时间较长的场景

3 synchronized和ReentrantLock

  ReentrantLock(重入锁)
    ① 位于Java.util.concurrent.lock包
    ② 和CountDownLatch、FutureTask、Semphore相同,由AQS实现
    ③ 可实现比synchronized更细粒度的控制,如控制fairless
    ④ 调用lock后,需调用unlock释放锁
    ⑤ 性能比synchronized高,可重入

  ReentrantLock可设置公平性,使用try/catch/finally
  synchronized为非公平的

  ReentrantLock将锁对象化 : 判断是否线程在排队等待获取锁
               带超时的获取锁的尝试
               感知是否成功获取锁
               因此需将wait/notify/notifyall对象化(Condition)

  ReentrantLock与synchronized区别:
    ① synchronized是关键字,ReentrantLock是类
    ② ReentrantLock可设置获取锁时等待时间,避免死锁
    ③ ReentrantLock可获取各种锁的状态
    ④ ReentrantLock可灵活实现多路通知
    ⑤ 机制:synchronized是操作Mark word,Lock是基于unsafe类

4 JVM内存可见性

  Java内存模型(JMM):一种抽象概念,是一组规范,规定程序中各变量的访问方式
  线程A <—> 本地内存A (共享变量副本)<—>
                      JMM控制规范 / 主内存(共享变量)
  线程B <—> 本地内存B(共享变量副本) <—>

  JMM中主内存:存储Java实例对象
         包括成员变量、类信息、常量和静态变量
         属于数据共享区域,多线程并发访问时有安全问题
  JMM中本地内存:存储当前方法的本地变量,对其他线程不可见
          字节码行号指示器,Native方法信息
          线程私有区域,无线程安全问题
  JMM和Java内存区域划分是不同概念
    ① JMM描述的是一组规则,包括原子性、有序性、可见性
    ② 相似点:存在共享区域和私有区域

  主内存和工作内存的数据类型及操作方式归纳
    ① 方法中基本数据类型的本地变量存储在工作内存的栈帧
    ② 引用类型的本地变量,引用存储在工作内存,实例存储在主内存
    ③ 成员变量、static变量、类信息均存储在主内存
    ④ 主内存共享变量由线程拷贝到工作内存,操作完成后刷新到主内存
  JMM如何解决可见性问题 - 指令重排序
    ① 单线程环境下不能改变程序运行结果
    ② 存在数据依赖关系不允许重排序
  happen-before八大原则 : 程序次序规则
              锁定规则
              volatile变量规则
              传递规则
              线程启动规则
              线程中断规则
              线程终结规则
              对象终结规则
  volatile轻量级同步机制:volatile共享变量对所有线程均可见
             禁止指令重排序
  volatile可见性:写volatile共享变量时,将工作内存共享变量刷新到主内存
         读volatile共享变量时,强制将共享变量从主内存刷新到工作内存
  volatile如何禁止重排序优化 - 内存屏障指令
    ① 保证特定操作的执行顺序
    ② 保证某些变量的内存可见性
  volatile和synchronized的区别
    ① volatile共享变量读写需强制读写主内存,synchronized是对变量加锁
    ② volatile仅作用在变量上,synchronized可作用在方法、变量和类
    ③ volatile可保证有序性、可见性,synchronized可保证有序性、可见性、原子性
    ④ volatile不会造成线程阻塞,synchronized会造成线程阻塞
    ⑤ volatile变量不会被编译器优化,synchronized变量可被编译器优化

5 CAS

  一种高效线程安全的方法-CAS
    ① 支持原子更新操作,适用于计数器、序列发生器等场景
    ② 属于乐观锁机制
    ③ CAS失败可有开发者决定是否继续尝试或执行其他操作
  CAS思想 -3个操作数:内存位置V、预期原值A、新值B
  CAS操作是透明的
    ① JUC中atomic中原子类型封装了CAS,是线程安全的首选
    ② Unsafe提供CAS服务,但存在内存隐患(Java9之后,可使用Variable Handle API)
  CAS缺点
    ① 若循环时间长,性能开销大
    ② 只能保证一个共享变量的原子操作
    ③ ABA问题(版本号),AtomicStampedReference

6 Java线程池

  利用Executors创建线程池满足不同场景 :
    ① newFixedThreadPool(),定长线程池
    ② newCachedThreadPool(),缓存型线程池
    ③ newSingleThreadExecutor),单线程线程池
    ④ newSingleThreadScheduledExecutor(),单线程调度线程池
    ⑤ newSingleScheduledThreadPool(),固定线程数量的调度线程池
    ⑥ newWorkStealingPool(),使用Fork/join框架,将大任务拆解为小任务,汇集小任务结果得到返回

  为什么使用线程池:降低资源消耗
           提高线程可管理性
  JUC中三个Executor接口:
    ① Executor:运行新任务的简单接口
    ② ExecutorService:具备管理执行器和任务生命周期的接口,提交任务机制较完善
    ③ ScheduledExecutorService:支持Future和定期执行任务

  Executors框架:
a
  ThreadPoolExecutor工作流程:
a
  ThreadPoolExecutor构造函数7个参数:corePoolSize/核心线程数、maximumPoolSize/最大线程数、keepAliveTime/非核心线程空闲存活时间、unit/时间单位、workQueue/任务等待队列、threadFactory/线程工厂、handler/拒绝策略
  拒绝策略:①AbortPolicy抛异常、②DiscardPolicy抛弃新任务、③DiscardOldestPolicy抛弃旧任务、④CallerRunsPolicy使用调用者线程执行任务

  线程池工作流程:
a
  线程池的状态:RUNNING,可接受新任务,可处理阻塞队列中任务
         SHUTDOWN,不接受新任务,可处理阻塞队列中任务
         STOP,不接受新任务,不处理阻塞队列中任务
         TIDYING,所有任务均终止
         TERMINATED,一个状态,什么都不做
  线程池状态转换:
a
  工作线程生命周期:
a
  线程池大小如何确定
    ① CPU密集型:线程数 = CPU核数 / CPU核数+1
    ② I/O密集型:线程数 = CPU核数 * (1 + 平均等待时间 / 平均工作时间)

- - -Java常用类库- - -

1 Java异常体系

  String、StringBuffer、StringBuilder的区别?

  异常处理机制主要回答了3个问题
    ① what 异常类型回答了什么被抛出
    ② where 异常堆栈跟踪回答了在哪里抛出
    ③ why 异常信息回答了为什么被抛出

  Java异常体系
在这里插入图片描述

  Error和Exception区别
    ① Error:程序无法处理的系统错误,编译器不做检查
    ② Exception:程序处理的一次,捕获后可能恢复
    ③ 总结:前者为程序无法处理的错误,后者是可以处理的异常

  从责任角度看
    ① Error是JVM需要处理的
    ② RuntimeException是程序需要处理的
    ③ CheckedException是编译器需要处理的

2 Java异常要点分析

  Java异常处理机制
    ① 抛出异常
    ② 捕获异常,寻找合适的异常处理器,否则终止
    ③ finally会在catch中return之前执行
  Java异常处理原则
    ① 具体明确:通过异常类名或日志明确异常原因
    ② 提前抛出:尽早发现异常
    ③ 延迟捕获:延迟捕获异常
  高效主流的异常处理框架
    ① 设计一个通用的继承自RuntimeException的异常统一处理
    ② 其他异常统一转译为上述异常的AppException
    ③ catch之后,抛出上述异常的子异常,提供定位信息
    ④ 前端接受AppException做统一处理
  try-catch性能: try-catch影响JVM性能优化
         异常实例需要报错堆栈快照,开销较大

3 Collection体系

  Java集合框架:优秀的算法和数据结构被封装到Java集合框架
  数据结构考点:数组和链表的区别
         链表操作,如翻转、链表环路检测、双向链表、循环链表操作
         队列、栈的应用
         二叉树遍历方式及其递归或非递归实现
         红黑树旋转
  算法考点:内部排序,如递归排序、交换排序(冒泡、快排)、选择排序、插入排序
       外部排序,利用有限内存和海量外部存储处理超大数据集(思路)

  Collection之List和Set
a

4 HashMap

  集合之Map
a
  HashMap结构:Java8以前为数组 + 链表,Java8以后为数组 + 链表 + 红黑树
  HashMap之put方法: 若未初始化,则初始化
            对key求hash值,求下标
            若无碰撞,则放入哈希数组
            若碰撞,则以链表方式插入尾部
            若链表长度超过阈值,则链表转换为红黑树
            若链表长度低于6,则红黑树转为链表
            若节点已存在,则更新旧值
            若满载(容量 * 加载因子),则扩容(扩容之后需要重排)
  HashMap减少碰撞:扰动函数,使元素位置均匀分布,减少碰撞概率
            使用final对象,并采用合适的equal方法和hashCode方法
  HashMap哈希算法:从hashCode到散列位置
  HashMap扩容问题:多线程环境下,扩容会存在竞争,造成死锁
            rehashing比较耗时
  HashMap知识回顾:成员变量、数据结构、树化阈值
            构造函数、延迟创建
            put、get流程
            hash算法、扩容、性能

  HashMap、HashTable和ConcurrentHashMap

5 ConcurrentHashMap

  如何优化HashTable:细化锁,将整个锁拆解成多个锁

  ConcurrentHashMap之put方法: 判断Node[]是否初始化,没有则初始化
                 计算哈希数组下标,判断是否有Node节点,没有则使用CAS添加,失败则for循环
                 检查是否正在扩容,若正在扩容则帮助扩容
                 若f != null,则使用synchronized锁住f元素(链表/红黑树头结点),执行链表/红黑树添加元素
                 判断链表长度,达到8则转为红黑树
  ConcurrentHashMap逻辑:早期使用分段锁Segment实现
               当前使用CAS + synchronized细化锁,锁数组元素
  ConcurrentHashMap总结:比Segment,锁拆得更细
               使用CAS插入头结点,失败则循环重试
               若存在头结点,则获取头结点同步锁,再插入或更新
  ConcurrentHashMap提升:size方法和mappingCount方法的异同,两者计数是否准确
               多线程环境如何自动扩容

  HashMap、HashTable和ConcurrentHashMap区别
    ① HashMap线程不安全,数组 + 链表 + 红黑树
    ② HashTable线程安全,锁住整个对象,数组 + 链表
    ③ ConcurrentHashMap线程安全,CAS + synchronized,数组 + 链表 + 红黑树
    ④ HashMap的key、value均可为null,其他两个不支持

6 JUC包梳理

  java.util.concurrent提供并发编程的解决方案
    ① CAS是java.util.concurrent.atomic包的基础
    ②AQS是java.util.concurrent.locks包、Semphore、ReentrantLock等类的基础

  JUC包的分类:线程执行器/executor
         锁/locks
         原子变量类/atomic
         并发工具类/tools
         并发集合/collections
  JUC包明细:
a
  并发工具类tools:CountDownLatch/闭锁,让主线程等待一组事件发生
           CyclicBarrier/栅栏,阻塞并等待其他线程
           Semaphore/信号量,控制资源可被同时访问数量
           Exchanger/交换器,两个线程到达交换的后交换数据

  BlockingQueue:阻塞队列,主要用于生产者/消费者模式,多线程场景下,隔离任务生成与消费
    ① ArrayBlockingQueue,数组构成的有界阻塞队列
    ② LinkedBlockingQueue,链表构成的有界阻塞队列
    ③ PriorityBlockingQueue,优先级排序的无界阻塞队列
    ④ DelayQueue,优先级队列实现的延迟无界阻塞队列
    ⑤ SynchronousQueue,不存储元素的阻塞队列(存一个元素)
    ⑥ LinkedTransferQueue,链表构成的无界阻塞队列
    ⑦ LinkedBlockingDeque,链表构成的双向阻塞队列

7 Java的IO机制

  BIO、NIO、AIO
    ① Block-IO:InputStream和OutputStream、Reader和Writer,阻塞
    ② NoBlock-IO:构建多路复用、同步非阻塞IO
            Channels,包括FileChannel、DatagramChannel、SocketChannel等
            Buffers,包括ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer等
                    -> Channel -> Buffer
            Thread -> Selector -> Channel -> Buffer
                    -> Channel -> Buffer
            IO多路复用,调用系统级别的select/poll/epoll监测Channel可读
            select、poll、epoll三者的区别(连接数、IO效率、消息传递方式)
    ③ Asynchronous IO:基于事件和回调机制、异步非阻塞IO
              基于回调,实现CompletionHandle接口,调用时出发回调函数
              返回Future,通过IsDone查看是否准备好,通过get等待返回数据

  BIO、NIO、AIO对比

属性/模型BIONIOAIO
blocking阻塞同步非阻塞同步非阻塞异步
线程数(server:client)1:11:N0:N
复杂度简单较复杂复杂
吞吐量

8 小结

- - -Spring框架- - -

1 Spring家族

  Spring Core、Spring Security、Spring Data、Spring Boot、Spring Cloud…

2 IOC原理

  你了解Spring IOC吗
  IOC/控制翻转:Spring Core最核心的部分
         需先了解依赖注入/DI
         普通依赖,定义成员变量,new对象赋予依赖的成员变量
         依赖注入,定义依赖对象,使用构造函数或者Setter方法注入
         依赖注入方式,Setter、Constructor、Interface、Annotation

  依赖倒置原则、IOC、DI、IOC容器的关系
  IOC容器的优势
    ① 避免使用new创建类,统一维护
    ② 创建实例时,无需了解其细节

3 SpringIOC应用

  Spring中Bean实例化过程:
a
  SpringIOC支持的功能:依赖注入、依赖检查、自动装配、支持集合、支持初始化方法和销毁方法、支持回调方法

  isSingleton:是否为单例
  BeanDefinition:用于描述Bean的定义
  BeanDefinitionRegister:提供向IOC容器注册BeanDefinition对象的方法
  SpringIOC容器的核心接口:BeanFactory、ApplicationContext

  BeanFactory: 提供IOC配置机制
         包含各种Bean定义,便于实例化Bean
         建立Bean之间的依赖关系
         Bean生命周期管理

  BeanFactory和ApplicationContext区别
    ① BeanFactory是Spring框架的基础设施
    ② ApplicationContext是面向Spring框架的开发者

  ApplicationContext功能(继承的接口)
    ① BeanFactory,管理装配bean
    ② ResourcePatternResolver,加载资源文件
    ③ MessageSource,实现国际化
    ④ ApplicationEventPublisher,注册监听器,实现监听机制

4 SpringIOC的refresh

4.1 Spring启动过程个人总结

  初始化Spring容器
    ① 创建BeanFactory
    ② 创建BeanDefinitionReader注解配置读取器
    ③ 创建ClassPathBeanDefinitionScanner路径扫描器
  注册配置类为BeanDefinition到容器
  调用refresh刷新容器
    ① 刷新前预处理
    ② BeanFactory预处理  设置BeanFactory类加载器、表达式解析器、类型转化注册器
                添加3个BeanPostProcessor实现类实例
                记录ignoreDependencyInterface
                记录ResolvableDependency
                添加3个单例Bean
    ③ 子类处理BeanFactory
    ④ 执行自定义的BeanFactory后置处理器
    ⑤ 执行后置处理器的postProcessBeanDefinitionRegistry
      扫描配置类
      设置排序和名称生成器
      解析配置类,包括@component、@componentScan、@Import、@Bean等标注的类/方法
    ⑥ 执行postProcessBeanFactory方法
    ⑦ 执行BeanFactory后置处理器(② ~ ⑦可简化为BeanFactory初始化及处理)
    ⑧ 初始化国际化资源对象messagesource
    ⑨ 初始化事件发布器
    ⑩ 注册监听器
    ⑪ 实例化非懒加载bean
    ⑫ 执行bean生命周期回调接口
    ⑬ 发布启动完成事件

4.2 SpringBoot启动过程个人总结

  新建SpringApplication对象,过程包括:
    ① 确定应用类型
    ② 加载spring.factories中定义的类
    ③ 设置带有mian方法的运行主类
  执行SpringApplication.run方法,过程包括:
    ① 创建应用监听器SpringApplicationRunListeners
    ② 准备应用程序参数和环境变量
    ③ 创建、准备、刷新应用上下文环境context(create…Context、prepareContext、refreshContext)
    ④ 发布启动完成事件

5 SpringIOC的getBean

  getBean方法逻辑: 转换beanName
           从缓存中加载实例
           实例化Bean
           检测parentBeanFactory
           初始化依赖的Bean
           创建Bean

  Spring Bean的作用域
    ① singleton:Spring默认作用域,容器中拥有唯一Bean实例
    ② prototype:针对每个getBean请求,容器创建一个Bean实例
    ③ request:针对每个Http请求,创建一个Bean实例
    ④ session:针对每个session,创建一个Bean实例
    ⑤ globalSession:针对每个globalSession,创建一个Bean实例

  Spring Bean生命周期 - 创建
    ① 实例化bean
    ② Aware(注入Bean ID、BeanFactory和AppCtx)
    ③ BeanPostProcess(s).postProcessBeforeInitialization
    ④ InitializingBean(s).afterPropertiesSet
    ⑤ 定制的Bean init方法
    ⑥ BeanPostProcess(s).postProcessAfterInitialization
    ⑦ Bean初始化完毕
  Spring Bean生命周期 - 销毁
    ① 若实现了DisposableBean接口,则调用destroy方法
    ② 若配置了destory-method属性,则调用其配置的销毁方法

5.1 Bean生命周期个人总结

  Bean配置 -> 实例化 -> 属性填充 -> 初始化 -> 使用 -> 销毁

  ① Bean配置 生成BeanDefinition -> 合并BeanDefinition -> 加载类
  ② 实例化   实例化前
          实例化 -> 推断构造方法、反射创建实例
          实例化后
  ③ 属性填充  用户属性填充
          容器属性填充 -> Aware接口
  ④ 初始化   初始化前
          初始化 -> @PostConstruct标注方法、InitializingBean.afterPropertiesSet、bean配置文件init-method标签方法
          初始化后
  ⑤ 使用
  ⑥ 销毁    @PreDestroy标注方法、DisposableBean接口实现、@Bean中destroyMethod属性方法

6 AOP介绍及其使用

  你了解Spring AOP吗?

  关注点分离:不同问题交给不同模块处理
    ① 切面编程(AOP)正是此种技术的实现
    ② 通用功能模块为切面(Aspect)
    ③ 业务功能模块和通用功能模块分开后,架构变得高内聚、低耦合
    ④ 切面需要合并到业务中,确保功能完整性

  AOP三种织入方式
    ① 编译时织入,需要特殊编译器,如AspectJ
    ② 类加载时织入,需要特殊编译器,如AspectJ
    ③ 运行时织入,Spring采用的方式,通过动态代理实现,比较简单

  AOP主要名词概念:Aspect,通用功能模块
           Target,被织入Aspect的对象
           Join Point,可作为切入点的地方
           Point Cut,Aspect实际应用到的Join Point,支持正则
           Advice,类中方法以及该方法织入到目标方法的方式
           Weaving,AOP的实现过程

  AOP种类:前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)、环绕通知(Around)

7 SpringAOP原理

  AOP的实现,JDKProxy和Cglib
    ① 由AopProxyFactory根据AdvisedSupport对象的配置决定
    ② 默认策略如果目标类是接口,则使用JDKProxy实现,否则用Cglib
    ③ JDKProxy的核心,InvocationHandle接口和Proxy类
    ④ Cglib,以继承的方式动态生成目标类的代理
    ⑤ JDKProxy,通过反射机制实现
    ⑥ Cglib,借助ASM实现
    ⑦ 反射机制在生成类的过程中比较高效
    ⑧ ASM在生成类之后的执行过程中比较高效

  代理模式:接口 + 真实实现类 + 代理类

  Spring代理模式实现
    ① 真实实现类的逻辑包含在getBean方法中
    ② getBean方法实际返回的是Proxy实例
    ③ Proxy实例是Spring采用JDKProxy或者Cglib动态生成的

8 本章小结

9 Java基础相关问题

1. Data注解是否有问题
  A类继承B类,使用@Data注解的A类自动生成的toString()函数无法打印B类的成员变量
2. 切面编程如何理解
  在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
3. spring容器中,实例加载顺序
  ******
4. Inherit组合注解使用
  ******
5. 注解如何起作用的
  ******

- - -Nginx- - -

1. 某现场升级后Nginx问题
  进程配置:主进程 + hms用户进程 + hcs用户进程,
  配置文件:hms用户配置文件和hcs用户配置文件,
  问题现象:hms用户进程、hcs用户进程不能同时工作,其中一个会挂死,
  解决办法:将hcs用户配置文件合并到hms用户配置文件后,解决了该问题,
  原因:?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值