阿里面试回答的认真总结

1:自我介绍

2: javahashmap底层是怎么实现的,扩容是怎么做的;

底层实现
JDK1.8 之前的版本中, HashMap 底层基于散列算法实现。 HashMap 内部实现是一个桶数组,每个桶中存放着一个单链表的头结点。其中每个结点存储的是一个键值对整体( Entry ), HashMap 采用拉链法解决哈希冲突。
但是当位于一个桶中的元素较多,即 hash 值相等的元素较多时,通过 key 值依次查找的效率较低。
JDK1.8 中, HashMap 采用数组 + 链表 + 红黑树实现,当链表长度超过阈值 8 时,将链表转换为红黑树,这样减少了查找时间。
当调用 put 操作时, HashMap 计算键值 K 的哈希值,然后将其对应到 HashMap 的某一个桶 (bucket) 上;此时找到以这个桶为头结点的一个单链表,然后顺序遍历该单链表找到某个节点的 Entry 中的 Key 是等于给定的参数 K ;若找到,则将其的 old V 替换为参数指定的 V ;否则直接在链表尾部插入一个新的 Entry 节点。
对于 get(K) 操作类似于 put 操作, HashMap 通过计算键的哈希值,先找到对应的桶,然后遍历桶存放的单链表通过比照 Entry 的键来找到对应的值。
由于哈希是一种压缩映射,换句话说就是每一个 Entry 节点无法对应到一个只属于自己的桶,那么必然会存在多个 Entry 共用一个桶,拉成一条链表的情况,这种情况叫做哈希冲突。当哈希冲突产生严重的情况,某一个桶后面挂着的链表就会特别长。
 
扩容
当我们不断的向 HashMap 对象里不停的添加元素时, HashMap 对象内部的数组就会出现无法装载更多的元素,
这是对象就需要扩大数组的长度,以便能装入更多的元素;当然 Java 里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组;
在对 HashMap 进行扩容时,阀值和 HashMap 的容量会变为原来的两倍;
map 中包含的 Entry 的数量大于等于 threshold = loadFactor (初始容量) * capacity (装载因子)的时候,且新建的 Entry 刚好落在一个非空的桶上,此刻触发扩容机制,将其容量扩大为 2 倍。
HashMap 构造方法中,可供我们调整的参数有两个,一个是初始容量 initialCapacity table 数组的大小,缺省值为 16 ),另一个负载因子
loadFactor (缺省值为 0.75 )。
扩容是一个特别耗性能的操作,所以在使用 HashMap 的时候,估算 map 的大小,初始化的时候给一个大致的数值,避免 map 进行频繁的扩容。
 
为什么两倍扩容,怎么复制,线程安全吗,
两倍扩容
HashMap 通过键的哈希值进行定位桶位置的时候,调用了一个 indexFor(hash, table.length); 方法。
这个方法中将哈希值与桶数组的长度 -1 (实际上也是 map 的容量 -1 )进行了一个与操作得出了对应的桶的位置。
Java % / 操作比 & 10 倍左右,因此采用 & 运算会提高性能。
通过限制长度是一个 2 的幂数,哈希值 & ( 长度 -1) 和哈希值 % 长度结果是一致的。
capacity :当前数组容量,始终保持 2^n ,可以扩容,扩容后数组大小为当前的 2 倍。
 
怎么复制
实现浅拷贝的方式有两种: = Map.putAll() ;实现深拷贝: hashmap.clone() HashMap.putAll (),
浅拷贝是地址引用,而深拷贝是数值引用,大多数情况下,我们需要实现的是深拷贝而不是浅拷贝,深拷贝才是真正意义上的拷贝。
 
线程安全
HashMap 非线程安全,它根据 key hashCode 值来保存 value ,不保证遍历的顺序和插入的顺序是一致的。
HashMap 允许有一条记录的 key null ,但是对值是否为 null 不做要求。
HashTable 类是线程安全的,它使用 synchronize 来做线程安全,全局只有一把锁,在线程竞争比较激烈的情况下 hashtable 的效率是比较低下的。
因为当一个线程访问 hashtable 的同步方法时,其他线程再次尝试访问的时候,会进入阻塞或者轮询状态。
ConcurrentHashMap 使用了分段锁技术来提高了并发度,不在同一段的数据互相不影响,多个线程对多个不同的段的操作是不会相互影响的。
每个段使用一把锁。所以在需要线程安全的业务场景下,推荐使用 ConcurrentHashMap ,而 HashTable 不建议在新的代码中使用,
如果需要线程安全,则使用 ConcurrentHashMap ,否则使用 HashMap 就足够了
HashMap 的使用频率在所有 map 中确实属于比较高的。它能满足我们大多数的场景了。
 
扩展
LinkedHashMap 属于 HashMap 的子类,与 HashMap 的区别在于 LinkedHashMap 保存了记录插入的顺序。 TreeMap 实现了 SortedMap 接口,
TreeMap 有能力对插入的记录根据 key 排序,默认按照升序排序,也可以自定义比较,在使用 TreeMap 的时候, key 应当实现 Comparable
 
为什么不怎么用 hashtable, hashmap 性能为什么高一点;其他集合用的比较多的 list set; set 底层; list 中添加集和的方法: addall() 方法;
为什么不怎么用 hashtable
在单线程中,无需做线程控制,运行效率更高;在多线程中, synchronized 会造成线程饥饿,死锁,可以用 concurrentHashMap 替代 .
 
hashmap 性能为什么高一点
它不需要考虑锁,也就没有在处理锁上的时间消耗。
HashMap 里面存入的键值对在取出的时候是随机的,它根据键的 HashCode 值存储数据 , 根据键可以直接获取它的值,具有很快的访问速度。
 
其他集合用的比较多的 list set;
list 中添加集合的方法:
addall() 方法;
 
 
3: ioc aop 的机制和 aop 动态代理,两种的区别;平常用什么代理,用 aop, aop 是怎么用的; aop 的底层实现,基于代理做的,什么是代
理,代理是怎么做到之前之后的处理;
ioc
控制反转, IoC 可以说是 spring 最核心的部分,解析 XML ,获得相应的 Bean 定义,实例化 Bean, 根据注入方式进行注入或利用反射进行注入。
(反射是根据 className 生成一个具体的实例)
 
aop
AOP Aspect Orient Programming ),面向切面编程,作为面向对象的一种补充。
用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等。
通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
 
aop 动态代理
AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是在内存中临时为方法生成一个 AOP 对象,
这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
 
JDK 动态代理和 CGLib 两种的区别
JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类: Proxy InvocationHandler
InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,
动态将横切逻辑和业务逻辑编制在一起。 Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。
 
CGLib 全称为 Code Generation Library ,是一个强大的高性能,高质量的代码生成类库,
可以在运行期扩展 Java 类与实现 Java 接口, CGLib 封装了 asm ,可以再运行期动态生成新的 class
JDK 动态代理相比较: JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,
则可以通过 CGLib 创建动态代理。
 
4: 数据库 mysql 索引的是怎么样的;索引的原理结构,索引的优缺点;
索引( Index )是帮助 MySQL 高效获取数据的数据结构。常见的查询算法 , 顺序查找 , 二分查找 , 二叉排序树查找 , 哈希散列法 ,
分块查找 , 平衡多路搜索树 B 树( B-tree
 
索引的原理结构
所有索引原理都是一样的,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,
也就是我们总是通过同一种查找方式来锁定数据。
 
索引的特点
1. 索引可以加快数据库的检索速度
2. 索引降低了数据库插入、修改、删除等维护任务的速度
3. 索引创建在表上,不能创建在视图上
4. 索引既可以直接创建,也可以间接创建
5. 可以在优化隐藏中,使用索引
6. 使用查询处理器执行 SQL 语句,在一个表上,一次只能使用一个索引
索引的优点
1. 创建唯一性索引,保证数据库表中每一行数据的唯一性
2. 大大加快数据的检索速度,这也是创建索引的最主要的原因
3. 加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
4. 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
5. 通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。
索引的缺点
1. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大
3. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度
 
B+ 树叶子节点是怎么样的,为什么用 b+ 树,结构是怎么样的,怎么构建 b+ 树,
B+ 树叶子节点是怎么样的 , 结构是怎么样的
1. 树中每个结点至多有 m 个孩子;
2. 除根结点和叶子结点外,其它每个结点至少有有 ceil(m / 2) 个孩子;
3. 若根结点不是叶子结点,则至少有 2 个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);
4. 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息
( 可以看做是外部结点或查询失败的结点,实际上这些结点不存在,指向这些结点的指针都为 null)
5. 每个非终端结点中包含有 n 个关键字信息: (n P0 K1 P1 K2 P2 ...... Kn Pn) 。其中:
a) Ki (i=1...n) 为关键字,且关键字按顺序排序 K(i-1)< Ki
b) Pi 为指向子树根的接点,且指针 P(i-1) 指向子树种所有结点的关键字均小于 Ki ,但都大于 K(i-1)
c) 关键字的个数 n 必须满足: ceil(m / 2)-1 <= n <= m-1
 
( B-tree 的差别
1. n 棵子树的结点中含有 n 个关键字; (B-tree n 棵子树有 n-1 个关键字 )
2. 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。
(B-tree 的叶子节点并没有包括全部需要查找的信息 )
3. 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。
(B-tree 的非终节点也包含需要查找的有效信息 )
)
 
为什么用 b+
文件很大,不可能全部存储在内存中,故要存储到磁盘上 , 索引的结构组织要尽量减少查找过程中磁盘 I/O 的存取次数 ,
局部性原理与磁盘预读,预读的长度一般为页( page )的整倍数,数据库系统巧妙利用了磁盘预读原理,
将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入,而红黑树这种结构, h 明显要深的多。
由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性
 
为什么用 b+ 树查词比较快,怎么做到提升效率,
B-Tree 作为索引结构效率是非常高的。
每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,
就实现了一个 node 只需一次 I/O
B-Tree 中一次检索最多需要 h-1 I/O (根节点常驻内存),渐进复杂度为 O(h)=O(logdN) 。一般实际应用中,出度 d 是非常大的数字,
通常超过 100 ,因此 h 非常小(通常不超过 3 )。
 
(
什么情况下用不到索引;
1 、如果条件中有 or
2 、对于多列索引,不是使用的第一部分,则不会使用索引;
3 like 查询是以 % 开头;
4 、存在索引列的数据类型隐形转换,则用不上索引
5 where 子句里对索引列上有数学运算
6 where 子句里对有索引列使用函数
7 、如果 mysql 估计使用全表扫描要比使用索引快
 
执行计划;
执行计划就是 Mysql 如何执行一条 Sql 语句 , 包括 Sql 查询的顺序、是否使用索引、以及使用的索引信息等内容。
explain select ……
explain extended select ...
explain partitions select ...
)
 
怎么构建 B+ 树;??
 
 
 
5: redis memcache 的区别 ; redis 的底层实现;
redis memcache 的区别
1 Redis 不仅仅支持简单的 key/value 类型的数据,同时还提供 list set zset hash 等数据结构的存储。
2 Redis 支持数据的备份,即主备模式的数据备份。
3 Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
 
redis 的底层实现;
底层数据结构共有八种: int long 类型的整数, embstr 编码的简单动态字符串, raw 简单动态字符串 ,ht 字典 ,linkedlist 双端链表 ,
ziplist 压缩列表 ,intset 整数集合 , skiplist 跳跃表和字典
5 种对象类型:字符串对象 , 列表对象 , 哈希对象 , 集合对象 , 有序集合对象
当执行一个处理数据类型的命令时, Redis 执行以下步骤:
根据给定 key ,在数据库字典中查找和它相对应的 redisObject ,如果没找到,就返回 NULL
检查 redisObject type 属性和执行命令所需的类型是否相符,如果不相符,返回类型错误。
根据 redisObject encoding 属性所指定的编码,选择合适的操作函数来处理底层的数据结构。
返回数据结构的操作结果作为命令的返回值。
 
redis 持久化的几种方式
快照
rdb 持久化: rdb 是保存一份数据快照,可以定时出发或者手动触发。 SAVE 命令会使用同步的方式生成 RDB 快照文件,
这意味着在这个过程中会阻塞所有其他客户端的请求。 BGSAVE 命令使用后台的方式保存 RDB 文件
过程:
Redis 调用 fork() ,产生一个子进程。子进程把数据写到一个临时的 RDB 文件。当子进程写完新的 RDB 文件后,把旧的 RDB 文件替换掉。
 
优点: RDB 文件是一个很简洁的单文件,它保存了某个时间点的 Redis 数据,很适合用于做备份。你可以设定一个时间点对 RDB 文件
进行归档,这样就能在需要的时候很轻易的把数据恢复到不同的版本。比起 AOF ,在数据量比较大的情况下, RDB 的启动速度更快。
 
缺点: RDB 容易造成数据的丢失。假设每 5 分钟保存一次快照,如果 Redis 因为某些原因不能正常工作,那么从上次产生快照到 Redis
出现问题这段时间的数据就会丢失了。 RDB 使用 fork() 产生子进程进行数据的持久化,如果数据比较大的话可能就会花费点时间,
造成 Redis 停止服务几毫秒。如果数据量很大且 CPU 性能不是很好的时候,停止服务的时间甚至会到 1 秒。
 
aof 持久化:每当 Redis 接受到会修改数据集的命令时,就会把命令追加到 AOF 文件里,当你重启 Redis 时, AOF 里的命令会被重新
执行一次,重建数据。
 
 
优点:比 RDB 可靠。你可以制定不同的 fsync 策略:不进行 fsync 、每秒 fsync 一次和每次查询进行 fsync 。默认是每秒 fsync 一次。
这意味着你最多丢失一秒钟的数据。
缺点:在相同的数据集下, AOF 文件的大小一般会比 RDB 文件大。
日志重写:新文件上会写入
能重建当前数据集的最小操作命令的集合。
 
过程: Redis 调用 fork() ,产生一个子进程。子进程把新的 AOF 写到一个临时文件里。主进程持续把新的变动写到内存里的 buffer
同时也会把这些新的变动写到旧的 AOF 里,这样即使重写失败也能保证数据的安全。当子进程完成文件的重写后,主进程会获得一
个信号,然后把内存里的 buffer 追加到子进程生成的那个新 AOF 里。
 
6: jvm 的内存结构,内存各个区域的作用;垃圾回收流转顺序,垃圾回收做什么事情,怎么判断一对象会不会被回收,
jvm 的内存结构,内存各个区域的作用
jvm 内存区域主要分为线程私有区域 [ 程序计数器、虚拟机栈、本地方法区 ] 、线程共享区域 [java 堆、方法区 ] 、直接内存。
 
线程私有数据区域生命周期与线程相同 , 依赖用户线程的启动 / 结束 而 创建 / 销毁 ( Hotspot VM , 每个线程都与操作系统的本地
线程直接映射 , 因此这部分内存区域的存 / 否跟随本地线程的生 / 死对应 )
线程共享区域随虚拟机的启动 / 关闭而创建 / 销毁。
 
直接内存并不是 JVM 运行时数据区的一部分 , 但也会被频繁的使用 : JDK 1.4 引入的 NIO 提供了基于 Channel Buffer IO 方式 ,
它可以使用 Native 函数库直接分配堆外内存 , 然后使用 DirectByteBuffer 对象作为这块内存的引用进行操作 ( 详见 : Java I/O 扩展 ),
这样就避免了在 Java 堆和 Native 堆中来回复制数据 , 因此在一些场景中可以显著提高性能。
 
程序计数器 ( 线程私有 )
一块较小的内存空间 , 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,
这类内存也称为 线程私有 的内存。
正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。
这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。
 
虚拟机栈 ( 线程私有 )
是描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧( Stack Frame )用于存储局部
变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到
出栈的过程。
栈帧( Frame )是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking) 、 方法返回值和异常
分派( Dispatch Exception )。栈帧随着方法调用而创建,随着方法结束而销毁 —— 无论方法是正常完成还是异常完成(抛出了在
方法内未被捕获的异常)都算作方法结束。
 
本地方法区 ( 线程私有 )
本地方法区和 Java Stack 作用类似 , 区别是虚拟机栈为执行 Java 方法服务 , 而本地方法栈则为 Native 方法
服务 , 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用 , 那么该栈将会是一个 C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟
机栈合二为一。
 
堆( Heap- 线程共享) - 运行时数据区
是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。
由于现代 VM 采用分代收集算法 , 因此 Java 堆从 GC 的角度还可以细分为 : 新生代 (Eden 区、 From Survivor 区和 To Survivor ) 和老年代。
 
方法区 / 永久代(线程共享)
即我们常说的永久代 (Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 .
HotSpot VM GC 分代收集扩展至方法区 , 即使用 Java 堆的永久代来实现方法区 , 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样
管理这部分内存 , 而不必为方法区开发专门的内存管理器 ( 永久带的内存回收的主要目标是针对常量池的回收和类型的卸载 , 因此收益
一般很小 ) 。 运行时常量池( Runtime Constant Pool )是方法区的一部分。 Class 文件中除了有类的版本、字段、方法、接口等描述等
信息外,还有一项信息是常量池( Constant Pool Table ),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后
存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于
存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。
 
JAVA8 与元数据
Java8 中,永久代已经被移除,被一个称为 元数据区 (元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间
最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入
native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制 , 而由系统的实际可
用空间来控制。
 
垃圾回收流转顺序,垃圾回收做什么事情,怎么判断一对象会不会被回收?
垃圾回收会跟踪所有仍在使用的对象,然后将剩余的对象标记为垃圾。回收无用内存空间并可用于未来实例的过程。
 
MinorGC 的过程(复制 -> 清空 -> 互换), MinorGC 采用复制算法
当一个实例被创建了,首先会被存储在堆内存新生代的 Eden 区中。(如果新创建的对象占用内存很大,则直接分配到老年代)。
首先,把 Eden ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄达到了老年的标准,则赋值到老年代区),
同时把这些对象的年龄 +1 (如果 ServicorTo 不够位置了就放到老年区);
然后,清空 Eden ServicorFrom 中的对象;
最后, ServicorTo ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom 区。
 
如何确定垃圾
引用计数法
Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否
可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数为 0 ,则说明对象不太可能再被用到,那么这个对象就是
可回收对象。
可达性分析
为了解决引用计数法的循环引用问题, Java 使用了可达性分析的方法。通过一系列的 “GC roots” 对象作为起点搜索。如果在 “GC roots”
和一个对象之间没有可达路径,则称该对象是不可达的。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,
则将面临回收。
 
标记清除算法( Mark-Sweep
复制算法( copying
标记整理算法 (Mark-Compact)
分代收集算法
 
 
7: java 的锁机制:高并发有什么锁机制;区别,怎么实现的; volatile 底层怎么做到可见性的;锁有什么机制; sy 底层怎么实现的; lock sy 区别;
锁机制 有 乐观锁,悲观锁,自旋锁
 
乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在
更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样
则更新),如果失败则要重复读 - 比较 - 写的操作。 java 中的乐观锁基本都是通过 CAS 操作实现的, CAS 是一种更新的原子操作,比较当前值跟传入
值是否一样,一样则更新,否则失败。
 
悲观锁 悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的
时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。 java 中的悲观锁就是 Synchronized,AQS 框架下的锁则是先尝试 cas 乐观锁去获取锁,
获取不到,才会转换为悲观锁,如 ReentrantLock
 
自旋锁 自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之
间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
线程自旋是需要消耗 cpu 的,说白了就是让 cpu 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cpu 自旋做无用功,所以需要设定一个自旋
等待的最大时间。 如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不
到锁,这时争用线程会停止自旋进入阻塞状态。
 
把代码块声明为 synchronized ,有两个重要后果,通常是指该代码具有 原子性( atomicity )和 可见性( visibility )。
原子性意味着一个线程一次只能执行由一个指定监控对象( lock )保护的代码,从而防止多个线程在更新共享状态时相互冲突。
可见性则更为微妙;它要对付内存缓存和编译器优化的各种反常行为。一般来说,线程以某种不必让其他线程立即可以看到的方式
(不管这些线程在寄存器中、在处理器特定的缓存中,还是通过指令重排或者其他编译器优化),不受缓存变量值的约束。
Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。
sychronized 修饰的方法或者语句块在代码执行完之后锁自动释放,而用 Lock 需要我们手动释放锁。
 
Synchronized 实现
1. JVM 每次从队列的尾部取出一个数据用于锁竞争候选者( OnDeck ),但是并发情况下, ContentionList 会被大量的并发线程进行 CAS 访问,
为了降低对尾部元素的竞争, JVM 会将一部分线程移动到 EntryList 中作为候选竞争线程。
2. Owner 线程会在 unlock 时,将 ContentionList 中的部分线程迁移到 EntryList 中,并指定 EntryList 中的某个线程为 OnDeck 线程
(一般是最先进去的那个线程)。
3. Owner 线程并不直接把锁传递给 OnDeck 线程,而是把锁竞争的权利交给 OnDeck OnDeck 需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的
提升系统的吞吐量,在 JVM 中,也把这种选择行为称之为 竞争切换
4. OnDeck 线程获取到锁资源后会变为 Owner 线程,而没有得到锁资源的仍然停留在 EntryList 中。如果 Owner 线程被 wait 方法阻塞,则转移到 WaitSet 队列
中,直到某个时刻通过 notify 或者 notifyAll 唤醒,会重新进去 EntryList 中。
5. 处于 ContentionList EntryList WaitSet 中的线程都处于阻塞状态,该阻塞是由操作系统来完成的( Linux 内核下采用 pthread_mutex_lock 内核函数实现的)。
6. Synchronized 是非公平锁。 Synchronized 在线程进入 ContentionList 时,等待的线程会先尝试自旋获取锁,如果获取不到就进入 ContentionList
这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁资源。
7. 每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象,代码块加锁是在前后分别加上 monitorEnter monitorExit 指令来实现的,方法加锁是通过一个
标记位来判断的
8. synchronized 是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有用操作消耗的时间更多。
9. Java1.6 synchronized 进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的 Java1.7 1.8 中,
均对该关键字的实现机理做了优化。引入了偏向锁和轻量级锁。都是在对象头中有标记位,不需要经过操作系统加锁。
10. 锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫做锁膨胀;
11. JDK 1.6 中默认是开启偏向锁和轻量级锁,可以通过 -XX:-UseBiasedLocking 来禁用偏向锁。
 
ReentrantLock synchronized 区别
1. ReentrantLock 通过方法 lock() unlock() 来进行加锁与解锁操作,与 synchronized 会被 JVM 自动解锁机制不同, ReentrantLock 加锁后需要手动进行解锁。
为了避免程序出现异常而无法正常解锁的情况,使用 ReentrantLock 必须在 finally 控制块中进行解锁操作。
2. ReentrantLock 相比 synchronized 的优势是可中断、公平锁、多个锁。这种情况下需要使用 ReentrantLock
 
volatile 底层怎么做到可见性的
volatile 变量,用来确保将变量的更新操作通知到其他线程。
在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此 volatile 变量是一种比 sychronized 关键字更轻量级的同步机制。 volatile
合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。
当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。如果计算机有多个 CPU ,每个线程可能在不同的 CPU 上被处理,这意味着每
个线程可以拷贝到不同的 CPU cache 中。而声明变量是 volatile 的, JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
 
8: 对于加密有什么了解;(对称加密, https 加密的认证的过程,公钥, 私钥;)
对称加密算法也就是加密和解密用相同的密钥 , 高级加密标准 (AES,Advanced Encryption Standard) 为最常见的对称加密算法 ( 微信小程序加密传输就是用这个加
密算法的 )
非对称加密是通过两个密钥(公钥 - 私钥)来实现对数据的加密和解密的。公钥用于加密,私钥用于解密。 RSA 加密算法是一种典型的非对称加密算法,它基于大
数的因式分解数学难题,它也是应用最广泛的非对称加密算法。
 
https 加密的认证的过程
1 、客户端发起 HTTPS 请求
这个没什么好说的,就是用户在浏览器里输入一个 HTTPS 网址,然后连接到服务端的 443 端口。
2 、服务端的配置
采用 HTTPS 协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继
续访问,而使用受信任的公司申请的证书则不会弹出提示页面。这套证书其实就是一对公钥和私钥。
3 、传送证书
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
4 、客户端解析证书
这部分工作是由客户端的 SSL/TLS 来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警示框,
提示证书存在的问题。如果证书没有问题,那么就生成一个 *** 随机值 *** 。然后用证书(也就是公钥)对这个随机值进行加密。
5 、传送加密信息
这部分传送的是用证书加密后的随机值,目的是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。
6 、服务端解密信息
服务端用私钥解密后,得到了客户端传过来的随机值,然后把内容通过该随机值进行对称加密,将信息和私钥通过某种算法混合在一起,这样
除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
7 、传输加密后的信息
这部分信息就是服务端用私钥加密后的信息,可以在客户端用随机值解密还原。
8 、客户端解密信息
客户端用之前生产的私钥解密服务端传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。
 
对称加密:
encrypt( 明文,秘钥 ) = 密文
decrypt( 密文,秘钥 ) = 明文
 
非对称加密:
encrypt( 明文,公钥 ) = 密文
decrypt( 密文,私钥 ) = 明文
 
TLS 主要提供三个基本服务
加密
身份验证,也可以叫证书验证
消息完整性校验
 
10: 计算机网络的知识,七层协议,: tcp, udp 哪一层,滑动窗口,具体是怎么实现的;
物理层 (PH) 、数据链路层 (DL) 、网络层 (N) 、传输层 (T) 、会话层 (S) 、表示层 (P) 、应用层 (A)
TCP (Transmission Control Protocol) UDP(User Datagram Protocol) 协议属于传输层协议
 
滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序
号,称为接收窗口。发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。发送方窗口内的序列号代
表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。
下面举例说明,假设发送窗口尺寸为 2 ,接收窗口尺寸为 1
分析:
初始态,发送方没有帧发出,发送窗口前后沿相重合。接收方 0 号窗口打开,等待接收 0 号帧;
发送方打开 0 号窗口,表示已发出 0 帧但尚确认返回信息。此时接收窗口状态不变;
发送方打开 0 1 号窗口,表示 0 1 号帧均在等待确认之列。至此,发送方打开的窗口数已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数
据帧。接收窗口此时状态仍未变;
接收方已收到 0 号帧, 0 号窗口关闭, 1 号窗口打开,表示准备接收 1 号帧。此时发送窗口状态不变;
发送方收到接收方发来的 0 号帧确认返回信息,关闭 0 号窗口,表示从重发表中删除 0 号帧。此时接收窗口状态仍不变;
发送方继续发送 2 号帧, 2 号窗口打开,表示 2 号帧也纳入待确认之列。至此,发送方打开的窗口又已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发
送新的数据帧,此时接收窗口状态仍不变;
接收方已收到 1 号帧, 1 号窗口关闭, 2 号窗口打开,表示准备接收 2 号帧。此时发送窗口状态不变;
发送方收到接收方发来的 1 号帧收毕的确认信息,关闭 1 号窗口,表示从重发表中删除 1 号帧。此时接收窗口状态仍不变。
 
滑动窗口功能:确认、差错控制、流量控制。
 
参考链接:

aop切面的实现
https://blog.csdn.net/menghuanzhiming/article/details/73792336

https://www.jianshu.com/p/397d8637f8bc

动态代理jdk 和cgib的区别:
jdk动态代理深入解析:
https://www.cnblogs.com/duanxz/archive/2012/12/03/2799504.html

aop内部动态代理解析:
https://blog.csdn.net/u014010769/article/details/52664520
加assert注解, 然后excution实现在哪个方法前,后或者前后增加方法。对方法重新编辑。

代理模式对解析:
静态代理
动态代理
https://segmentfault.com/a/1190000011291179

数据库:mysql索引 实现 b+树
索引的原理结构与优缺点:
https://www.jianshu.com/p/84aeae986cd9

为什么使用b+树做索引
https://blog.csdn.net/weixin_30531261/article/details/79312676

b+树的实现
https://juejin.im/post/5b9073f9f265da0acd209624

Redis与Memcached的区别
https://www.jianshu.com/p/23ea6815eca8

Redis的底层实现:
https://www.cnblogs.com/jaycekon/p/6227442.html

Java的内存结构:
http://www.importnew.com/23746.html

Java的垃圾回收:
https://blog.csdn.net/qq496013218/article/details/76968464

如何判断一个对象是否应该被回收:
https://www.zhihu.com/question/34684686

Java的锁机制:
https://juejin.im/post/5adf14dcf265da0b7b358d58

https://tech.meituan.com/2018/11/15/java-lock.html

Java锁synchronized的底层原理:
https://blog.csdn.net/javazejian/article/details/72828483

滑动窗口的实现:
https://blog.csdn.net/wdscq1234/article/details/52444277

猜你喜欢

转载自www.cnblogs.com/liguo-wang/p/10604697.html