QQ空间 新浪微博 腾讯微博 微信 更多
  

阿里面试回答的认真总结

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 构造方法中,可供我们调整的参数有两个,一个是初始容量 initialCapacitytable数组的大小,缺省值为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非线程安全,它根据keyhashCode值来保存value,不保证遍历的顺序和插入的顺序是一致的。
HashMap允许有一条记录的keynull,但是对值是否为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: iocaop的机制和aop动态代理,两种的区别;平常用什么代理,用aop, aop是怎么用的;aop的底层实现,基于代理做的,什么是代
理,代理是怎么做到之前之后的处理;
ioc
控制反转,IoC可以说是spring最核心的部分,解析XML,获得相应的Bean定义,实例化Bean,根据注入方式进行注入或利用反射进行注入。
(反射是根据className生成一个具体的实例)
 
aop
AOPAspect Orient Programming),面向切面编程,作为面向对象的一种补充。
用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等。
通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
 
aop动态代理
AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,
这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
 
JDK动态代理和CGLib两种的区别
JDK动态代理主要涉及到java.lang.reflect包中的两个类:ProxyInvocationHandler
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个关键字信息: (nP0K1P1K2P2......KnPn)。其中:
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-treen棵子树有n-1个关键字)
2.所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。
(B-tree的叶子节点并没有包括全部需要查找的信息)
3.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。
(B-tree的非终节点也包含需要查找的有效信息)
)
 
为什么用b+
文件很大,不可能全部存储在内存中,故要存储到磁盘上,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,
局部性原理与磁盘预读,预读的长度一般为页(page)的整倍数,数据库系统巧妙利用了磁盘预读原理,
将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入,而红黑树这种结构,h明显要深的多。
由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性
 
为什么用b+树查词比较快,怎么做到提升效率,
B-Tree作为索引结构效率是非常高的。
每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,
就实现了一个node只需一次I/O
B-Tree中一次检索最多需要h-1I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,
通常超过100,因此h非常小(通常不超过3)。
 
(
什么情况下用不到索引;
1、如果条件中有or
2、对于多列索引,不是使用的第一部分,则不会使用索引;
3like查询是以%开头;
4、存在索引列的数据类型隐形转换,则用不上索引
5where 子句里对索引列上有数学运算
6where 子句里对有索引列使用函数
7、如果mysql估计使用全表扫描要比使用索引快
 
执行计划;
执行计划就是Mysql如何执行一条Sql语句,包括Sql查询的顺序、是否使用索引、以及使用的索引信息等内容。
explain select ……
explain extended select ...
explain partitions select ...
)
 
怎么构建B+树;??
 
 
 
5: redismemcache的区别; redis的底层实现;
redismemcache的区别
1 Redis不仅仅支持简单的key/value类型的数据,同时还提供listsetzsethash等数据结构的存储。
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提供了基于ChannelBufferIO方式,
它可以使用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 VMGC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样HotSpot的垃圾收集器就可以像管理Java堆一样
管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益
一般很小)。 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等
信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后
存放到方法区的运行时常量池中。 Java虚拟机对Class文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于
存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。
 
JAVA8与元数据
Java8中,永久代已经被移除,被一个称为元数据区(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间
最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入
native memory, 字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可
用空间来控制。
 
垃圾回收流转顺序,垃圾回收做什么事情,怎么判断一对象会不会被回收?
垃圾回收会跟踪所有仍在使用的对象,然后将剩余的对象标记为垃圾。回收无用内存空间并可用于未来实例的过程。
 
MinorGC的过程(复制->清空->互换),MinorGC采用复制算法
当一个实例被创建了,首先会被存储在堆内存新生代的 Eden 区中。(如果新创建的对象占用内存很大,则直接分配到老年代)。
首先,把EdenServivorFrom区域中存活的对象复制到ServicorTo区域(如果有对象的年龄达到了老年的标准,则赋值到老年代区),
同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区);
然后,清空EdenServicorFrom中的对象;
最后,ServicorToServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区。
 
如何确定垃圾
引用计数法
Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否
可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数为0,则说明对象不太可能再被用到,那么这个对象就是
可回收对象。
可达性分析
为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”
和一个对象之间没有可达路径,则称该对象是不可达的。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,
则将面临回收。
 
标记清除算法(Mark-Sweep
复制算法(copying
标记整理算法(Mark-Compact)
分代收集算法
 
 
7: java的锁机制:高并发有什么锁机制;区别,怎么实现的;volatile底层怎么做到可见性的;锁有什么机制;sy底层怎么实现的;locksy区别;
锁机制 有 乐观锁,悲观锁,自旋锁
 
乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在
更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样
则更新),如果失败则要重复读-比较-写的操作。 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线程,而是把锁竞争的权利交给OnDeckOnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的
提升系统的吞吐量,在JVM中,也把这种选择行为称之为竞争切换
4. OnDeck线程获取到锁资源后会变为Owner线程,而没有得到锁资源的仍然停留在EntryList中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列
中,直到某个时刻通过notify或者notifyAll唤醒,会重新进去EntryList中。
5. 处于ContentionListEntryListWaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。
6. Synchronized是非公平锁。 Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList
这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。
7. 每个对象都有个monitor对象,加锁就是在竞争monitor对象,代码块加锁是在前后分别加上monitorEntermonitorExit指令来实现的,方法加锁是通过一个
标记位来判断的
8. synchronized是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有用操作消耗的时间更多。
9. Java1.6synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的Java1.71.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帧但尚确认返回信息。此时接收窗口状态不变;
发送方打开01号窗口,表示01号帧均在等待确认之列。至此,发送方打开的窗口数已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数
据帧。接收窗口此时状态仍未变;
接收方已收到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

 

posted @ 2019-03-26 23:32  nupt想象之中  阅读(460)  评论(0编辑  收藏  举报