面试准备笔记整理

Java集合框架

Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
└Queue
Map
├Hashtable
├HashMap
└WeakHashMap

重点要分清数组与链表的特点:

数组链表的优缺点:

  1. 数组占用空间小,链表元素还要包含上一元素和下一个元素的的信息
  2. 数组的访问速度快,因为内存是连续的
  3. 数组内部元素可以随机访问,而链表依赖于上一个元素的信息

链表的插入删除操作由于数组,因为内存不连续,只需要更改元素的前后节点信息就行了,并不需要更改元素内存地址,而数组的连续内存想要插入和删除的话就要移动所有的内存地址
链表的内存利用率高于数组,链表内存是分散的一个元素占用一块空间,数组元素少于内存空间的话,会有部分的内存浪费;
链表的扩展性强,数组的创建完成内存大小就确定了,满了就没法扩展只能再次创建新的数组,而链表可以随意的增加扩展

效率:数组查询效率高,链表增,删效率高

众所周知,数组的长度是固定的,无法对数组进行扩容,那么ArrayList和HashMap是怎样实现动态扩容的呢?
生成一个更大的数组,然后把旧数组的元素拷贝进去
ArrayList 默认会在容量不足时进行1.5倍的扩容
而HashMap会根据加载因子(默认为0.75,容量到达总容量的0.75时,进行扩容),扩容后容量默认为2倍

快速随机访问(RandomAccess):ArrayList,HashMap

RandomAccess 接口是一个标志接口,本身并没有提供任何方法,任务凡是通过调用 RandomAccess 接口的对象都可以认为是支持快速随机访问的对象。此接口的主要目的是标识那些可支持快速随机访问的 List 实现。任何一个基于数组的 List 实现都实现了 RaodomAccess 接口,而基于链表的实现则都没有。因为只有数组能够进行快速的随机访问,而对链表的随机访问需要进行链表的遍历。因此,此接口的好处是,可以在应用程序中知道正在处理的 List 对象是否可以进行快速随机访问,从而针对不同的 List 进行不同的操作,以提高程序的性能。

线程安全: Vector, Stack, HashTable, ConcurrentHashMap

HashTable由于多线程环境下性能低下,现在已经不建议使用,多线程环境下建议使用ConcurrentHashMap。
ConcurrentHashMap对比HashTable的优化在于分段锁,引入了segment概念,segment本质是一个ReentrantLock(可重入锁),每个segment只锁住ConcurrentHashMap中的一部分元素
在多线程取数据时,如果取的数据是不同segment管理下的数据,那么就不存在锁的竞争,故能大幅度提高效率
ConcurrentHashMap计算Size:;理论上是segment块求和,但是存在求和过程中变化,这里有一个类似乐观锁的操作:
因为在累加count操作过程中,之前累加过的count发生变化的几率非常小,所以ConcurrentHashMap的做法是先尝试2次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小。

ArrayList 与 LinkedList 对比,

HashMap取值的时间复杂度:理想状态下是O(1),实际会发生hash碰撞,看遍历链表或者红黑树的时间复杂度
怎样才能达到理想的时间复杂度?思路就是让元素尽可能的均匀分布,减少hash冲突:
手段有:1. 优化Hash算法,减少Hash碰撞 2. 增大桶容量,从而减少Hash碰撞
HashMap实质是元素为链表的数组:java 8,优化之后在链表长度超过8之后会将链表转为红黑树,以提高查询效率(体现在红黑树查询效率比链表高)  
为什么长度为8时转为红黑树?根据统计之后的链表长度的泊松分布,超过8的很少。

平衡树、红黑树

JVM

此处所有的JVM讨论都是基于JDK自带的Hotspot的JVM。

客户模式或服务器模式

JIT 编译器在运行程序时有两种编译模式可以选择,并且其会在运行时决定使用哪一种以达到最优性能。这两种编译模式的命名源自于命令行参数(eg: -client 或者 -server)。JVM Server 模式与 client 模式启动,最主要的差别在于:-server 模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。原因是:当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器,而-server 模式启动的虚拟机采用相对重量级代号为 C2 的编译器。C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高。

通过 java -version 命令行可以直接查看当前系统使用的是 client 还是 server 模式。

JVM内存模型:

按照JVM规范,JAVA虚拟机在运行时会管理以下的内存区域:

  • 程序计数器:当前线程执行的字节码的行号指示器,线程私有
  • JAVA虚拟机栈:Java方法执行的内存模型,每个Java方法的执行对应着一个栈帧的进栈和出栈的操作。
  • 本地方法栈:类似“ JAVA虚拟机栈 ”,但是为native方法的运行提供内存环境。
  • JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。可分为新生代,老生代。
  • 方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot中的“永久代”。 注意:这里的永久代(PermanentSpace)针对jdk1.7之前的版本,JDK1.7常量已经放在Heap分区,JDK8中则完全移除了PermanentSpace,InternedStrings也被放到了MetaSpace(元空间)中
  • 运行时常量池:方法区的一部分,存储常量信息,如各种字面量、符号引用等。
  • 直接内存:并不是JVM运行时数据区的一部分, 可直接访问的内存, 比如NIO会用到这部分。

按照JVM规范,除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。

判断对象存活状态

根路径搜索算法(GC-Root-trancing)
如果对象与GC-Root之间不可达,则认为该对象可回收
解决了引用计数器方法不能回收两个循环引用但无外部引用的对象这一问题

三种GC算法:

标记-清除算法(Mark-sweep) (老年代)

分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记对象。
缺点:
1)产生大量不连续的内存碎片
2)标记和清除效率都不高

复制算法(Copying) (新生代)

它将可用内存按照容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,则就将还存活的对象复制到另一块上面,然后再把已经使用过的内存空间一次清理掉。使得每次都是对整个半区进行内存回收。

优点:
1)不会出现内存碎片。
2)只需移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

缺点:
1)将内存缩小为原来的一半。
2)在对象存活率较高时会进行较多复制操作,效率较低。

商业虚拟机的分配担保机制:
将内存分为一块较大的 eden 空间和两块较小的 survivor 空间,默认比例是 8:1:1,即每次新生代中可用内存空间为整个新生代容量的 90%,每次使用 eden 和其中一个 survivour。当回收时,将 eden 和 survivor 中还存活的对象一次性复制到另外一块 survivor 上,最后清理掉 eden 和刚才用过的 survivor,若另外一块 survivor 空间没有足够内存空间存放上次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。

标记-整理算法(Mark-Compact) (老年代)

标记过程和“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清除,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。

七种收集器:

  1. Serial (复制算法)
  2. ParNew (复制算法)
  3. Parallel Scavenge (复制算法) 并行
  4. CMS(Concurrent Mark Sweep ) (标记-清除) 并发
  5. Serial Old(MSC) (标记-整理)
  6. Parallel Old (标记-整理)并行
  7. G1 (分块 总体看是标记-整理 每块都是复制算法)

最常见的OOM情况有以下三种:

  • java.lang.OutOfMemoryError: Java heap space ——>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
  • java.lang.OutOfMemoryError: PermGen space ——>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
  • java.lang.StackOverflowError ——> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。

OOM分析: –heapdump

  • 设置JVM参数-XX:+HeapDumpOnOutOfMemoryError,设定当发生OOM时自动dump出堆信息。不过该方法需要JDK5以上版本。
  • 使用JDK自带的jmap命令。”jmap -dump:format=b,file=heap.bin “ 其中pid可以通过jps获取。

dump堆内存信息后,需要对dump出的文件进行分析,从而找到OOM的原因。常用的工具有:

  • mat: eclipse memory analyzer, 基于eclipse RCP的内存分析工具。详细信息参见:http://www.eclipse.org/mat/, 推荐使用。
  • jhat:JDK自带的java heap analyze tool,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言OQL,分析相关的应用后,可以通过http://localhost:7000 来访问分析结果。不推荐使用,因为在实际的排查过程中,一般是先在生产环境 dump出文件来,然后拉到自己的开发机器上分析,所以,不如采用高级的分析工具比如前面的mat来的高效。
  • Plumbr

如何减少GC出现的次数:

  1. 对象不用时显示置null。

  2. 少用System.gc()。

  3. 尽量少用静态变量。

  4. 尽量使用StringBuffer,而不用String累加字符串。

  5. 分散对象创建或删除的时间。

  6. 少用finalize函数。

  7. 如果需要使用经常用到的图片,可以使用软引用类型,它可以尽可能将图片保存在内存中,供程序调用,而不引起OOM。

  8. 能用基本类型就不用基本类型封装类。

  9. 增大-Xmx的值。

类加载的时机

  1. 遇到new、getstaic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则会触发初始化。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则会先触发其初始化。
  3. 当初始化一个类的时候,如果发现其父类还没进行过初始化,则需要触发其父类初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类。
  5. 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则会先触发其初始化。

类加载器(ClassLoader)的双亲委派模型:
类加载时,默认首先寻找父 类加载器(parent class loader),如果没有找到就会选择BootStrap ClassLoader,

反射

java的反射机制主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接。但是反射使用不当会成本很高!

从代码中可以看出,先检查 AccessibleObject的override属性是否为true(override属性默认为false)。AccessibleObject是Method,Field,Constructor的父类,可调用setAccessible方法改变,如果设置为true,则表示可以忽略访问权限的限制,直接调用。如果不是ture,则要进行访问权限检测。用Reflection的quickCheckMemberAccess方法先检查是不是public的,如果不是再用Reflection.getCallerClass()方法获得到调用这个方法的Class,然后做是否有权限访问的校验,校验之后缓存一次,以便下次如果还是这个类来调用就不用去做校验了,直接用上次的结果。

简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

Java 反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法

由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。

另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

AOP

(aspect-oriented programming 面向切面编程) 面向切面编程的思想里面,把功能分为核心业务功能,和周边功能.

  • 业务代码:登录登出,增删改查,
  • 周边代码:权限检查,日志管理,事务管理,性能检查(方法执行耗时)

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
本质:代理模式 代理增强
一般会用到Spring的动态代理库CGLib或者JDK自带的动态代理库,注意一个细节是如果被代理的对象没有抽象接口,则不能用JDK代理,而Spring AOP没有这个限制
另一个细节是Spring AOP有一个@DeclareParents引介增强注解可以为Bean引入另一个Bean的方法

Spring

Spring框架四大模块:

  • Data:提供了一些数据相关的组件:包括JDBC、orm(对象关系映射)、事务操作、oxm(对象xml映射)、Jms(Java消息服务)。
  • WEB:扩展了Spring的Web功能。使其符合MVC的设计规范,最重要的是提供了Spring MVC的容器。
  • AOP:负责Spring的所有AOP(面向切面)的功能。(日志,事务)
  • Core Container:核心容器,从核心俩字我们可以看出,这是Spring最重要的部分。主要的功能是实现了控制反转(IOC)与依赖注入(DI)、Bean配置、加载以及生命周期的管理。

Spring IOC

控制反转,依赖注入
Factory生成代理bean时机,依赖注入时机

注入方式:

  • 构造器注入
  • setter注入
  • 接口注入

Spring cloud


了解Spring cloud之前需要先了解下什么是微服务

微服务(Micro Service):微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

微服务为什么会出现?

  • 大型整体式应用维护困难。
  • 传统架构升级困难。
  • 新的轻量级协议(RESTful)、容器化(Docker)的出现。

Spring Cloud是一套用于微服务的、简单易懂、易部署和易维护的分布式系统开发工具包。
它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。

Spring Cloud如何实现微服务?

  • Eureka(注册中心):负责所有微服务的管理。
  • Ribbon(服务发现):主要负责客户端的负载均衡
  • Feign(接口伪装):使微服务之间的调用像本地调用一样简单。
  • Hystrix(熔断处理):在微服务出现问题时防止出现雪崩效应。
  • Zuul(代理机制):安全认证、动态路由、流量管理、服务器负载均衡
  • Config(配置管理):管理所有微服务的配置文件(GIT/SVN)。

Dobbo

SQL

善用 explain执行计划

事务

事务属性(ACID)

  • 原子性(Atomicity):事务是一个原子操作单元。在当时原子是不可分割的最小元素,其对数据的修改,要么全部成功,要么全部都不成功。
  • 一致性(Consistent):事务开始到结束的时间段内,数据都必须保持一致状态。
  • 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的”独立”环境执行。
  • 持久性(Durable):事务完成后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

回滚日志(undo log)和重做日志(redo log)

在数据库系统中,事务的原子性和持久性是由事务日志(transaction log)保证的,在实现时也就是上面提到的两种日志,前者用于对事务的影响进行撤销,后者在错误处理时对已经提交的事务进行重做,它们能保证两点:

发生错误或者需要回滚的事务能够成功回滚(原子性);
在事务提交后,数据没来得及写会磁盘就宕机时,在下次重新启动后能够成功恢复数据(持久性);
在数据库中,这两种日志经常都是一起工作的,我们可以将它们整体看做一条事务日志,其中包含了事务的 ID、修改的行元素以及修改前后的值。
一条事务日志同时包含了修改前后的值,能够非常简单的进行回滚和重做两种操作,

事务常见问题

  • 更新丢失(Lost Update)
    原因:当多个事务选择同一行操作,并且都是基于最初选定的值,由于每个事务都不知道其他事务的存在,就会发生更新覆盖的问题。类比github提交冲突。

  • 脏读(Dirty Reads)
    原因:事务A读取了事务B已经修改但尚未提交的数据。若事务B回滚数据,事务A的数据存在不一致性的问题。

  • 不可重复读(Non-Repeatable Reads)
    原因:事务A第一次读取最初数据,第二次读取事务B已经提交的修改或删除数据。导致两次读取数据不一致。不符合事务的隔离性。

  • 幻读(Phantom Reads)
    原因:事务A根据相同条件第二次查询到事务B提交的新增数据,两次数据结果集不一致。不符合事务的隔离性。

幻读和脏读有点类似
脏读是事务B里面修改了数据,
幻读是事务B里面新增了数据。

隔离级别:MySQL默认的是可重复读的隔离级别

隔离级别 读数据一致性 脏读 不可重复读 幻读
未提交读(Read uncommitted) 最低级别
已提交读(Read committed) 语句级
可重复读(Repeatable read) 事务级
可序列化(Serializable) 最高级别,事务级

RAED UNCOMMITED:使用查询语句不会加锁,可能会读到未提交的行(Dirty Read);
READ COMMITED:只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果(Non-Repeatable Read);
REPEATABLE READ:多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读(Phantom Read);
SERIALIZABLE:InnoDB 隐式地将全部的查询语句加上共享锁,解决了幻读的问题;

以上的所有的事务隔离级别都不允许脏写入(Dirty Write),也就是当前事务更新了另一个事务已经更新但是还未提交的数据,大部分的数据库中都使用了 READ COMMITED 作为默认的事务隔离级别,但是 MySQL 使用了 REPEATABLE READ 作为默认配置;从 RAED UNCOMMITED 到 SERIALIZABLE,随着事务隔离级别变得越来越严格,数据库对于并发执行事务的性能也逐渐下降。

行锁与表锁

行锁

行锁的劣势:开销大;加锁慢;会出现死锁
行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强
加锁的方式:自动加锁。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;当然我们也可以显示的加锁:
共享锁:’select 语句 + lock in share more’
排他锁:’select 语句 + for update’

  • 间隙锁(GAP) (Next-Key锁) 锁住当前行的同时还会锁住相邻的行
  • 排他锁 也称写锁,独占锁。当前写操作没有完成前,它会阻断其他写锁和读锁。
  • 共享锁 也称读锁,多用于判断数据是否存在,多个读操作可以同时进行而不会互相影响。当如果事务对读锁进行修改操作,很可能会造成死锁。

表锁

  • 共享读锁
    不会阻塞其他进程对同一表的读操作,但会阻塞对同一表的写操作。只有当读锁释放后,才能执行其他进程的写操作。在锁释放前不能取其他表。

  • 独占写锁
    会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。在锁释放前不能写其他表。

使用乐观锁更新的时候不加锁,当提交更新时需要判断数据是否已经被修改(AND number=#{number}),只有在 number等于上一次查询到的number时 才提交更新。
有 CAS思想(Compare And Set)比较,没有冲突则修改

MySql两种常见引擎:

InnoDB 和 MyISAM
两者都使用B+Tree作为索引的数据结构,区别是InnoDB支持事务,而MyISAM不支持;
InnoDB 和 MyISAM之间的区别:
1>.InnoDB支持事物,而MyISAM不支持事物

2>.InnoDB支持行级锁,而MyISAM支持表级锁

3>.InnoDB支持MVCC, 而MyISAM不支持

4>.InnoDB支持外键,而MyISAM不支持

5>.InnoDB不支持全文索引,而MyISAM支持。(X)

MVCC

MySql中的MVCC本质就是: 更新前建立undo log,根据各种策略读取时非阻塞就是MVCC

通过维护多个版本的数据,数据库可以允许事务在数据被其他事务更新时对旧版本的数据进行读取,很多数据库都对这一机制进行了实现;因为所有的读操作不再需要等待写锁的释放,所以能够显著地提升读的性能,MySQL 和 PostgreSQL 都对这一机制进行自己的实现,也就是 MVCC,虽然各自实现的方式有所不同,MySQL 就通过文章中提到的回滚日志实现了 MVCC,保证事务并行执行时能够不等待互斥锁的释放直接获取数据。

一般我们认为MVCC有下面几个特点:

  1. 每行数据都存在一个版本,每次数据更新时都更新该版本
  2. 修改时Copy出当前版本随意修改,个事务之间无干扰
  3. 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道。。。,而Innodb的实现方式是:

  1. 事务以排他锁的形式修改原始数据
  2. 把修改前的数据存放于undo log,通过回滚指针与主数据关联
  3. 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)

Innodb的实现真算不上MVCC,因为并没有实现核心的多版本共存,undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。

聚集索引和非聚集索引

聚集索引表记录的排列顺序与索引的排列顺序一致

聚集索引中叶子节点保存的就是数据本身,key是主键,而非聚集索引中叶子节点储存的是主键,还需要通过主键查找数据
优点是查询速度快,因为一旦具有第一个索引值的纪录被找到,具有连续索引值的记录也一定物理的紧跟其后。
缺点是对表进行修改速度较慢,这是为了保持表中的记录的物理顺序与索引的顺序一致,而把记录插入到数据页的相应位置,必须在数据页中进行数据重排, 降低了执行速度。建议使用聚集索引的场合为:

  1. 此列包含有限数目的不同值;
  2. 查询的结果返回一个区间的值;
  3. 查询的结果返回某值相同的大量结果集。
  4. 范围查询时必须要聚集索引

非聚集索引指定了表中记录的逻辑顺序,但记录的物理顺序和索引的顺序不一致,聚集索引和非聚集索引都采用了B+树的结构,但非聚集索引的叶子层并不与实际的数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针的方式。
非聚集索引比聚集索引层次多,添加记录不会引起数据顺序的重组。
建议使用非聚集索引的场合为:

  1. 此列包含了大量数目不同的值;
  2. 查询的结束返回的是少量的结果集;
  3. order by 子句中使用了该列。

hash索引

hash索引,相比较于B树而言,不需要从根节点到叶子节点的遍历,可以一次定位到位置,查询效率更高,但缺点也很明显

  • 仅能满足”=”,”IN”和”<=>”查询,不能使用范围查询

  • 因为是通过hash值进行计算,所以只能精确查询,hash值是没什么规律的,不能保证顺序和原来一致,所以范围查询不行

  • 无法进行排序

原因同上

  • 不支持部分索引

  • hash值的计算,是根据完整的几个索引列计算,如果少了其中一个乃至几个,这个hash值就没法计算了

  • hash碰撞

mysql保证事务的隔离性主要通过Lock(锁)和MVCC(multiple version concurrent control 多版本并发控制)

总结

  1. InnoDB 支持表锁和行锁,使用索引作为检索条件修改数据时采用行锁,否则采用表锁。
  2. InnoDB 自动给修改操作加锁,给查询操作不自动加锁
  3. 行锁可能因为未使用索引而升级为表锁,所以除了检查索引是否创建的同时,也需要通过explain执行计划查询索引是否被实际使用。
  4. 行锁相对于表锁来说,优势在于高并发场景下表现更突出,毕竟锁的粒度小。
  5. 当表的大部分数据需要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁。
  6. 为了保证数据的一致完整性,任何一个数据库都存在锁定机制。锁定机制的优劣直接影响到一个数据库的并发处理能力和性能。

多线程

两种重要的锁:synchronized lock

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个类
锁的释放 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能 少量同步 大量同步
  • synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果 再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
  • 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
  • 每个对象只有一个锁(lock)与之相关联。

volatile 轻量级,只保证修改同步和可见性,不保证线程安全

atomic 乐观锁 AtomicInteger 通过CAS(Compare And Set)操作实现线程安全的自增。
CAS 可能会带来ABA问题

LongAdder 思路类似于ConcurrentHashMap,Cell数组分块加锁

AQS框架(AbstractQueuedSynchronizer的简写,中文名应该叫抽象队列同步器)

IO模型

BIO,NIO,AIO

觉得有帮助就赞赏一下吧
0%