一.常用的集合
Collection下的List集合的话有ArrayList和LinkedList,Set集合常用的有HashSet。双列集合Map有HashMap,当设计到线程安全性问题时有用到ConcurrentHashMap。
1.ArrayList的底层时一个数组,然后它初始化的时候数据量时0,当你add的时候它的默认变成10,它的扩容机制每次扩容时它之前容量的1.5倍,它的特性时查询时通过脚标查询是比较快的,但是删除效率比较低。
2.LinkedList的底层是带有头节点和尾节点的双向链表,它提供的两种插入方式,一个是头插LinkedFirst,尾插是LinkedLast,它的特性是适合于经常增加、删除操作的场景,在查询量比较多的场景下,LinkedList的效率是比较慢的。因为它是链表查询,拿值按照顺序从第一个开始一个一个的进行比较。
3.在遇到线程安全问题的时候,想使用List可以使用Vector,跟ArrayList一样,它的底层是一个数组,但它的大部分方法都被synchronized关键字所修饰,所以说它是线程安全的,但效率会相对慢点。它扩容的时候和ArrayList还有点区别,他的扩容大小默认是两倍。
4.HashMap,先从它的底层结构开始说起,在1.7和1.8的时候是不同的。1.7时它是数组加上一个单链表,数据节点是一个Entry节点这样一个类,它在数据插入过程。1.8的时候是数组加上一个单链表或者红黑树的方式,把原来的Entry节点也编程了一个Node节点,单链表和红黑树的转换是,它的单链表长度大于等于8,并且它的哈希桶大于等于64的时候,它会将单链表转换成红黑树形式存储。它在红黑树的节点的数量小于等于6的时候,它会在重新转成一个单链表,这是它底层结构的一个变化。另外一点关于它的Hash桶的数量,默认是16个,阈值默认时0.75。扩容的时候它是首先会检测数组里的元素个数,因为LoadFactor负载因子默认为0.75,阈值是16 * 0.75 = 12 ,当它哈希桶占用的容量大于12的时候就会触发扩容,它会扩容成之前哈希桶容量的2倍,它会同乘2的N次幂,然后会把之前的元素再次进行一次哈希运算,添加到新的哈希桶里面,按照链表或者红黑树的方式再排列起来。它不是线程安全的,在put插入操作的时候多线程会有数据覆盖的可能,还有个因素是1.7版本put时有个resize的过程,然后它又调用叻transfer()方法,把里面的entry进行了一个rehash,这个过程会造成它的头插会形成一个环形链表,在下一次get的时候导致一直死循环,1.8好像是改成尾插了,没有改变数据插入的这么一个顺序,所以不会出现一个链表循环的过程。正常的保证它的线程安全平时开发的话会直接用ConcurrentHashMap,通俗意义上觉得他更常用一些,或者使用Collections的synchronizedMap()方法,使他具有线程安全能力。ConcurrentHashMap,它的并发度可能是更高的,它只会锁住目前所获取到的Entry所在的那个节点的值,上锁的时候是使用了CAS+Synchronized。
5.ConcurrentHashMap,首先它的数据结构在1.7版底层是个分片数组,为了保证线程安全它有个Segment分段锁,这个Segment继承ReentrantLock来保证线程安全的,它每次只给一段加速来保证它的并发度。在1.8的时候改成了和HashMap一样的数据结构,数组加单链表或者红黑树的数据结构,逐渐放弃这种分片锁的机制而是使用的Synchronized和CAS来操作。因为1.6版本的话对JVM和Synchronized的优化(锁升级的一个过程)非常大,现在也是用这种方法保证它的线程安全。
tips:锁升级的过程:最开始的时候(是无锁的状态 先去判断一下)我们的这个锁是支持偏向锁的,我优先获取到锁资源的线程,我会优先让它再去获取到这个锁,如果它没有获取到这个锁,就升级成一个轻量级的CAS的锁就是一个乐观锁,它是一个比较有交换的过程。如果CAS连续没有设置成功的话,它会进行一个自旋,自旋到一定次数之后才会升级成一个Synchronized就是这么一个重量级的锁,这样的话,能保证它的性能问题。
二.聊聊CAS(Compare and Swap 比较并替换)
1.CAS相当于一个轻量级的加锁的过程,比如说在并发量不是很大的情况下,锁竞争不是很激烈,你要修改个东西的话,需要先查,查完再修改。修改完再写之前,它还会再查一次,比较之前的结果有没有区别。若有区别说明这个修改是不安全的,如果没有区别说明修改是安全的。这时候它会去安全的修改,而不是直接加锁的方式,在低并发的情况下性能会好一点。
2.缺点:当并发量特别大的时候,它会有一个忙循环的过程,对CPU的性能消耗是比较大的,当高并发的时候还是建议直接使用状态机,锁之类的。另外一点,它可能会产生一个ABA的问题,就是之前读和再过段时间读中间会被第三人修改过,但是又改回来了,这个问题的话可以通过添加一个戳或者标志位来保证它中间没有被篡改过。
三.聊聊Synchronized
关于Synchronized的话,可以用在比如说在同步代码块,可以指定任意对象作为锁。当它应用于方法上时,它的锁就是This。如果用来静态方法上,则它的锁是class对象。
(12分钟左右的听不懂了。。。)
1.什么时候使用Synchronized,什么时候使用ReentrantLock????
Synchronized是JVM层面的一个关键字,使用比较简单,直接使用同步代码块或者同步方法,我也不需要担心锁的释放。而ReentrantLock是一个类需要手动去Lock,然后配合try finally代码块一定要去把锁释放。
ReentrantLock相比Synchronized是有几个高级特性的:
(1)如果一个线程长期等待不到一个锁的时候,为了防止死锁,可以手动的去调用一个LockInterruptibly去尝试释放这个锁,释放自己的资源不去等待
(2)ReentrantLock提供了一个构建公平锁的方式,它的构造函数有一个但是不推荐使用,他会让ReentrantLock等级下降。
(3)此外他提供了一个condition,你可以指定去唤醒绑定到condition身上的线程,来实现选择性通知的一个机制。
如果不需要用到这几种特性的话,还是使用Synchronized比较好的。在优化JDK的时候方便去了解当前哪些锁被哪些线程所持有,这些状态ReentrantLock不能相比的
2.细聊ReentrantLock怎么实现比如公平锁、非公平锁的,底层AQS等
四.并发包下面都用过哪些类
automicInteget。。。
五.Volatile
它是不能保证变量的原子性的,像是自增自减这种操作它是不能保证的。
原子性的话可以使用像Synchronized或者ReentrantLock加锁来保证原子性
六.总线嗅探机制
。。。。
七.线程池
(24分钟左右)
八.SQL语句调优,索引
1.平时SQL调优的思路:
表要有主键,有主键的表MySQL会创建聚簇索引,聚簇索引的好处是它的主键和数据行是在一行的,在explain查询语句的时候会发现它的type级别是const,这是很高的一种级别。
有主键的话,如果一条SQL查询很慢,可以去查看它是否建立了相应的索引,建议索引的字段最好可以是where条件后面的或者是orderby,groupby或者是join连接的字段作为索引列,并且这些索引列也要排个序,要符合最左匹配原则。选的时候根据索引选择器,你的非重复的数据行和重复的数据行中间的排列大的放左小的放右这样的形式。这种多列的索引列,要去建立联合索引而非单个索引。这个是索引的选择。
2.在SQL书写的时候,不要将索引列放到一个表达式中,或者说你用了一些反向判断比如Notnull,不等于,NotIn这种关键词,都会让索引失效的。
3.数据查询非常频繁,可以考虑使用覆盖索引,可以直接在索引中查询到数据。
建唯一索引号还是普通索引好点
普通索引它的索引存放的是叶子节点和它的主键和它的索引列,在查询的时候是通过索引列查询到它的主键,通过主键再回表查询,还是走一个唯一索引查的,它有一个回表的过程,速度相对来说慢点。
唯一索引,确保列的唯一,多一次判断过程,开销很小,真正的开销是在他的Buffer区
九.MVCC和事务隔离级别的关系
InnoDB引擎的特性就是它是支持事务的,为了保证事务的并发度,它提供了一个隔离级别。它有四种隔离级别:一种是Read Uncommited,结果什么都不能保证,第二种是Read Commited,可以有效的解决事务之间数据的脏读,但对于数据的不可重复读和幻读是不能保证的。第三个是MySQL的默认隔离级别Repeatable Read,它可以有效解决脏读和不可重复读。最后一个是Serializable,它是最高的安全性级别。
当索引范围内查询的时候,后面加上for uodate锁住未出现的那个行,保证你的事务中不读到其他事务中提交的数据行。另外一点for update所著未出现的那行,用的是一个行级锁。
关于事务另一个问题的话,就是丢失更新,手动处理一下。乐观锁:类似之前CAS的加个版本号。悲观锁:在等人查询的时候加个for update给那行上个锁,来防止丢失更新。
MVCC(多版本并发控制):
它是InnoDB所支持的
应用高并发事务,MVCC比单纯的加行锁的形式有效,且开销更小
MVCC可以使用乐观锁和悲观锁形式来实现
MVCC只能在读已提交和可重复读两个隔离级别下实现,其他两个不兼容
innoDB实现Mysql的方式:
1.事务的排他锁形式修改数据
2.修改之前先把数据放到Undolog,通过回滚指针关联
3.成功了什么都不做,失败了从Undolog回滚数据
看redlog undolog binlog
Spring做到隔离级别用到的@Transactional注解,隔离级别多一个默认的,传播特性有7个
十.Spring AOP是怎么实现的,有几种实现方式
JDK动态代理主要是使用了JAVA反射中的两个类,一个是Proxy,另一个是InvocationHandler,通过Bind的方式与原来的实现类保持关系,并且它是通过Proxy.newProxyInstance()的来创建代理对象,第一个参数是类的类加载器,第二个是它实现的接口数组,第三个是InvocationHandler对象
十一.Spring的生命周期,怎么区解决它的循环依赖的(三级缓存有关)?
十二.JVM的内存模型
JVM运行时的内存区域可以分为两大类,一个是它线程的私有区,另一个是线程共享区。对于线程私有,首先第一个就是它的程序计数器,这里是在JVM中栈内存比较小的一个地方,但是也是唯一一块不会OOM(out of memory)的区域,这里会告诉你执行的代码归于哪一行,上下文切换之后它可以回到一个正确的状态,另外线程私有的是虚拟机栈和本地方法栈,这里虚拟机栈主要使用的是栈帧,一个方法的调用就是一个栈帧从入栈到出栈的过程。接着是你的线程共享区,首先要说到的是方法区,在方法区呢它其实就是JVM的一个规范,1.7版它的实现是在HotSpot虚拟机中叫做永久代,存放的是一些常量池常量还有类的元数据信息,1.8的时候它转移到了一个集结内存中,叫做元空间,存放的是类的元数据信息,这里区域也是会OOM的,还有一个区域是堆,它里面存放的是Java里产生的对象、对象的实例,它也是GC重点回收的一个区域,会产生OOM。最后一个是运行常量池,它是在1.8时将运行常量池转移到堆中,因为之前它时在元空间里面,后来搬到堆里面了,堆里面存放的时常量池被加载之后运行时常量池和静态变量都存储到了堆中。关于直接内存的话,它现在存放的是JVM的元空间,放在这里有个好处就是,OOM的基于相对之前变小了很多
2.垃圾回收算法:
(1)标记清除。他因为这种使用可能会造成很多的内存碎片,当它对象比较大的时候,他的标记效率是比较低的。
(2)标记复制。将堆划分成两块,GC时活动对象复制到另一半,把之前那块抹掉。缺点就是它会造成你的内存支持量偏低,但是它好处不会产生内存碎片。
(3)标记整理。每次只使用一块区域,将一些死亡的对象往另一端移动、复制然后就可以找出剩下的一块区域
(4)分代回收
3.垃圾回收器
(1)CMS:
(2)G1:
4.可达性分析
十三.GC排查过程,如果CPU突然飙高了,怎么排查?
如果服务linux上,跑命令去查看下当前CPU百分百的进程ID,会jstack命令登上进程来代替...............
十四.dubbo的服务暴露过程
dubbo协议适用于小数据量,高并发,消费者大于生产者的方式
十五.Nginx的使用方法以及负载均衡策略
策略:轮询,一致性哈希,还有加权哈希
一致性哈希就是相同请求参数的情况都会打到同一台机器上。
(压缩,虚拟主机配置,负载均衡配置)
十六.常用的设计模式
单例模式,工厂模式,模板模式,建造者模式,策略模式,装饰者模式等
十七.Redis缓存击穿、穿透、雪崩
1.缓存击穿 :
客户端请求打到我们的服务器上,如果需要数据交互的话,是需要去从redis或者mysql中去查询数据的。而我们的数据库是主键自增,如果此时有个key是-1,在redis中没有对应的value,在数据库中也查不到,所以这种情况下直穿两层,打到我们的mysql上,会造成较大的访问压力,可能会打崩数据库。
解决方案:-1如果不在redis中就添加它的值为null,但是如果拿-2 , -3 , UUID等来请求访问的话,这种方案是不可取的。需要在redis和mysql中间用到布隆过滤器,怎么设计呢?把mysql的id备份一份,先经过过滤器筛选。它底层的一个原理?它能判断这个东西一定不在这里面,但不能判断一定在里面,就听起来有点拗口,原因是它的内部数据像我们比方说key为1,它会经过三次不同的哈希运算,存放在一个哈希数组中,在对应的三个位置给它置1。下次进来一个值的时候,也同样会经过三次哈希运算,如果这三个位置对不上的话,就说明一定不存在这里面。他们的点可能有些会一样,会不会存在三个都一样的呢,其实也是会的。再创建布隆过滤器的时候会去设置一个固定大小和偏差率,如果这时候你设置小且你的数据量比较多的情况下,误差率会越来越大。因为就这么大一个池子在里面,哈希来哈希去也就在这里面,到最后这里面就全部都填满了,所以说这个需要去提前预估值的大小和你锁能接受的误差率。
布隆过滤器在电商系统中的应用场景:
(1)爬虫系统堆URL去重,避免爬取重复数据
(2)邮件系统反垃圾邮箱
(3)抖音快手推送系统,基本不会推送看过的数据等
各个实现可能大同小异把...
![](https://img.haomeiwen.com/i12486329/58c3488896c8ee6a.png)
2.缓存击穿:
比较在一个限时抢购的场景下,redis中有一个库存,有一万个。如果这时候这个缓存的key突然失效了(或者redis突然挂了),它就会一直访问到我们的数据库,给压力。所以的话对于这种热点数据最好就不设置失效时间的,但是写代码的,难保一定不失效。最好的话可能是换用第三方的缓存,像谷歌的Guava和Ehcache,中间再做一层缓存的方案。
3.缓存雪崩
redis中的key大面积同时失效,原因是因为存放时设置了相同的失效时间。
解决方式:不让他们在同一时间失效,
缓存的话就是一种技术,有利有弊,对Redis保证可用可以从三个时间段分析:
(1)事前:部署redis的高可用,主从+哨兵,集群,避免全盘崩溃。
(2)事中:本地ehcache缓存 + hystrix限流+降级(做这个限流组件,限定能经受的每秒的最大QPS是多少,只让这么多请求进去),避免mysql被打死
(3)事后:redis持久化 RDB+AOF,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
十八.JAVA8的新特性
十九.ThreadLocal的底层原理
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。
ThreadLocal local1 = new ThreadLocal();
local1 .set("张三");
String name = local1 .get();
localName.remove();//在移除前可以拿到数据
需要理解的是,set存值的过程。
public void set(T value) {
Thread t = Thread.currentThread();// 获取当前线程
ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
if (map != null) // 校验对象是否为空
map.set(this, value); // 不为空set
else
createMap(t, value); // 为空创建一个map对象
}
获取当前线程,通过它的一个变量threadLocals获得一个ThreadLocalMap这样一个数据结构
二十.索引
1.本质:索引是为了加速对表中数据行的检索而创建的分散存储的数据结构。
网友评论