应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

2020最后3天,得了个鬼的肺炎

  前言
  为什么写这些内容,因为2020年3月份疫情的原因裁员,而我自己本身从2015年开始就基本没有出去面试过了。
  所以,在年初的时候就开始网上找问题、刷面试题,然后疯狂的面试。
  中间形成了一个文档,不过内容都是网上整理来的,然后从2020年7月底的时候,开始想自己写个公众号,同时把这些东西分享出来,因为觉得虽然是从网上看来的部分面试题,但是同时也加上了自己和朋友真实的面试真题,对其他人应该也是挺有帮助的。
  然后陆续发了一个月,慢慢的把这些笔记都发到公众号上了,只不过后面发现其实这不是原创啊,没办法,于是乎,就重新再次整理,画图,这个过程也可以算是一个重新提高自己的过程。毕竟,给自己看的明白和能让别人能明白还是不一样的。
  今天,2020年最后一天,几天前突然发烧了,体温一会上去一会下来的,熬了几天发现扛不住了,去医院一检查,好家伙,肺炎了,挂水10天,请了几天假,顺便就趁着今天最后一天把这4个多月以来写的还不错的整理出来了,超过5万字了,4个多月5万多字也不少了,哈哈。
  公众号回复面经下载PDF。
  Mysql能说下myisam和innodb的区别吗?
  myisam引擎是5。1版本之前的默认引擎,支持全文检索、压缩、空间函数等,但是不支持事务和行级锁,所以一般用于有大量查询少量插入的场景来使用,而且myisam不支持外键,并且索引和数据是分开存储的。
  innodb是基于聚簇索引建立的,和myisam相反它支持事务、外键,并且通过MVCC来支持高并发,索引和数据存储在一起。说下mysql的索引有哪些吧,聚簇和非聚簇索引又是什么?
  索引按照数据结构来说主要包含B树和Hash索引。
  假设我们有张表,结构如下:createtableuser(idint(11)notnull,ageint(11)notnull,primarykey(id),key(age));
  B树是左小右大的顺序存储结构,节点只包含id索引列,而叶子节点包含索引列和数据,这种数据和索引在一起存储的索引方式叫做聚簇索引,一张表只能有一个聚簇索引。假设没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有的话则会隐式定义一个主键作为聚簇索引。
  这是主键聚簇索引存储的结构,那么非聚簇索引的结构是什么样子呢?非聚簇索引(二级索引)保存的是主键id值,这一点和myisam保存的是数据地址是不同的。
  最终,我们一张图看看InnoDB和Myisam聚簇和非聚簇索引的区别
  那你知道什么是覆盖索引和回表吗?
  覆盖索引指的是在一次查询中,如果一个索引包含或者说覆盖所有需要查询的字段的值,我们就称之为覆盖索引,而不再需要回表查询。
  而要确定一个查询是否是覆盖索引,我们只需要explainsql语句看Extra的结果是否是Usingindex即可。
  以上面的user表来举例,我们再增加一个name字段,然后做一些查询试试。explainselectfromuserwhereage1;查询的name无法从索引数据获取explainselectid,agefromuserwhereage1;可以直接从索引获取锁的类型有哪些呢
  mysql锁分为共享锁和排他锁,也叫做读锁和写锁。
  读锁是共享的,可以通过lockinsharemode实现,这时候只能读不能写。
  写锁是排他的,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为表锁和行锁两种。
  表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表。
  行锁又可以分为乐观锁和悲观锁,悲观锁可以通过forupdate实现,乐观锁则通过版本号实现。你能说下事务的基本特性和隔离级别吗?
  事务基本特性ACID分别是:
  原子性指的是一个事务中的操作要么全部成功,要么全部失败。
  一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设中间sql执行过程中系统崩溃A也不会损失100块,因为事务没有提交,修改也就不会保存到数据库。
  隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
  持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。
  而隔离性有4个隔离级别,分别是:
  readuncommit读未提交,可能会读到其他事务未提交的数据,也叫做脏读。
  用户本来应该读取到id1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age20,这就是脏读。
  readcommit读已提交,两次读取结果不一致,叫做不可重复读。
  不可重复读解决了脏读的问题,他只会读取已经提交的事务。
  用户开启事务读取id1用户,查询到age10,再次读取发现结果20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。
  repeatableread可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
  serializable串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。那ACID靠什么保证的呢?
  A原子性由undolog日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
  C一致性一般由代码层面来保证
  I隔离性由MVCC来保证
  D持久性由内存redolog来保证,mysql修改数据同时在内存和redolog记录这次操作,事务提交的时候通过redolog刷盘,宕机的时候可以从redolog恢复那你说说什么是幻读,什么是MVCC?
  要说幻读,首先要了解MVCC,MVCC叫做多版本并发控制,实际上就是保存了数据在某个时间节点的快照。
  我们每行数实际上隐藏了两列,创建时间版本号,过期(删除)时间版本号,每开始一个新的事务,版本号都会自动递增。
  还是拿上面的user表举例子,假设我们插入两条数据,他们实际上应该长这样。
  idnamecreateversiondeleteversion1张三12李四2
  这时候假设小明去执行查询,此时currentversion3selectfromuserwhereid3;
  同时,小红在这时候开启事务去修改id1的记录,currentversion4updateusersetname张三三whereid1;
  执行成功后的结果是这样的
  idnamecreateversiondeleteversion1张三12李四21张三三4
  如果这时候还有小黑在删除id2的数据,currentversion5,执行后结果是这样的。
  idnamecreateversiondeleteversion1张三12李四251张三三4
  由于MVCC的原理是查找创建版本小于或等于当前事务版本,删除版本为空或者大于当前事务版本,小明的真实的查询应该是这样selectfromuserwhereid3andcreateversion3and(deleteversion3ordeleteversionisnull);
  所以小明最后查询到的id1的名字还是张三,并且id2的记录也能查询到。这样做是为了保证事务读取的数据是在事务开始前就已经存在的,要么是事务自己插入或者修改的。
  明白MVCC原理,我们来说什么是幻读就简单多了。举一个常见的场景,用户注册时,我们先查询用户名是否存在,不存在就插入,假定用户名是唯一索引。小明开启事务currentversion6查询名字为王五的记录,发现不存在。小红开启事务currentversion7插入一条数据,结果是这样:
  idNamecreateversiondeleteversion1张三12李四23王五7小明执行插入名字王五的记录,发现唯一索引冲突,无法插入,这就是幻读。那你知道什么是间隙锁吗?
  间隙锁是可重复读级别下才会有的锁,结合MVCC和间隙锁可以解决幻读的问题。我们还是以user举例,假设现在user表有几条记录
  idAge110220330
  当我们执行:begin;selectfromuserwhereage20forupdate;begin;insertintouser(age)values(10);成功insertintouser(age)values(11);失败insertintouser(age)values(20);失败insertintouser(age)values(21);失败insertintouser(age)values(30);失败
  只有10可以插入成功,那么因为表的间隙mysql自动帮我们生成了区间(左开右闭)(negativeinfinity,10〕,(10,20〕,(20,30〕,(30,positiveinfinity)
  由于20存在记录,所以(10,20〕,(20,30〕区间都被锁定了无法插入、删除。
  如果查询21呢?就会根据21定位到(20,30)的区间(都是开区间)。
  需要注意的是唯一索引是不会有间隙索引的。你们数据量级多大?分库分表怎么做的?
  首先分库分表分为垂直和水平两个方式,一般来说我们拆分的顺序是先垂直后水平。
  垂直分库
  基于现在微服务拆分来说,都是已经做到了垂直分库了
  垂直分表
  如果表字段比较多,将不常用的、数据较大的等等做拆分
  水平分表
  首先根据业务场景来决定使用什么字段作为分表字段(shardingkey),比如我们现在日订单1000万,我们大部分的场景来源于C端,我们可以用userid作为shardingkey,数据查询支持到最近3个月的订单,超过3个月的做归档处理,那么3个月的数据量就是9亿,可以分1024张表,那么每张表的数据大概就在100万左右。
  比如用户id为100,那我们都经过hash(100),然后对1024取模,就可以落到对应的表上了。那分表后的ID怎么保证唯一性的呢?
  因为我们主键默认都是自增的,那么分表之后的主键在不同表就肯定会有冲突了。有几个办法考虑:设定步长,比如11024张表我们设定1024的基础步长,这样主键落到不同的表就不会冲突了。分布式ID,自己实现一套分布式ID生成算法或者使用开源的比如雪花算法这种分表后不使用主键作为查询依据,而是每张表单独新增一个字段作为唯一主键使用,比如订单表订单号是唯一的,不管最终落在哪张表都基于订单号作为查询依据,更新也一样。分表后非shardingkey的查询怎么处理呢?可以做一个mapping表,比如这时候商家要查询订单列表怎么办呢?不带userid查询的话你总不能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查询到用户列表,再通过userid去查询。打宽表,一般而言,商户端对数据实时性要求并不是很高,比如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方式来做。或者异步的形式也是可以的。ListCallableListUsertaskListLists。newArrayList();for(intshardingIndex0;shardingIndex1024;shardingIndex){taskList。add(()(userMapper。getProcessingAccountList(shardingIndex)));}ListThirdAccountInfolistnull;try{listtaskExecutor。executeTask(taskList);}catch(Exceptione){dosomething}publicclassTaskExecutor{publicTListTexecuteTask(Collectionlt;?extendsCallableTtasks)throwsException{ListTresultLists。newArrayList();ListFutureTfuturesExecutorUtil。invokeAll(tasks);for(FutureTfuture:futures){result。add(future。get());}returnresult;}}说说mysql主从同步怎么做的吧?
  首先先了解mysql主从同步的原理master提交完事务后,写入binlogslave连接到master,获取binlogmaster创建dump线程,推送binglog到slaveslave启动一个IO线程读取同步过来的master的binlog,记录到relaylog中继日志中slave再开启一个sql线程读取relaylog事件并在slave执行,完成同步slave记录自己的binglog
  由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。
  全同步复制
  主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
  半同步复制
  和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。那主从的延迟怎么解决呢?针对特定的业务场景,读写请求都强制走主库读请求走从库,如果没有数据,去主库做二次查询Redis说说Redis基本数据类型有哪些吧字符串:redis没有直接使用C语言传统的字符串表示,而是自己实现的叫做简单动态字符串SDS的抽象类型。C语言的字符串不记录自身的长度信息,而SDS则保存了长度信息,这样将获取字符串长度的时间由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修改字符串长度时所需的内存重分配次数。链表linkedlist:redis链表是一个双向无环链表结构,很多发布订阅、慢查询、监视器功能都是使用到了链表来实现,每个链表的节点由一个listNode结构来表示,每个节点都有指向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向NULL。字典hashtable:用于保存键值对的抽象数据结构。redis使用hash表作为底层实现,每个字典带有两个hash表,供平时使用和rehash时使用,hash表使用链地址法来解决键冲突,被分配到同一个索引位置的多个键值对会形成一个单向链表,在对hash表进行扩容或者缩容的时候,为了服务的可用性,rehash的过程不是一次性完成的,而是渐进式的。跳跃表skiplist:跳跃表是有序集合的底层实现之一,redis中在实现有序集合键和集群节点的内部结构中都是用到了跳跃表。redis跳跃表由zskiplist和zskiplistNode组成,zskiplist用于保存跳跃表信息(表头、表尾节点、长度等),zskiplistNode用于表示表跳跃节点,每个跳跃表的层高都是132的随机数,在同一个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯一的,节点按照分值大小排序,如果分值相同,则按照成员对象的大小排序。整数集合intset:用于保存整数值的集合抽象数据结构,不会出现重复元素,底层实现为数组。压缩列表ziplist:压缩列表是为节约内存而开发的顺序性数据结构,他可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
  基于这些基础的数据结构,redis封装了自己的对象系统,包含字符串对象string、列表对象list、哈希对象hash、集合对象set、有序集合对象zset,每种对象都用到了至少一种基础的数据结构。
  redis通过encoding属性设置对象的编码形式来提升灵活性和效率,基于不同的场景redis会自动做出优化。不同对象的编码如下:字符串对象string:int整数、embstr编码的简单动态字符串、raw简单动态字符串列表对象list:ziplist、linkedlist哈希对象hash:ziplist、hashtable集合对象set:intset、hashtable有序集合对象zset:ziplist、skiplistRedis为什么快呢?
  redis的速度非常的快,单机的redis就可以支撑每秒10几万的并发,相对于mysql来说,性能是mysql的几十倍。速度快的原因主要有几点:完全基于内存操作C语言实现,优化过的数据结构,基于几种基础的数据结构,redis做了大量的优化,性能极高使用单线程,无上下文的切换成本基于非阻塞的IO多路复用机制那为什么Redis6。0之后又改用多线程呢?
  redis使用多线程并非是完全摒弃单线程,redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程。
  这样做的目的是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。
  (见网络篇下单线程Reactor模型)知道什么是热key吗?热key问题怎么解决?
  所谓热key问题就是,突然有几十万的请求去访问redis上的某个特定key,那么这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机引发雪崩。
  针对热key的解决方案:提前把热key打散到不同的服务器,降低压力加入二级缓存,提前加载热key数据到内存中,如果redis宕机,走内存查询什么是缓存击穿、缓存穿透、缓存雪崩?缓存击穿
  缓存击穿的概念就是单个key并发访问过高,过期时导致所有请求直接打到db上,这个和热key的问题比较类似,只是说的点在于过期导致请求全部打到DB上而已。
  解决方案:加锁更新,比如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写入缓存,再返回给用户,这样后面的请求就可以从缓存中拿到数据了。将过期时间组合写在value中,通过异步的方式不断的刷新过期时间,防止此类现象。
  https:tva缓存穿透
  缓存穿透是指查询不存在缓存中的数据,每次请求都会打到DB,就像缓存不存在一样。
  针对这个问题,加一层布隆过滤器。布隆过滤器的原理是在你存入数据的时候,会通过散列函数将它映射为一个位数组中的K个点,同时把他们置为1。
  这样当用户再次来查询A,而A在布隆过滤器值为0,直接返回,就不会产生击穿请求打到DB了。
  显然,使用布隆过滤器之后会有一个问题就是误判,因为它本身是一个数组,可能会有多个值落到同一个位置,那么理论上来说只要我们的数组长度够长,误判的概率就会越低,这种问题就根据实际情况来就好了。
  缓存雪崩
  当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上,这样可能导致整个系统的崩溃,称为雪崩。雪崩和击穿、热key的问题不太一样的是,他是指大规模的缓存都过期失效了。
  针对雪崩几个解决方案:针对不同key设置不同的过期时间,避免同时过期限流,如果redis宕机,可以限流,避免同时刻大量请求打崩DB二级缓存,同热key的方案。Redis的过期策略有哪些?
  redis主要有2种过期删除策略惰性删除
  惰性删除指的是当我们查询key的时候才对key进行检测,如果已经达到过期时间,则删除。显然,他有一个缺点就是如果这些过期的key没有被访问,那么他就一直无法被删除,而且一直占用内存。
  定期删除
  定期删除指的是redis每隔一段时间对数据库做一次检查,删除里面的过期key。由于不可能对所有key去做轮询来删除,所以redis会每次随机取一些key去做检查和删除。那么定期惰性都没有删除过期的key怎么办?
  假设redis每次定期随机查询key的时候没有删掉,这些key也没有做查询的话,就会导致这些key一直保存在redis里面无法被删除,这时候就会走到redis的内存淘汰机制。volatilelru:从已设置过期时间的key中,移出最近最少使用的key进行淘汰volatilettl:从已设置过期时间的key中,移出将要过期的keyvolatilerandom:从已设置过期时间的key中随机选择key淘汰allkeyslru:从key中选择最近最少使用的进行淘汰allkeysrandom:从key中随机选择key进行淘汰noeviction:当内存达到阈值的时候,新写入操作报错持久化方式有哪些?有什么区别?
  redis持久化方案分为RDB和AOF两种。RDB
  RDB持久化可以手动执行也可以根据配置定期执行,它的作用是将某个时间点上的数据库状态保存到RDB文件中,RDB文件是一个压缩的二进制文件,通过它可以还原某个时刻数据库的状态。由于RDB文件是保存在硬盘上的,所以即使redis崩溃或者退出,只要RDB文件存在,就可以用它来恢复还原数据库的状态。
  可以通过SAVE或者BGSAVE来生成RDB文件。
  SAVE命令会阻塞redis进程,直到RDB文件生成完毕,在进程阻塞期间,redis不能处理任何命令请求,这显然是不合适的。
  BGSAVE则是会fork出一个子进程,然后由子进程去负责生成RDB文件,父进程还可以继续处理命令请求,不会阻塞进程。AOF
  AOF和RDB不同,AOF是通过保存redis服务器所执行的写命令来记录数据库状态的。
  AOF通过追加、写入、同步三个步骤来实现持久化机制。当AOF持久化处于激活状态,服务器执行完写命令之后,写命令将会被追加append到aofbuf缓冲区的末尾在服务器每结束一个事件循环之前,将会调用flushAppendOnlyFile函数决定是否要将aofbuf的内容保存到AOF文件中,可以通过配置appendfsync来决定。alwaysaofbuf内容写入并同步到AOF文件everysec将aofbuf中内容写入到AOF文件,如果上次同步AOF文件时间距离现在超过1秒,则再次对AOF文件进行同步no将aofbuf内容写入AOF文件,但是并不对AOF文件进行同步,同步时间由操作系统决定
  如果不设置,默认选项将会是everysec,因为always来说虽然最安全(只会丢失一次事件循环的写命令),但是性能较差,而everysec模式只不过会可能丢失1秒钟的数据,而no模式的效率和everysec相仿,但是会丢失上次同步AOF文件之后的所有写命令数据。怎么实现Redis的高可用?
  要想实现高可用,一台机器肯定是不够的,而redis要保证高可用,有2个可选方案。主从架构
  主从模式是最简单的实现高可用的方案,核心就是主从同步。主从同步的原理如下:slave发送sync命令到mastermaster收到sync之后,执行bgsave,生成RDB全量文件master把slave的写命令记录到缓存bgsave执行完毕之后,发送RDB文件到slave,slave执行master发送缓存中的写命令到slave,slave执行
  这里我写的这个命令是sync,但是在redis2。8版本之后已经使用psync来替代sync了,原因是sync命令非常消耗系统资源,而psync的效率更高。哨兵
  基于主从方案的缺点还是很明显的,假设master宕机,那么就不能写入数据,那么slave也就失去了作用,整个架构就不可用了,除非你手动切换,主要原因就是因为没有自动故障转移机制。而哨兵(sentinel)的功能比单纯的主从架构全面的多了,它具备自动故障转移、集群监控、消息通知等功能。
  哨兵可以同时监视多个主从服务器,并且在被监视的master下线时,自动将某个slave提升为master,然后由新的master继续接收命令。整个过程如下:初始化sentinel,将普通的redis代码替换成sentinel专用代码初始化masters字典和服务器信息,服务器信息主要保存ip:port,并记录实例的地址和ID创建和master的两个连接,命令连接和订阅连接,并且订阅sentinel:hello频道每隔10秒向master发送info命令,获取master和它下面所有slave的当前信息当发现master有新的slave之后,sentinel和新的slave同样建立两个连接,同时每个10秒发送info命令,更新master信息sentinel每隔1秒向所有服务器发送ping命令,如果某台服务器在配置的响应时间内连续返回无效回复,将会被标记为下线状态选举出领头sentinel,领头sentinel需要半数以上的sentinel同意领头sentinel从已下线的的master所有slave中挑选一个,将其转换为master让所有的slave改为从新的master复制数据将原来的master设置为新的master的从服务器,当原来master重新回复连接时,就变成了新master的从服务器
  sentinel会每隔1秒向所有实例(包括主从服务器和其他sentinel)发送ping命令,并且根据回复判断是否已经下线,这种方式叫做主观下线。当判断为主观下线时,就会向其他监视的sentinel询问,如果超过半数的投票认为已经是下线状态,则会标记为客观下线状态,同时触发故障转移。能说说redis集群的原理吗?
  如果说依靠哨兵可以实现redis的高可用,如果还想在支持高并发同时容纳海量的数据,那就需要redis集群。redis集群是redis提供的分布式数据存储方案,集群通过数据分片sharding来进行数据的共享,同时提供复制和故障转移的功能。节点
  一个redis集群由多个节点node组成,而多个node之间通过clustermeet命令来进行连接,节点的握手过程:节点A收到客户端的clustermeet命令A根据收到的IP地址和端口号,向B发送一条meet消息节点B收到meet消息返回pongA知道B收到了meet消息,返回一条ping消息,握手成功最后,节点A将会通过gossip协议把节点B的信息传播给集群中的其他节点,其他节点也将和B进行握手
  槽slot
  redis通过集群分片的形式来保存数据,整个集群数据库被分为16384个slot,集群中的每个节点可以处理016384个slot,当数据库16384个slot都有节点在处理时,集群处于上线状态,反之只要有一个slot没有得到处理都会处理下线状态。通过clusteraddslots命令可以将slot指派给对应节点处理。
  slot是一个位数组,数组的长度是1638482048,而数组的每一位用1表示被节点处理,0表示不处理,如图所示的话表示A节点处理07的slot。
  当客户端向节点发送命令,如果刚好找到slot属于当前节点,那么节点就执行命令,反之,则会返回一个MOVED命令到客户端指引客户端转向正确的节点。(MOVED过程是自动的)
  如果增加或者移出节点,对于slot的重新分配也是非常方便的,redis提供了工具帮助实现slot的迁移,整个过程是完全在线的,不需要停止服务。故障转移
  如果节点A向节点B发送ping消息,节点B没有在规定的时间内响应pong,那么节点A会标记节点B为pfail疑似下线状态,同时把B的状态通过消息的形式发送给其他节点,如果超过半数以上的节点都标记B为pfail状态,B就会被标记为fail下线状态,此时将会发生故障转移,优先从复制数据较多的从节点选择一个成为主节点,并且接管下线节点的slot,整个过程和哨兵非常类似,都是基于Raft协议做选举。了解Redis事务机制吗?
  redis通过MULTI、EXEC、WATCH等命令来实现事务机制,事务执行过程将一系列多个命令按照顺序一次性执行,并且在执行期间,事务不会被中断,也不会去执行客户端的其他请求,直到所有命令执行完毕。事务的执行过程如下:服务端收到客户端请求,事务以MULTI开始如果客户端正处于事务状态,则会把事务放入队列同时返回给客户端QUEUED,反之则直接执行这个命令当收到客户端EXEC命令时,WATCH命令监视整个事务中的key是否有被修改,如果有则返回空回复到客户端表示失败,否则redis会遍历整个事务队列,执行队列中保存的所有命令,最后返回结果给客户端
  WATCH的机制本身是一个CAS的机制,被监视的key会被保存到一个链表中,如果某个key被修改,那么REDISDIRTYCAS标志将会被打开,这时服务器会拒绝执行事务。消息队列MQ你们为什么使用mq?具体的使用场景是什么?
  mq的作用很简单,削峰填谷。以电商交易下单的场景来说,正向交易的过程可能涉及到创建订单、扣减库存、扣减活动预算、扣减积分等等。每个接口的耗时如果是100ms,那么理论上整个下单的链路就需要耗费400ms,这个时间显然是太长了。
  如果这些操作全部同步处理的话,首先调用链路太长影响接口性能,其次分布式事务的问题很难处理,这时候像扣减预算和积分这种对实时一致性要求没有那么高的请求,完全就可以通过mq异步的方式去处理了。同时,考虑到异步带来的不一致的问题,我们可以通过job去重试保证接口调用成功,而且一般公司都会有核对的平台,比如下单成功但是未扣减积分的这种问题可以通过核对作为兜底的处理方案。
  使用mq之后我们的链路变简单了,同时异步发送消息我们的整个系统的抗压能力也上升了。那你们使用什么mq?基于什么做的选型?
  我们主要调研了几个主流的mq,kafka、rabbitmq、rocketmq、activemq,选型我们主要基于以下几个点去考虑:由于我们系统的qps压力比较大,所以性能是首要考虑的要素。开发语言,由于我们的开发语言是java,主要是为了方便二次开发。对于高并发的业务场景是必须的,所以需要支持分布式架构的设计。功能全面,由于不同的业务场景,可能会用到顺序消息、事务消息等。
  基于以上几个考虑,我们最终选择了RocketMQ。
  你上面提到异步发送,那消息可靠性怎么保证?
  消息丢失可能发生在生产者发送消息、MQ本身丢失消息、消费者丢失消息3个方面。生产者丢失
  生产者丢失消息的可能点在于程序发送失败抛异常了没有重试处理,或者发送的过程成功但是过程中网络闪断MQ没收到,消息就丢失了。
  由于同步发送的一般不会出现这样使用方式,所以我们就不考虑同步发送的问题,我们基于异步发送的场景来说。
  异步发送分为两个方式:异步有回调和异步无回调,无回调的方式,生产者发送完后不管结果可能就会造成消息丢失,而通过异步发送回调通知本地消息表的形式我们就可以做出一个解决方案。以下单的场景举例。下单后先保存本地数据和MQ消息表,这时候消息的状态是发送中,如果本地事务失败,那么下单失败,事务回滚。下单成功,直接返回客户端成功,异步发送MQ消息MQ回调通知消息发送结果,对应更新数据库MQ发送状态JOB轮询超过一定时间(时间根据业务配置)还未发送成功的消息去重试在监控平台配置或者JOB程序处理超过一定次数一直发送不成功的消息,告警,人工介入。
  一般而言,对于大部分场景来说异步回调的形式就可以了,只有那种需要完全保证不能丢失消息的场景我们做一套完整的解决方案。MQ丢失
  如果生产者保证消息发送到MQ,而MQ收到消息后还在内存中,这时候宕机了又没来得及同步给从节点,就有可能导致消息丢失。
  比如RocketMQ:
  RocketMQ分为同步刷盘和异步刷盘两种方式,默认的是异步刷盘,就有可能导致消息还未刷到硬盘上就丢失了,可以通过设置为同步刷盘的方式来保证消息可靠性,这样即使MQ挂了,恢复的时候也可以从磁盘中去恢复消息。
  比如Kafka也可以通过配置做到:acksall只有参与复制的所有节点全部收到消息,才返回生产者成功。这样的话除非所有的节点都挂了,消息才会丢失。replication。factorN,设置大于1的数,这会要求每个partion至少有2个副本min。insync。replicasN,设置大于1的数,这会要求leader至少感知到一个follower还保持着连接retriesN,设置一个非常大的值,让生产者发送失败一直重试
  虽然我们可以通过配置的方式来达到MQ本身高可用的目的,但是都对性能有损耗,怎样配置需要根据业务做出权衡。消费者丢失
  消费者丢失消息的场景:消费者刚收到消息,此时服务器宕机,MQ认为消费者已经消费,不会重复发送消息,消息丢失。
  RocketMQ默认是需要消费者回复ack确认,而kafka需要手动开启配置关闭自动offset。
  消费方不返回ack确认,重发的机制根据MQ类型的不同发送时间间隔、次数都不尽相同,如果重试超过次数之后会进入死信队列,需要手工来处理了。(Kafka没有这些)
  你说到消费者消费失败的问题,那么如果一直消费失败导致消息积压怎么处理?
  因为考虑到时消费者消费一直出错的问题,那么我们可以从以下几个角度来考虑:消费者出错,肯定是程序或者其他问题导致的,如果容易修复,先把问题修复,让consumer恢复正常消费如果时间来不及处理很麻烦,做转发处理,写一个临时的consumer消费方案,先把消息消费,然后再转发到一个新的topic和MQ资源,这个新的topic的机器资源单独申请,要能承载住当前积压的消息处理完积压数据后,修复consumer,去消费新的MQ和现有的MQ数据,新MQ消费完成后恢复原状
  那如果消息积压达到磁盘上限,消息被删除了怎么办?
  这他妈都删除了我有啥办法啊冷静,再想想有了。
  最初,我们发送的消息记录是落库保存了的,而转发发送的数据也保存了,那么我们就可以通过这部分数据来找到丢失的那部分数据,再单独跑个脚本重发就可以了。如果转发的程序没有落库,那就和消费方的记录去做对比,只是过程会更艰难一点。说了这么多,那你说说RocketMQ实现原理吧?
  RocketMQ由NameServer注册中心集群、Producer生产者集群、Consumer消费者集群和若干Broker(RocketMQ进程)组成,它的架构原理是这样的:Broker在启动的时候去向所有的NameServer注册,并保持长连接,每30s发送一次心跳Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择一台服务器来发送消息Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费
  为什么RocketMQ不使用Zookeeper作为注册中心呢?
  我认为有以下几个点是不使用zookeeper的原因:根据CAP理论,同时最多只能满足两个点,而zookeeper满足的是CP,也就是说zookeeper并不能保证服务的可用性,zookeeper在进行选举的时候,整个选举的时间太长,期间整个集群都处于不可用的状态,而这对于一个注册中心来说肯定是不能接受的,作为服务发现来说就应该是为可用性而设计。基于性能的考虑,NameServer本身的实现非常轻量,而且可以通过增加机器的方式水平扩展,增加集群的抗压能力,而zookeeper的写是不可扩展的,而zookeeper要解决这个问题只能通过划分领域,划分多个zookeeper集群来解决,首先操作起来太复杂,其次这样还是又违反了CAP中的A的设计,导致服务之间是不连通的。持久化的机制来带的问题,ZooKeeper的ZAB协议对每一个写请求,会在每个ZooKeeper节点上保持写一个事务日志,同时再加上定期的将内存数据镜像(Snapshot)到磁盘来保证数据的一致性和持久性,而对于一个简单的服务发现的场景来说,这其实没有太大的必要,这个实现方案太重了。而且本身存储的数据应该是高度定制化的。消息发送应该弱依赖注册中心,而RocketMQ的设计理念也正是基于此,生产者在第一次发送消息的时候从NameServer获取到Broker地址后缓存到本地,如果NameServer整个集群不可用,短时间内对于生产者和消费者并不会产生太大影响。那Broker是怎么保存数据的呢?
  RocketMQ主要的存储文件包括commitlog文件、consumequeue文件、indexfile文件。
  Broker在收到消息之后,会把消息保存到commitlog的文件当中,而同时在分布式的存储当中,每个broker都会保存一部分topic的数据,同时,每个topic对应的messagequeue下都会生成consumequeue文件用于保存commitlog的物理位置偏移量offset,indexfile中会保存key和offset的对应关系。
  CommitLog文件保存于{RocketHome}storecommitlog目录中,从图中我们可以明显看出来文件名的偏移量,每个文件默认1G,写满后自动生成一个新的文件。
  由于同一个topic的消息并不是连续的存储在commitlog中,消费者如果直接从commitlog获取消息效率非常低,所以通过consumequeue保存commitlog中消息的偏移量的物理地址,这样消费者在消费的时候先从consumequeue中根据偏移量定位到具体的commitlog物理文件,然后根据一定的规则(offset和文件大小取模)在commitlog中快速定位。
  Master和Slave之间是怎么同步数据的呢?
  而消息在master和slave之间的同步是根据raft协议来进行的:在broker收到消息后,会被标记为uncommitted状态然后会把消息发送给所有的slaveslave在收到消息之后返回ack响应给mastermaster在收到超过半数的ack之后,把消息标记为committed发送committed消息给所有slave,slave也修改状态为committed你知道RocketMQ为什么速度快吗?
  是因为使用了顺序存储、PageCache和异步刷盘。我们在写入commitlog的时候是顺序写入的,这样比随机写入的性能就会提高很多写入commitlog的时候并不是直接写入磁盘,而是先写入操作系统的PageCache最后由操作系统异步将缓存中的数据刷到磁盘什么是事务、半事务消息?怎么实现的?
  事务消息就是MQ提供的类似XA的分布式事务能力,通过事务消息可以达到分布式事务的最终一致性。
  半事务消息就是MQ收到了生产者的消息,但是没有收到二次确认,不能投递的消息。
  实现原理如下:生产者先发送一条半事务消息到MQMQ收到消息后返回ack确认生产者开始执行本地事务如果事务执行成功发送commit到MQ,失败发送rollback如果MQ长时间未收到生产者的二次确认commit或者rollback,MQ对生产者发起消息回查生产者查询事务执行最终状态根据查询事务状态再次提交二次确认
  最终,如果MQ收到二次确认commit,就可以把消息投递给消费者,反之如果是rollback,消息会保存下来并且在3天后被删除。
  Java基础说说进程和线程的区别?
  进程是程序的一次执行,是系统进行资源分配和调度的独立单位,他的作用是是程序能够并发执行提高资源利用率和吞吐率。
  由于进程是资源分配和调度的基本单位,因为进程的创建、销毁、切换产生大量的时间和空间的开销,进程的数量不能太多,而线程是比进程更小的能独立运行的基本单位,他是进程的一个实体,可以减少程序并发执行时的时间和空间开销,使得操作系统具有更好的并发性。
  线程基本不拥有系统资源,只有一些运行时必不可少的资源,比如程序计数器、寄存器和栈,进程则占有堆、栈。知道synchronized原理吗?
  synchronized是java提供的原子性内置锁,这种内置的并且使用者看不到的锁也被称为监视器锁,使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,他依赖操作系统底层互斥锁实现。他的作用主要就是实现原子性操作和解决共享变量的内存可见性问题。
  执行monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器1。此时其他竞争锁的线程则会进入等待队列中。
  执行monitorexit指令时则会把计数器1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。
  synchronized是排它锁,当一个线程获得锁之后,其他线程必须等待该线程释放锁后才能获得锁,而且由于Java中的线程和操作系统原生线程是一一对应的,线程被阻塞或者唤醒时时会从用户态切换到内核态,这种转换非常消耗性能。
  从内存语义来说,加锁的过程会清除工作内存中的共享变量,再从主内存读取,而释放锁的过程则是将工作内存中的共享变量写回主内存。
  实际上大部分时候我认为说到monitorenter就行了,但是为了更清楚的描述,还是再具体一点。
  如果再深入到源码来说,synchronized实际上有两个队列waitSet和entryList。当多个线程进入同步代码块时,首先进入entryList有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器1如果线程调用wait方法,将释放锁,当前线程置为null,计数器1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁如果线程执行完毕,同样释放锁,计数器1,当前线程置为null
  那锁的优化机制了解吗?
  从JDK1。6版本之后,synchronized本身也在不断优化锁的机制,有些情况下他并不会是一个很重量级的锁了。优化机制包括自适应锁、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁。
  锁的状态从低到高依次为无锁偏向锁轻量级锁重量级锁,升级的过程就是从低到高,降级在一定条件也是有可能发生的。
  自旋锁:由于大部分时候,锁被占用的时间很短,共享变量的锁定时间也很短,所有没有必要挂起线程,用户态和内核态的来回上下文切换严重影响性能。自旋的概念就是让线程执行一个忙循环,可以理解为就是啥也不干,防止从用户态转入内核态,自旋锁可以通过设置XX:UseSpining来开启,自旋的默认次数是10次,可以使用XX:PreBlockSpin设置。
  自适应锁:自适应锁就是自适应的自旋锁,自旋的时间不是固定时间,而是由前一次在同一个锁上的自旋时间和锁的持有者状态来决定。
  锁消除:锁消除指的是JVM检测到一些同步的代码块,完全不存在数据竞争的场景,也就是不需要加锁,就会进行锁消除。
  锁粗化:锁粗化指的是有很多操作都是对同一个对象进行加锁,就会把锁的同步范围扩展到整个操作序列之外。
  偏向锁:当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,之后这个线程再次进入同步块时都不需要CAS来加锁和解锁了,偏向锁会永远偏向第一个获得锁的线程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,反之,当有其他线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁。可以用过设置XX:UseBiasedLocking开启偏向锁。
  轻量级锁:JVM的对象的对象头中包含有一些锁的标志位,代码进入同步块的时候,JVM将会使用CAS方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,当前线程就尝试自旋来获得锁。
  整个锁升级的过程非常复杂,我尽力去除一些无用的环节,简单来描述整个升级的机制。
  简单点说,偏向锁就是通过对象头的偏向线程ID来对比,甚至都不需要CAS了,而轻量级锁主要就是通过CAS修改对象头锁记录和自旋来实现,重量级锁则是除了拥有锁的线程其他全部阻塞。
  那对象头具体都包含哪些内容?
  在我们常用的Hotspot虚拟机中,对象在内存中布局实际包含3个部分:对象头实例数据对齐填充
  而对象头包含两部分内容,MarkWord中的内容会随着锁标志位而发生变化,所以只说存储结构就好了。对象自身运行时所需的数据,也被称为MarkWord,也就是用于轻量级锁和偏向锁的关键点。具体的内容包含对象的hashcode、分代年龄、轻量级锁指针、重量级锁指针、GC标记、偏向锁线程ID、偏向锁时间戳。存储类型指针,也就是指向类的元数据的指针,通过这个指针才能确定对象是属于哪个类的实例。
  如果是数组的话,则还包含了数组的长度
  对于加锁,那再说下ReentrantLock原理?他和synchronized有什么区别?
  相比于synchronized,ReentrantLock需要显式的获取锁和释放锁,相对现在基本都是用JDK7和JDK8的版本,ReentrantLock的效率和synchronized区别基本可以持平了。他们的主要区别有以下几点:等待可中断,当持有锁的线程长时间不释放锁的时候,等待中的线程可以选择放弃等待,转而处理其他的任务。公平锁:synchronized和ReentrantLock默认都是非公平锁,但是ReentrantLock可以通过构造函数传参改变。只不过使用公平锁的话会导致性能急剧下降。绑定多个条件:ReentrantLock可以同时绑定多个Condition条件对象。
  ReentrantLock基于AQS(AbstractQueuedSynchronizer抽象队列同步器)实现。别说了,我知道问题了,AQS原理我来讲。
  AQS内部维护一个state状态位,尝试加锁的时候通过CAS(CompareAndSwap)修改值,如果成功设置为1,并且把当前线程ID赋值,则代表加锁成功,一旦获取到锁,其他的线程将会被阻塞进入阻塞队列自旋,获得锁的线程释放锁的时候将会唤醒阻塞队列中的线程,释放锁的时候则会把state重新置为0,同时当前线程ID置为空。
  CAS的原理呢?
  CAS叫做CompareAndSwap,比较并交换,主要是通过处理器的指令来保证操作的原子性,它包含三个操作数:变量内存地址,V表示旧的预期值,A表示准备设置的新值,B表示
  当执行CAS指令时,只有当V等于A时,才会用B去更新V的值,否则就不会执行更新操作。那么CAS有什么缺点吗?
  CAS的缺点主要有3点:
  ABA问题:ABA的问题指的是在CAS更新的过程中,当读取到的值是A,然后准备赋值的时候仍然是A,但是实际上有可能A的值被改成了B,然后又被改回了A,这个CAS更新的漏洞就叫做ABA。只是ABA的问题大部分场景下都不影响并发的最终效果。
  Java中有AtomicStampedReference来解决这个问题,他加入了预期标志和更新后标志两个字段,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部相等的话才会更新。
  循环时间长开销大:自旋CAS的方式如果长时间不成功,会给CPU带来很大的开销。
  只能保证一个共享变量的原子操作:只对一个共享变量操作可以保证原子性,但是多个则不行,多个可以通过AtomicReference来处理或者使用锁synchronized实现。好,说说HashMap原理吧?
  HashMap主要由数组和链表组成,他不是线程安全的。核心的点就是put插入数据的过程,get查询数据以及扩容的方式。JDK1。7和1。8的主要区别在于头插和尾插方式的修改,头插容易导致HashMap链表死循环,并且1。8之后加入红黑树对性能有提升。
  put插入数据流程
  往map插入元素的时候首先通过对keyhash然后与数组长度1进行与运算((n1)hash),都是2的次幂所以等同于取模,但是位运算的效率更高。找到数组中的位置之后,如果数组中没有元素直接存入,反之则判断key是否相同,key相同就覆盖,否则就会插入到链表的尾部,如果链表的长度超过8,则会转换成红黑树,最后判断数组长度是否超过默认的长度负载因子也就是12,超过则进行扩容。
  get查询数据
  查询数据相对来说就比较简单了,首先计算出hash值,然后去数组查询,是红黑树就去红黑树查,链表就遍历链表查询就可以了。
  resize扩容过程
  扩容的过程就是对key重新计算hash,然后把数据拷贝到新的数组。那多线程环境怎么使用Map呢?ConcurrentHashmap了解过吗?
  多线程环境可以使用Collections。synchronizedMap同步加锁的方式,还可以使用HashTable,但是同步的方式显然性能不达标,而ConurrentHashMap更适合高并发场景使用。
  ConcurrentHashmap在JDK1。7和1。8的版本改动比较大,1。7使用SegmentHashEntry分段锁的方式实现,1。8则抛弃了Segment,改为使用CASsynchronizedNode实现,同样也加入了红黑树,避免链表过长导致性能的问题。
  1。7分段锁
  从结构上说,1。7版本的ConcurrentHashMap采用分段锁机制,里面包含一个Segment数组,Segment继承与ReentrantLock,Segment则包含HashEntry的数组,HashEntry本身就是一个链表的结构,具有保存key、value的能力能指向下一个节点的指针。
  实际上就是相当于每个Segment都是一个HashMap,默认的Segment长度是16,也就是支持16个线程的并发写,Segment之间相互不会受到影响。
  put流程
  其实发现整个流程和HashMap非常类似,只不过是先定位到具体的Segment,然后通过ReentrantLock去操作而已,后面的流程我就简化了,因为和HashMap基本上是一样的。计算hash,定位到segment,segment如果是空就先初始化使用ReentrantLock加锁,如果获取锁失败则尝试自旋,自旋超过次数就阻塞获取,保证一定获取锁成功遍历HashEntry,就是和HashMap一样,数组中key和hash一样就直接替换,不存在就再插入链表,链表同样
  get流程
  get也很简单,key通过hash定位到segment,再遍历链表定位到具体的元素上,需要注意的是value是volatile的,所以get是不需要加锁的。
  1。8CASsynchronized
  1。8抛弃分段锁,转为用CASsynchronized来实现,同样HashEntry改为Node,也加入了红黑树的实现。主要还是看put的流程。
  put流程首先计算hash,遍历node数组,如果node是空的话,就通过CAS自旋的方式初始化如果当前数组位置是空则直接通过CAS自旋写入数据如果hashMOVED,说明需要扩容,执行扩容如果都不满足,就使用synchronized写入数据,写入数据同样判断链表、红黑树,链表写入和HashMap的方式一样,keyhash一样就覆盖,反之就尾插法,链表长度超过8就转换成红黑树
  get查询
  get很简单,通过key计算hash,如果keyhash相同就返回,如果是红黑树按照红黑树获取,都不是就遍历链表获取。volatile原理知道吗?
  相比synchronized的加锁方式来解决共享变量的内存可见性问题,volatile就是更轻量的选择,他没有上下文切换的额外开销成本。使用volatile声明的变量,可以确保值被更新的时候对其他线程立刻可见。volatile使用内存屏障来保证不会发生指令重排,解决了内存可见性的问题。
  我们知道,线程都是从主内存中读取共享变量到工作内存来操作,完成之后再把结果写会主内存,但是这样就会带来可见性问题。举个例子,假设现在我们是两级缓存的双核CPU架构,包含L1、L2两级缓存。线程A首先获取变量X的值,由于最初两级缓存都是空,所以直接从主内存中读取X,假设X初始值为0,线程A读取之后把X值都修改为1,同时写回主内存。这时候缓存和主内存的情况如下图。
  线程B也同样读取变量X的值,由于L2缓存已经有缓存X1,所以直接从L2缓存读取,之后线程B把X修改为2,同时写回L2和主内存。这时候的X值入下图所示。那么线程A如果再想获取变量X的值,因为L1缓存已经有x1了,所以这时候变量内存不可见问题就产生了,B修改为2的值对A来说没有感知。image20201111171451466
  那么,如果X变量用volatile修饰的话,当线程A再次读取变量X的话,CPU就会根据缓存一致性协议强制线程A重新从主内存加载最新的值到自己的工作内存,而不是直接用缓存中的值。
  再来说内存屏障的问题,volatile修饰之后会加入不同的内存屏障来保证可见性的问题能正确执行。这里写的屏障基于书中提供的内容,但是实际上由于CPU架构不同,重排序的策略不同,提供的内存屏障也不一样,比如x86平台上,只有StoreLoad一种内存屏障。StoreStore屏障,保证上面的普通写不和volatile写发生重排序StoreLoad屏障,保证volatile写与后面可能的volatile读写不发生重排序LoadLoad屏障,禁止volatile读与后面的普通读重排序LoadStore屏障,禁止volatile读和后面的普通写重排序
  那么说说你对JMM内存模型的理解?为什么需要JMM?
  本身随着CPU和内存的发展速度差异的问题,导致CPU的速度远快于内存,所以现在的CPU加入了高速缓存,高速缓存一般可以分为L1、L2、L3三级缓存。基于上面的例子我们知道了这导致了缓存一致性的问题,所以加入了缓存一致性协议,同时导致了内存可见性的问题,而编译器和CPU的重排序导致了原子性和有序性的问题,JMM内存模型正是对多线程操作下的一系列规范约束,因为不可能让陈雇员的代码去兼容所有的CPU,通过JMM我们才屏蔽了不同硬件和操作系统内存的访问差异,这样保证了Java程序在不同的平台下达到一致的内存访问效果,同时也是保证在高效并发的时候程序能够正确执行。
  原子性:Java内存模型通过read、load、assign、use、store、write来保证原子性操作,此外还有lock和unlock,直接对应着synchronized关键字的monitorenter和monitorexit字节码指令。
  可见性:可见性的问题在上面的回答已经说过,Java保证可见性可以认为通过volatile、synchronized、final来实现。
  有序性:由于处理器和编译器的重排序导致的有序性问题,Java通过volatile、synchronized来保证。
  happenbefore规则
  虽然指令重排提高了并发的性能,但是Java虚拟机会对指令重排做出一些规则限制,并不能让所有的指令都随意的改变执行位置,主要有以下几点:单线程每个操作,happenbefore于该线程中任意后续操作volatile写happenbefore与后续对这个变量的读synchronized解锁happenbefore后续对这个锁的加锁final变量的写happenbefore于final域对象的读,happenbefore后续对final变量的读传递性规则,A先于B,B先于C,那么A一定先于C发生说了半天,到底工作内存和主内存是什么?
  主内存可以认为就是物理内存,Java内存模型中实际就是虚拟机内存的一部分。而工作内存就是CPU缓存,他有可能是寄存器也有可能是L1L2L3缓存,都是有可能的。说说ThreadLocal原理?
  ThreadLocal可以理解为线程本地变量,他会在每个线程都创建一个副本,那么在线程之间访问内部副本变量就行了,做到了线程之间互相隔离,相比于synchronized的做法是用空间来换时间。
  ThreadLocal有一个静态内部类ThreadLocalMap,ThreadLocalMap又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备了保存keyvalue键值对的能力。
  弱引用的目的是为了防止内存泄露,如果是强引用那么ThreadLocal对象除非线程结束否则始终无法被回收,弱引用则会在下一次GC的时候被回收。
  但是这样还是会存在内存泄露的问题,假如key和ThreadLocal对象被回收之后,entry中就存在key为null,但是value有值的entry对象,但是永远没办法被访问到,同样除非线程结束运行。
  但是只要ThreadLocal使用恰当,在使用完之后调用remove方法删除Entry对象,实际上是不会出现这个问题的。
  那引用类型有哪些?有什么区别?
  引用类型主要分为强软弱虚四种:强引用指的就是代码中普遍存在的赋值方式,比如AanewA()这种。强引用关联的对象,永远不会被GC回收。软引用可以用SoftReference来描述,指的是那些有用但是不是必须要的对象。系统在发生内存溢出前会对这类引用的对象进行回收。弱引用可以用WeakReference来描述,他的强度比软引用更低一点,弱引用的对象下一次GC的时候一定会被回收,而不管内存是否足够。虚引用也被称作幻影引用,是最弱的引用关系,可以用PhantomReference来描述,他必须和ReferenceQueue一起使用,同样的当发生GC的时候,虚引用也会被回收。可以用虚引用来管理堆外内存。线程池原理知道吗?
  首先线程池有几个核心的参数概念:最大线程数maximumPoolSize核心线程数corePoolSize活跃时间keepAliveTime阻塞队列workQueue拒绝策略RejectedExecutionHandler
  当提交一个新任务到线程池时,具体的执行流程如下:当我们提交任务,线程池会根据corePoolSize大小创建若干任务数量线程执行任务当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队当阻塞队列也满了之后,那么将会继续创建(maximumPoolSizecorePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSizecorePoolSize额外创建的线程等待keepAliveTime之后被自动销毁如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理
  拒绝策略有哪些?
  主要有4种拒绝策略:AbortPolicy:直接丢弃任务,抛出异常,这是默认策略CallerRunsPolicy:只用调用者所在的线程来处理任务DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务DiscardPolicy:直接丢弃任务,也不抛出异常

亲子鉴定逼疯无数男人!最毁三观的冷门行业,为何突然爆火?文金错刀频道祥燎经旭没想到,200亿市场的亲子鉴定,居然已经成为了拯救情感节目的流量密码。二三线城市频道的调解类节目,只要加入了亲子鉴定,就能占据收视高地,谁看谁上……鬼鬼吴映洁晒纯素颜照片,皮肤状态好真实尖下巴抢镜2月17日凌晨,鬼鬼吴映洁分享了一张素颜照片,并且配文称:初六!没有美肌滤镜的me!刚擦完保养品的me!祝大家开工大吉的me!照片里的鬼鬼身穿粉色睡衣,棕色长发披在肩头,……谁红和谁玩的杨幂,终于与刘诗诗和好了作者李婷婷责编顾圆原创深度稿件,阅读约需要6分钟最近这些年,很多娱乐圈女明星之间的友谊事实上被调侃为塑料姐妹花,一些当红女明星之间的矛盾受到了很多的关注,这其中就包……县城体制女成三四线城市大龄剩女重灾区,背后的原因让人一言难尽如果仔细留意下,就会发现,在如今的国内县城,有一个很奇怪的现象,那就是很多县城,明明是光棍数量很多,女性数量极少,可是即使如此,在很多情况下,很多女性依旧要忙不迭地迭地为自己相……选门面的标准是看素颜吗?看看这几位官方门面,的确是很好看的叶舒华她本身就带有一些清冷感,而且皮肤还是冷白皮,不需要任何修饰都很有氛围感,就像是一块毫无瑕疵的美玉。裴珠泫素颜也是很好看的啊,感觉不化妆很嫩的样子,化完妆……17岁嘎丽娅,劝450名日军投降,却被粗暴拖入要塞,只剩一块中国人讲究人如其表,看一个人的长相,大致可以初步判断一个人究竟是好是坏,从西方雕塑以及圣母画像中,我们可以看到许多美的形象。今天我们要讲的这位女英雄,她的长相酷似西方画中……恐怖片拍成这样,谁敢上床?日落黄昏时,一女子突然从家里跑了出来。身着一件居家睡衣。站在大街上,左右查看。发生了什么事,她这么慌张?是遭遇了家暴吗?还是家里进了强盗?开……向往的生活未播先惹争议黄磊强塞两人进组,何炅偏爱彭昱畅提到国内慢综艺,相信很多人都会想到《向往的生活》这个节目。甚至可以说是国内慢综艺的开山节目。在一个快节奏的社会之中,做一个慢节奏的综艺,这其中的稀缺性,足以让《向往……彭富春生活世界就是欲望技术和大道的游戏欲技道游戏规则的制定在根本上只是欲望、技术和大道三者之间的约定,是它们三者之间形成的契约。这一游戏规则无非是要让欲望、技术和大道三者生生不息,并由此不断生成世界。(此处已添加圈……只有经历过一无所有,才能够珍惜眼前的一切作为一个打工十几年的打工人来说,我虽然没有从工作中赚取多少工资,但由于自己平时比较节俭,也会存一些钱以备不时之需,父母其实随着早年间干多了重体力活而且父亲更是得了职业病,常常呼……最帅的展昭,何家劲当了老板,为何还不老?1993年,何家劲因为出演电视剧《包青天》中的展昭而红遍大江南北。此后,展昭这个称号就成了何家劲影视生涯中最为重要的代表角色。现年已经61岁的何家劲,仍然是单身一人……张子枫00后第一小花?CP百搭体质的她,变成资源咖不好吗5月13日中午,张子枫官宣加盟沈严导演的网剧《天才基本法》,这部剧也是子枫妹妹首部做女主的电视剧。这剧的配置和制作团队在业内都很不错,加上几位演员靠谱,算是一部值得期待的……
被身家亿万亲爹ampampquot吸血ampampquot多作者:睿姐娱乐圈的童星可以说是数不胜数,像我们所熟悉的杨紫、张一山、赫邵文以及释小龙等等。他们都是年少成名,光环加身,小小的年纪备受追捧,既替父母争了面子,也早早有……曝网红艾比患玉玉症,19岁记忆力严重减退,称人生已没有希望引子:抑郁症在当今社会,已经变得越来越普遍,大部分公众人物,都曾经历过网络暴力。网络暴力的危害,已经从明星蔓延到网红,甚至连普通素人,都曾经是全民公敌。网红在大众心目中,……赵露思新剧开播!剧情老套口碑很差,男主还被吐槽撑不起霸总人设《长歌行》刚播完,赵露思主演的电视剧《一不小心捡到爱》也上线了,这部剧是和《长歌行》同期拍摄的,是一部狗血的玛丽苏偶像剧,拍摄之初赵露思的粉丝就很不满意,怕败坏她的口碑,该剧昨……揭秘33岁阚清子恋情曝光内幕,神秘男友到底是什么来头?又一位女星被曝疑似有新恋情了,她就是33岁的阚清子。阚清子疑似有新恋情的事情登顶热搜。媒体拍到了阚清子和一位神秘男子牵手逛街的照片,两人的互动颇为亲密,牵手逛街还一起去吃烤肉。……一档综艺引发的音乐剧思考最近,看了一档名为《天赐的声音》音乐综艺,作为音乐剧专业出身的娄艺潇,在节目中也感叹中国音乐剧市场不太理想的现实,道出了毕业等于失业的现实。不过《爱情公寓》倒也造就了她的……德云斗笑社首期看点多,郭德纲敲打众徒,德云断头台十年一剑8月27日晚间,盼望已久的德云社团综《德云斗笑社》第一期,终于在正版视频平台上线。《德云斗笑社》旨在以全新真人秀形式,演绎德云社内部通过比赛角逐新德云一哥的故事。这……刘宇宁片场吸烟,曾自曝已经戒烟,如今好人设崩塌现在娱乐圈优秀的艺人都不会止步于在一个领域里发光发彩,而是不断的去探索自己潜在力量,让自己可以在多个领域里纵横。唱而优则演,演而优则导已经成为了娱乐圈的不二法则,很多优秀的艺人……双马尾届颜值巅峰,二次元千万不要看,网友隔夜饭yue出来了如果要说最能代表二次元的发现,那势必当属双马尾。穿搭JK可爱妹妹再加以双马尾的加持堪称三次元YYDS,可由于越来越多的小姐姐热衷于这种穿搭,导致了审美疲劳的产生,越来越多的网友……李子柒张钧甯郭富城林更新和吴谨言丁真姚安娜刘昊然等一般来说,和经纪人相处的不是很好的艺人,发展还是很受限制。有很多明星想找李子柒合作,但是人家不乐意。张钧甯为了解决自己的绯闻问题,确实想从根本上解决自己的感情问题。……2019春晚节目单新鲜出炉,让我们先睹为快2019年春晚节目单新鲜出炉,让我们先睹为快,看看你最喜欢的节目出现在哪个时段,大家一起来讨论分享吧!春晚节目主持人中的常青树朱军,周涛,董卿都退出了舞台,必须新的主持人……大博弈将拍,秦昊万茜再聚首,金牌编剧周梅森操刀,收视稳了《大博弈》将拍,秦昊万茜再聚首,金牌编剧周梅森操刀,收视稳了作为业内头部公司,这两年正午阳光再次展现了顶尖的业务水平。今年仅播出的《大江大河2》《山海情》都双双成为爆款剧……蜘蛛侠英雄远征10亿票房背后,看漫威电影宇宙是如何布局的第一娱乐讯从2017年到2019年,索尼拍摄了3部真人超英电影和1部超英动画电影,初步构建了一个以蜘蛛侠为核心,囊括众多知名漫威角色的电影宇宙索尼漫威电影宇宙(SMCU),这是……
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网