这场面试,我佛了

1、HashMap为什么不是线程安全的,并发情况下会有什么问题?

首先,为什么会存在线程安全的问题,这个得从JMM(Java Memory Model)说起。大家都知道,计算机在执行一个指令的时候,会先将数据和指令从硬盘或网络等地方加载到内存,然后CPU在从内存加载指令和数据到CPU缓存,然后到寄存器,最终计算的结果,也会写回到内存,最终保存到磁盘或者其他地方(所有的读写都会用到读缓冲区和写缓冲区)。因为现在基本都是多核CPU,内存是所有CPU共享的,但每个CPU内部的缓存和寄存器是私有的,这就造成了同一个数据,由不同的CPU同时访问一个内存地址,就会存在一个数据有多个副本(内存一份,每个访问到的CPU内部一份),所以造成了线程安全,即数据可能出现不一致的情况。其实面试和工作中经常遇到的缓存和数据库的一致性问题,也是因为同一份数据有了多个副本导致的。

但JMM为了屏蔽计算机硬件结构体系的复杂度,为我们出了一个规范,具体来说就是大名鼎鼎的JSR133,可以参考http://www.jcp.org/en/jsr/detail?id=133。我的理解大意是说,JMM规定了内存由线程私有的工作内存(类似的可以对照CPU缓存和寄存器)和所有线程共享的主内存(类似计算机的内存)组成。

每个线程在访问变量的时候,都要从主内存中去读取,然后在将变量的值copy一份到自己的工作内存中。那么在多线程并发访问同一个共享变量的时候,就会导致同一个共享变量,会存在于一个主内存+N个工作内存的多个副本。如果此时一个线程修改了变量的值,新值首先会写入该线程的工作内存,然后在写入主内存,而其他线程是不知道主内存的值以及被修改了,所以其他线程在做后续的操作的时候,都是用的原来的值,就造成了数据不一致,即线程安全的问题。

 

线程安全的手段有:synchrionzed ,Lock, volatile , final 以及拒绝共享的ThreadLocal 和局部变量等方式。当然,JDK也为我们提供了很多的并发容器来保证线程安全,如 ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentLinkedList, ArrayBlockingQueue, LinkedBlockingQueue等以及Collections#synchronizedList, Collections#synchronizedMap等方法返回的同步容器,具体的实现,大家也可以自己去看看。

 

并发情况下可能产生的问题:

HashMap在多线程put后可能导致get无限循环;

多线程put的时候可能导致元素丢失等。

 

2、ConcurrentHashMap 怎么实现线程安全的?

ConcurrentHashMap 在1.7中 实现线程安全是通过锁住Segment对象的。而在1.8 中则是针对首个节点(table[hash(key)]取得的链接或红黑树的首个节点)进行加锁操作。

下面文章有具体讲解。

 

3、如何判断一个对象是否要被回收?

常用的方法有两种:

  1. 引用计数法;

  2. 对象可达性分析。

由于引用计数法存在互相引用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析算法。

 

1、引用计数法

引用计数法的逻辑非常简单,但是存在问题,java并不采用这种方式进行对象存活判断。

引用计数法的逻辑是:在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。

这种方法来标记对象的状态会存在很多问题:

 
jdk从1.2开始增加了多种引用方式:软引用、弱引用、虚引用,且在不同引用情况下程序应进行不同的操作。如果我们只采用一个引用计数法来计数无法准确的区分这么多种引用的情况。

 

2、可达性分析算法

在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。

 

4、Left Join 是怎么执行的?

什么是笛卡尔积呢?
  就是两张表连接的时候,是通过笛卡尔积的方式连接。
  笛卡尔(Descartes)乘积又叫直积。假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1), (b,2)}。可以扩展到多个集合的情况。类似的例子有,如果A表示某学校学生的集合,B表示该学校所有课程的集合,则A与B的笛卡尔积表示所有可能的选课情况。
  所以两个表连接后(使用join、逗号连接)就是笛卡尔积。

无论是join还是left join,都是先把表以笛卡尔积的方式连接,

然后通过on来筛选数据,join只显示符合条件的数据,left join不仅会显示所有满足条件的数据,而且还会把主表没有匹配上的也显示出来。

left join后面必须加上on。

 

执行顺序:

1、from

2、有多表关联的情况,先产生笛卡尔积

3、on,对产生的笛卡尔积进行筛选

4、join,对on筛选的结果生成一张临时表

5、如果是out join(left),还需要把没匹配上的行数添加和join的数据合并,生成一张临时表

6、where,对临时表进行过滤

 

T-SQL在查询各个阶级分别干了什么

(1)FROM 阶段

FROM阶段标识出查询的来源表,并处理表运算符。在涉及到联接运算的查询中(各种join),主要有以下几个步骤:
a.求笛卡尔积。不论是什么类型的联接运算,首先都是执行交叉连接(cross join),求笛卡儿积,生成虚拟表VT1-J1。
b.ON筛选器。这个阶段对上个步骤生成的VT1-J1进行筛选,根据ON子句中出现的谓词进行筛选,让谓词取值为true的行通过了考验,插入到VT1-J2。
c.添加外部行。如果指定了outer join,还需要将VT1-J2中没有找到匹配的行,作为外部行添加到VT1-J2中,生成VT1-J3。
经过以上步骤,FROM阶段就完成了。概括地讲,FROM阶段就是进行预处理的,根据提供的运算符对语句中提到的各个表进行处理(除了join,还有apply,pivot,unpivot)
(2)WHERE阶段

WHERE阶段是根据<where_predicate>中条件对VT1中的行进行筛选,让条件成立的行才会插入到VT2中。
(3)GROUP BY阶段

GROUP阶段按照指定的列名列表,将VT2中的行进行分组,生成VT3。最后每个分组只有一行。
(4)HAVING阶段

该阶段根据HAVING子句中出现的谓词对VT3的分组进行筛选,并将符合条件的组插入到VT4中。
(5)SELECT阶段

这个阶段是投影的过程,处理SELECT子句提到的元素,产生VT5。这个步骤一般按下列顺序进行
a.计算SELECT列表中的表达式,生成VT5-1。
b.若有DISTINCT,则删除VT5-1中的重复行,生成VT5-2
c.若有TOP,则根据ORDER BY子句定义的逻辑顺序,从VT5-2中选择签名指定数量或者百分比的行,生成VT5-3
(6)ORDER BY阶段

根据ORDER BY子句中指定的列明列表,对VT5-3中的行,进行排序,生成游标VC6.
如果On和where只能选其一的话:
先进行on的过滤, 而后才进行join, 这样就避免了两个大表产生全部数据的笛卡尔积的庞大数据.
这些步骤执行时, 每个步骤都会产生一个虚拟表,该虚拟表被用作下一个步骤的输入。这些虚拟表对调用者(客户端应用程序或者外部查询)不可用。只是最后一步生成的表才会返回 给调用者。
如果没有在查询中指定某一子句,将跳过相应的步骤。
on 和where 那个更高效呢?

如果是inner join, 放on和放where产生的结果一样, 但没说哪个效率速度更高? 如果有outer join (left or right), 就有区别了, 因为on生效在先, 已经提前过滤了一部分数据, 而where生效在后。
综合一下, 感觉还是放在on里更有效率, 因为它先于where执行。
先笛卡尔积, 然后再on过滤, 如果join是inner的, 就继续往下走, 如果join 是left join, 就把on过滤掉的左主表中的数据再添加回来; 然后再执行where里的过滤;
on中不是最终过滤, 因为后面left join还可能添加回来, 而where才是最终过滤。
只有当使用外连接(left, right)时, on 和 where 才有这个区别, 如果用inner join, 在哪里制定都一样, 因为on 之后就是where, 中间没有其它步骤。

 

5、怎么判断一个查询走没走索引,like 走索引吗?

使用解释函数explain,只需添加在sql语句之前即可。

图片

我们只需要注意一个最重要的type 的信息很明显的提现是否用到索引:

type结果

type结果值从好到坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般来说,得保证查询至少达到range级别,最好能达到ref,否则就可能会出现性能问题。

possible_keys:sql所用到的索引

key:显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL

rows: 显示MySQL认为它执行查询时必须检查的行数。

 

like是否走索引:

后通配  走索引;

前通配 走全表。

 

6、Hash 可以做索引吗?为什么 InnoDB 不使用 Hash 索引? 

图片

  1).Hash 索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询。
    由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样。
  2).Hash 索引无法被用来避免数据的排序操作。
    由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;
  3).Hash 索引不能利用部分索引键查询。
    对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。
  4).Hash 索引在任何时候都不能避免表扫描。
    前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
  5).Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。
    对于选择性比较低的索引键,如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下
 
     简单地说,哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
从上面的图来看,B+树索引和哈希索引的明显区别是:
    1).如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;当然了,这个前提是,键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据;
    2).从示意图中也能看到,如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索;
    3).同理,哈希索引也没办法利用索引完成排序,以及like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询);
    4).哈希索引也不支持多列联合索引的最左匹配规则;
    5).B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞问题。

    在MySQL中,只有HEAP/MEMORY引擎表才能显式支持哈希索引(NDB也支持,但这个不常用),InnoDB引擎的自适应哈希索引(adaptive hash index)不在此列,因为这不是创建索引时可指定的。
    还需要注意到:HEAP/MEMORY引擎表在mysql实例重启后,数据会丢失。
    通常,B+树索引结构适用于绝大多数场景,像下面这种场景用哈希索引才更有优势:
    在HEAP表中,如果存储的数据重复度很低(也就是说基数很大),对该列数据以等值查询为主,没有范围查询、没有排序的时候,特别适合采用哈希索引
例如这种SQL:
SELECT … FROM t WHERE C1 = ?; — 仅等值查询
在大多数场景下,都会有范围查询、排序、分组等查询特征,用B+树索引就可以了。

 

其他面试内容:

如何利用索引提升查询速率(任何优化一个慢查询);

MyBatis 执行一个 Select 查询的流程?

线程volatile 关键字原理;

Synchronized 和 ReentraintLock 区别;

线程通信方式有哪些;

双向链表如何判断有交叉?如何找到交叉点?

另附资源下载:关注 “Java面试百分百
1,后台回复:面试资料,可获取一份最新的Java面试资料。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值