java面试题实战三

 

华为优招面试经验。html

 

1.笔试(这部分按照华为之前的风格不会为难人的,认真作AC一道题就能够进面试了,我编程能力通常吧,作了一道半而已,-_-||!前端

2.测评(性格测试,不要太偏激就好了)

3.面试分为业务面和综合面两面,业务面就是技术面,综合面就问的比较杂了,我选的岗位是IT软件开发,下面分享一下我依稀记得面试题目:你们接好了!java

4.录用签约node

5.入职linux

1nginx

2程序员

3面试

4redis

5算法

我是817参加的面试,当天我还迟到了2分钟,让我去的东门,好不容易才找到,其实去南门进去就是了,可能他们考虑人分流的缘由吧。

业务面:先介绍下面试官,面试个人面试官是个大概37岁左右的大叔戴个眼镜,头发黑白参差的那种,就像吴秀波的那种发色,可是发型彻底不同,头发是那种寸头,人家是根根有劲,给人感受沧桑干练,就感受很牛逼的样子....

1.首先自我介绍,介绍完了他就问你挑一两个你作的工做比较多的项目说说看,我简历上写了三个项目,一个资源目录的项目(这个比较熟悉),一个给学校作研究的试点项目(有关中小河流的预报,这个就是和我研究的模式挖掘很相关),还有一个是我78月正在广州实习的珠三角水资源配置工程项目。

2. 我就主要说了资源目录的那个项目,从数据库的分表提及,由于咱们那个数据库的数据有1000多万的数据量,为了方便查询我说咱们采用了分表处理,给他介绍了咱们采用的分表方式,数据库这个点他却是没有问我问题;以后说到数据抽取,创建模型都没有问我问题(感到庆幸),说到编目的时候我提到了多线程的使用,这时候他问题就来了,首先问了我使用什么什么完成“消费者和生产者模式“,这个我有准备就回答了,而后我说咱们认为编目操做是IO比较频繁的操做,采用了生产者与消费者线程 12这样的比例来进行多线程操做,他再次提问:为何IO密集的操做须要较多的线程数?,我当时就懵逼了。。。,这个没答上来,我说我不大清楚。。。,第三个问题:那你把你用到的多线程操做类给我写出来吧,我当时也是心悬了一下,还好,我想了下给他把我使用的线程池和使用的锁,阻塞队列勉强写了下,他看了下,我以为应该差很少,确定有遗漏的,主要写出来了

3. 后面在这个项目上没问什么了,让我在介绍下一个项目,我就介绍起了我研究的成果,模式挖掘的那块东西,才说了两句,他就让停下来了,“我不关心你的学术研究,你说说下一个项目吧“,好直接。。。,不关心非技术的项目,而后我又介绍了珠三角的项目,其实这个项目是实习才接触的,一个多月吧,也不是很熟悉,大致介绍完了,他说大家那个定时任务怎么作的?还好还好,我能答出来,我说咱们使用的是Quartz来作的定时任务,他点了头,没有继续问了(幸好没有继续问底层,否则就GG。。。)

4. 介绍完了,我看他们每一个人是有三页的问题纸,(这可能就是若是咱们不主动说话,他会按照问题纸来提问吧。。可怕),其实我已经说了不少废话了,耗了很长的时间,可是面试官可能必需要问他纸上准备好的问题,而后他就提问“java内存的空间分布“,这个是比较简单的问题了,还让我把内存模型画了出来,这也难不倒我,也比较简单。以后jvm的垃圾回收机制,这个我也比较熟,让比较了CMSG1的区别,还提了“若是是大数据量的状况下使用什么回收器比较好?“G1是针对服务器的,而且是分块整理的固然选择G1。最后问了一个问题“若是jvm一直在作FullGC怎么办?“这个我开始反应就回答了设置jvm的堆区最大值,他说要是仍是FullGC呢?我就没答上来,他说咱们须要检测为何出现这种状况了,我立马接上话说使用jconsole之类的工具进行检测,算是求生欲很强了。。。

5. 继续提问“缓存池咱们不少地方都要用到,如何实现缓存池?“说实话刚听到这个问题我又懵了,我想到了Java io的缓存池,好像是队列实现的,我就答了能够使用队列实现,面试官说“你以为这样的数据结构好嘛,咱们是否是能够换个容器?“这个时候我才想起来,咱们能够使用map来实现。

6. 下面就是一大堆map的问题,hashmap的底层结构,为何是2*n次方,如何扩容,并发怎么解决,concurrentHashMap如何实现高效读写(分区+锁技术),最后竟然问了句:为何负载因子是0.75,这个就没有答上来,答案你们本身百度。

7. 前先后后也有半小时了,面试官问题也差很少了,他就说能够了,我这里你经过了,竟然经过了,其实好几题没答上来,大致还行,基础真的好重要呀,华为问的基础仍是比较简单的,但愿你们好好复习一下,不难的,没有那么恐怖,也可能我遇到的面试官不错的缘由吧,我不会还会引导我回答,很棒的一位大佬,颇有范儿。

 

综合面:综合面就比较简单了,问的技术问题很少,个人综合面面试官是位40多岁的大叔,头发没前面那个技术大佬那么多白发,可是也有少许白头发,发型就彻底不同了,软软的顺顺的感受,是那种给人看起来很和善主管感受,综合面的自我介绍我没发挥好有点紧张,可是以后我彻底找到了节奏,期间他我把全部的项目都简要的说完了,没业务面那么细节说,问了一个抽象工厂模式,问了对华为的映像,如何评价外界对华为的不一样见解,如何看待华为的文化,我还特意扯了我去了华为总部参观,把华为不折不扣的夸了一番,(其实参观过华为总部你就会以为华为是真的牛逼,他的那个企业解决方案作的真的是好)这里面试官还特地和我聊起我怎么过来面试的,由于我面试以前在广州实习,是坐飞机赶去南京面试的,买的晚上7.25的机票由于台风问题,延误到凌晨2点多我才到宿舍。。。,真的累人,以后他还问了薪资待遇问题,我说南京这边价格通常在14k吧,原来没准备说,面试官特地说没事,仍是说了下。最后我以为今年华为迁到东莞,可能要招的人多一点,面试官一直在询问我是否接受派遣,因此我就很虚,感受就算运气好能进也是会被安排到东莞去了,算了要是能进也是能够接受的,毕竟华为的平台仍是大的。最后让我问他问题,我就问了他华为的培训相关事宜和晋升机制,面试官很耐心的给我解答了,最有问了个你们都关心的问题,何时有通知?回答一周以内,至此面试所有结束

 

如今的简历状态是录用排序中,这个状态我也查了下资料,过了面试以后按分数排序,靠前进资源池吧,估计一个礼拜若是发信息也是是否进资源池的信息,进了资源池其实尚未肯定能拿offer,还要看人家能不能把你捞起来,大厂就是有资本的,今年为华为的大年,小伙伴的机会仍是大的,在此我也但愿咱们能拿到本身想要的结果,加油!

 

Java面试底层原理

面试发现常常有些重复的面试问题,本身也应该学会记录下来,最好本身能作成笔记,在下一次面的时候说得有条不紊,深刻具体,面试官想必也很开心。如下是我我的总结,请参考:

HashSet底层原理:(问了大概率跟HashMap一块儿面)

HashMap底层原理:(很是大概率问到)

Hashtable底层原理:(问的少,问了大概率问你跟HashMap的区别)

synchronized底层如何实现?锁优化,怎么优化?

ReentrantLock 底层实现;

ConcurrentHashMap 的工做原理,底层原理(谈到多线程高并发大概率会问它)

JVM调优(JVM层层渐进问时大概率问)

JVM内存管理,JVM的常见的垃圾收集器,GC调优,Minor GC Full GC 触发条件(像是必考题)

java内存模型

线程池的工做原理(谈到多线程高并发大概率会问它)

ThreadLocal的底层原理(有时问)

voliate底层原理

NIO底层原理

IOC底层实现原理(Spring IOC AOP会问的两个原理,面试官常常会问看过源码吗?因此你有所准备吧)

AOP底层实现原理

MyisAMinnodb的有关索引的疑问(容易混淆,能够问的会深刻)

HashSet底层原理:(面试过)
http://zhangshixi.iteye.com/blog/673143

http://www.javashuo.com/article/p-memupmkd-bc.html

HashSet实现Set接口,由哈希表(其实是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类容许使用null元素。

2.    HashSet的实现:

   对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存全部元素,所以HashSet 的实现比较简单,相关HashSet的操做,基本上都是直接调用底层HashMap的相关方法来完成, (实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。)

HashSet的源代码

对于HashSet中保存的对象,请注意正确重写其equalshashCode方法,以保证放入的对象的惟一性。

插入
当有新值加入时,底层的HashMap会判断Key值是否存在(HashMap细节请移步深刻理解HashMap),若是不存在,则插入新值,同时这个插入的细节会依照HashMap插入细节;若是存在就不插入

HashMap底层原理:

1.    HashMap概述:

   HashMap是基于哈希表的Map接口的非同步实现。此实现提供全部可选的映射操做,并容许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

2.    HashMap的数据结构:

HashMap其实是一个数组+链表+红黑树的数据结构

3.    HashMap的存取实现:

1.8以前的)

当咱们往HashMapput元素的时候,先根据keyhashCode从新计算hash值,根据hash值获得这个元素在数组中的位置(即下标),若是数组该位置上已经存放有其余元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最早加入的放在链尾。若是数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

1.8

put():

根据key计算获得key.hash = (h = k.hashCode()) ^ (h >>> 16)

根据key.hash计算获得桶数组的索引index = key.hash & (table.length - 1),这样就找到该key的存放位置了:

若是该位置没有数据,用该数据新生成一个节点保存新数据,返回null

若是该位置有数据是一个红黑树,那么执行相应的插入 / 更新操做

若是该位置有数据是一个链表,分两种状况一是该链表没有这个节点,另外一个是该链表上有这个节点,注意这里判断的依据是key.hash是否同样:若是该链表没有这个节点,那么采用尾插法新增节点保存新数据,返回null若是该链表已经有这个节点了,那么找到該节点并更新新数据,返回老数据。注意: HashMapput会返回key的上一次保存的数据。

get():

计算需获取数据的hash值(计算过程跟put同样),计算存放在数组table中的位置(计算过程跟put同样),而后依次在数组,红黑树,链表中查找(经过equals()判断),最后再判断获取的数据是否为空,若为空返回null不然返回该数据

 

树化与还原

哈希表的最小树形化容量

当哈希表中的容量大于这个值时(64),表中的桶才能进行树形化

不然桶内元素太多时会扩容,而不是树形化

为了不进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD

一个桶的树化阈值

当桶中元素个数超过这个值时(8),须要使用红黑树节点替换链表节点

这个值必须为 8,要否则频繁转换效率也不高

一个树的链表还原阈值

当扩容时,桶中元素个数小于这个值(6),就会把树形的桶元素还原(切分)为链表结构

这个值应该比上面那个小,至少为 6,避免频繁转换

条件1. 若是当前桶数组为null或者桶数组的长度 < MIN_TREEIFY_CAPACITY64),则进行扩容处理(见代码片断2resize());

条件2. 当不知足条件1的时候则将桶中链表内的元素转换成红黑树!!!稍后再详细讨论红黑树。

 

扩容机制的实现

扩容(resize)就是从新计算容量。当向HashMap对象里不停的添加元素,而HashMap对象内部的桶数组没法装载更多的元素时,HashMap对象就须要扩大桶数组的长度,以便能装入更多的元素。

capacity 就是数组的长度/大小,loadFactor 是这个数组填满程度的最大比比例。 

size表示当前HashMap中已经储存的Node<key,value>的数量,包括桶数组和链表 / 红黑树中的的Node<key,value>

threshold表示扩容的临界值,若是size大于这个值,则必需调用resize()方法进行扩容。 

jdk1.7及之前,threshold = capacity * loadFactor,其中 capacity 为桶数组的长度。这里须要说明一点,默认负载因子0.75是是对空间和时间(纵向横向)效率的一个平衡选择,建议你们不要修改。 jdk1.8threshold值进行了改进,经过一系列位移操做算法最后获得一个power of two size的值

何时扩容

当向容器添加元素的时候,会判断当前容器的元素个数,若是大于等于阈值---即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦。

扩容必须知足两个条件:

1存放新值的时候   当前已有元素的个数  (size) 必须大于等于阈值

2存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)

//若是计算的哈希位置有值(及hash冲突),且key值同样,则覆盖原值value,并返回原值value

      if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

        V oldValue = e.value;

        e.value = value;

        e.recordAccess(this);

        return oldValue;

      }

resize()方法:该函数有2种使用状况1.初始化哈希表 2.当前数组容量太小,需扩容

过程:

插入键值对时发现容量不足,调用resize()方法方法,

1.首先进行异常状况的判断,如是否须要初始化,二是若当前容量》最大值则不扩容,

2.而后根据新容量(是就容量的2倍)新建数组,将旧数组上的数据(键值对)转移到新的数组中,这里包括:(遍历旧数组的每一个元素,从新计算每一个数据在数组中的存放位置(原位置或者原位置+旧容量),将旧数组上的每一个数据逐个转移到新数组中,这里采用的是尾插法。)

3.新数组table引用到HashMaptable属性上

4.最后从新设置扩容阙值,此时哈希表table=扩容后(2倍)&转移了旧数据的新table

synchronized底层如何实现?锁优化,怎么优化?

synchronized Java 内建的同步机制,因此也有人称其为 Intrinsic Locking,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其余试图获取的线程只能等待或者阻塞在那里。

原理:

synchronized能够保证方法或者代码块在运行时,同一时刻只有一个方法能够进入到临界区,同时它还能够保证共享变量的内存可见性

 

底层实现:

同步代码块是使用monitorentermonitorexit指令实现的,,当且一个monitor被持有以后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor全部权,即尝试获取对象的锁; 

 

同步方法(在这看不出来须要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。  synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtualareturn指令,在VM字节码层面并无任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的ClassJVM的内部对象表示 Klass 作为锁对象。

 

Java对象头和monitor是实现synchronized的基础!

synchronized存放的位置:

synchronized用的锁是存在Java对象头里的。

 

其中, Java对象头包括: 

Mark Word(标记字段):用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。它是实现轻量级锁和偏向锁的关键

Klass Pointer(类型指针):是对象指向它的类元数据的指针,虚拟机经过这个指针来肯定这个对象是哪一个类的实例

monitor:  能够把它理解为一个同步工具,它一般被描述为一个对象。 是线程私有的数据结构

锁优化,怎么优化?

jdk1.6对锁的实现引入了大量的优化。锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁能够升级不可降级,这种策略是为了提升得到锁和释放锁的效率。重量级锁降级发生于STW阶段,降级对象为仅仅能被VMThread访问而没有其余JavaThread访问的对象。( HotSpot JVM/JRockit JVM是支持锁降级的)

偏斜锁:

当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS 操做(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,因此并不涉及真正的互斥锁。

自旋锁:

自旋锁 for(;;)结合cas确保线程获取取锁

就是让该线程等待一段时间,不会被当即挂起,看持有锁的线程是否会很快释放锁。怎么等待呢?执行一段无心义的循环便可(自旋)。

轻量级锁:

引入偏向锁主要目的是:为了在无多线程竞争的状况下尽可能减小没必要要的轻量级锁执行路径。当关闭偏向锁功能或者多个线程竞争偏向锁致使偏向锁升级为轻量级锁,则会尝试获取轻量级锁

重量级锁:

重量级锁经过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操做系统的Mutex Lock实现,操做系统实现线程之间的切换须要从用户态到内核态的切换,切换成本很是高。

ReentrantLock 底层实现

https://blog.csdn.net/u011202334/article/details/73188404

AQS原理:

AQSCondition各自维护了不一样的队列,在使用lockcondition的时候,其实就是两个队列的互相移动。若是咱们想自定义一个同步器,能够实现AQS。它提供了获取共享锁和互斥锁的方式,都是基于对state操做而言的。

 

概念+实现:

ReentrantLock实现了Lock接口,是AQS( 一个用来构建锁和同步工具的框架, AQS没有锁之类的概念)的一种。加锁和解锁都须要显式写出,注意必定要在适当时候unlockReentranLock这个是可重入的。其实要弄明白它为啥可重入的呢,咋实现的呢。其实它内部自定义了同步器Sync,这个又实现了AQS,同时又实现了AOS,然后者就提供了一种互斥锁持有的方式。其实就是每次获取锁的时候,看下当前维护的那个线程和当前请求的线程是否同样,同样就可重入了。

 

synhronized相比:

synchronized相比,ReentrantLock用起来会复杂一些。在基本的加锁和解锁上,二者是同样的,因此无特殊状况下,推荐使用synchronizedReentrantLock的优点在于它更灵活、更强大,增长了轮训、超时、中断等高级功能。

可重入锁。可重入锁是指同一个线程能够屡次获取同一把锁。ReentrantLocksynchronized都是可重入锁。

可中断锁。可中断锁是指线程尝试获取锁的过程当中,是否能够响应中断。synchronized是不可中断锁,而ReentrantLockz,dz提供了中断功能。

公平锁与非公平锁。公平锁是指多个线程同时尝试获取同一把锁时,获取锁的顺序按照线程达到的顺序,而非公平锁则容许线程插队synchronized是非公平锁,而ReentrantLock的默认实现是非公平锁,可是也能够设置为公平锁。

 

lock()unlock()是怎么实现的呢?

lock()unlock的源码能够看到,它们只是分别调用了sync对象的lock()release(1)方法。而  SyncReentrantLock的内部类, 其扩展了AbstractQueuedSynchronizer

lock()

final void lock() {

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

acquire(1);

}

首先用一个CAS操做,判断state是不是0(表示当前锁未被占用),若是是0则把它置为1,而且设置当前线程为该锁的独占线程,表示获取锁成功。当多个线程同时尝试占用同一个锁时,CAS操做只能保证一个线程操做成功,剩下的只能乖乖的去排队啦。(非公平即体如今这里)。

设置state失败,走到了else里面。咱们往下看acquire

第一步。尝试去获取锁。若是尝试获取锁成功,方法直接返回。

2. 第二步,入队。(自旋+CAS组合来实现非阻塞的原子操做)

3. 第三步,挂起。让已经入队的线程尝试获取锁,若失败则会被挂起

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    selfInterrupt();

}

unlock()
流程大体为先尝试释放锁,若释放成功,那么查看头结点的状态是否为SIGNAL,若是是则唤醒头结点的下个节点关联的线程,

若是释放失败那么返回false表示解锁失败。这里咱们也发现了,每次都只唤起头结点的下一个节点关联的线程。

public void unlock() {

sync.release(1);

}

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

return true;

}

return false;

}

ConcurrentHashMap 的工做原理

概念:

ConcurrentHashMap的目标是实现支持高并发、高吞吐量的线程安全的HashMap

1.8以前:

数据结构:

ConcurrentHashMap是由Segment数组结构和多个HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构,一个Segment里包含一个HashEntry数组,每一个HashEntry是一个链表结构的元素,每一个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。

 

putget的时候,都是现根据key.hashCode()算出放到哪一个Segment中: ConcurrentHashMap中默认是把segments初始化为长度为16的数组

http://www.javashuo.com/article/p-sfmdxgau-bp.html

1.8后:

变化:

ConcurrentHashMapJDK8JDK7版本的并发实现相比,最大的区别在于JDK8的锁粒度更细,理想状况下talbe数组元素的大小就是其支持并发的最大个数

实现:

改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素做为锁,从而实现了对每一行数据进行加锁,进一步减小并发冲突的几率。

数据结构:

改进二:将原先table数组+单向链表的数据结构,变动为table数组+单向链表+红黑树的结构。对于hash表来讲,最核心的能力在于将key hash以后能均匀的分布在数组中。

概念:

JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用SynchronizedCAS来操做,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,可是已经简化了属性,只是为了兼容旧版本。

 

树化和还原:

HashMap同样

 

一些成员:

NodeConcurrentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据。,就是一个链表,可是只容许对数据进行查找,不容许进行修改

经过TreeNode做为存储结构代替Node来转换成黑红树。

TreeBin
TreeBin
就是封装TreeNode的容器,它提供转换黑红树的一些条件和锁的控制

 // 读写锁状态

    static final int WRITER = 1; // 获取写锁的状态

    static final int WAITER = 2; // 等待写锁的状态

    static final int READER = 4; // 增长数据时读锁的状态

 

构造器

public ConcurrentHashMap() {

} 初始化实际上是一个空实现,初始化操做并非在构造函数实现的,而是在put操做中实现。还提供了其余的构造函数,有指定容量大小或者指定负载因子,跟HashMap同样。

 

 

存取实现:

put():对当前的table进行无条件自循环直到put成功

若是没有初始化就先调用initTable()方法来进行初始化过程

若是没有hash冲突就直接CAS插入

若是还在进行扩容操做就先进行扩容

若是存在hash冲突,就加锁来保证线程安全,这里有两种状况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,

最后一个若是该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环

若是添加成功就调用addCount()方法统计size,而且检查是否须要扩容。

get()

计算hash值,定位到该table索引位置,若是是首节点符合就返回

若是遇到扩容的时候,会调用标志正在扩容节点ForwardingNodefind方法,查找该节点,匹配就返回

以上都不符合的话,就往下遍历节点,匹配就返回,不然最后就返回null

归纳版:

(1)对于get读操做,若是当前节点有数据,还没迁移完成,此时不影响读,可以正常进行。 

若是当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时get线程会帮助扩容。 

(2)对于put/remove写操做,若是当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时写线程会帮助扩容,若是扩容没有完成,当前链表的头节点会被锁住,因此写线程会被阻塞,直到扩容完成。 

 

扩容机制:https://www.e-learn.cn/content/java/1154828

引入了一个ForwardingNode类,在一个线程发起扩容的时候,就会改变sizeCtl这个值,

sizeCtl :默认为0,用来控制table的初始化和扩容操做,具体应用在后续会体现出来。  

-1 表明table正在初始化  

-N 表示有N-1个线程正在进行扩容操做 

 

扩容时候会判断这个值,

若是超过阈值就要扩容,首先根据运算获得须要遍历的次数i,而后利用tabAt方法得到i位置的元素f,初始化一个forwardNode实例fwd,若是f == null,则在table中的i位置放入fwd

不然采用头插法的方式把当前旧table数组的指定任务范围的数据给迁移到新的数组中,

而后 

给旧table原位置赋值fwd。直到遍历过全部的节点之后就完成了复制工做,把table指向nextTable,并更新sizeCtl为新数组大小的0.75,扩容完成。在此期间若是其余线程的有读写操做都会判断head节点是否为forwardNode节点,若是是就帮助扩容。 

 

 

Hashtable底层原理:

 

概念:

HashTable类继承自Dictionary类,实现了Map接口。 大部分的操做都是经过synchronized锁保护的,是线程安全的, keyvalue都不能够为null 每次put方法不容许null值,若是发现是null,则直接抛出异常。

官方文档也说了:若是在非线程安全的状况下使用,建议使用HashMap替换,若是在线程安全的状况下使用,建议使用ConcurrentHashMap替换。

 

数据结构:

数组+链表。

 

存取实现:

put()

限制了value不能为null

因为直接使用key.hashcode(),而没有向hashmap同样先判断key是否为null,因此keynull时,调用key.hashcode()会出错,因此hashtablekey也不能为null

Hashtable是在链表的头部添加元素的。

 int index = (hash & 0x7FFFFFFF) %tab.length;获取index的方式与HashMap不一样

 

扩容机制:

Hashtable默认capacity11,默认负载因子是0.75.。当前表中的Entry数量,若是超过了阈值,就会扩容,即调用rehash方法,从新计算每一个键值对的hashCode;

 判断新的容量是否超过了上限,没超过就新建一个新数组,大小为原数组的2+1,将旧数的键值对从新hash添加到新数组中。

 

 

 

JVM调优

查看堆空间大小分配(年轻代、年老代、持久代分配)

垃圾回收监控(长时间监控回收状况)

线程信息监控:系统线程数量

线程状态监控:各个线程都处在什么样的状态下

线程详细信息:查看线程内部运行状况,死锁检查

CPU热点:检查系统哪些方法占用了大量CPU时间

内存热点:检查哪些对象在系统中数量最大

jvm问题排查和调优:

jps主要用来输出JVM中运行的进程状态信息。

jstat命令能够用于持续观察虚拟机内存中各个分区的使用率以及GC的统计数据

jmap能够用来查看堆内存的使用详情。

jstack能够用来查看Java进程内的线程堆栈信息。 jstack是个很是好用的工具,结合应用日志能够迅速定位到问题线程。

 

Java性能分析工具
jdk
会自带JMC(JavaMissionControl)工具。能够分析本地应用以及链接远程ip使用。提供了实时分析线程、内存,CPUGC等信息的可视化界面。

 

JVM内存调优

JVM内存的系统级的调优主要的目的是减小GC的频率和Full GC的次数。过多的GCFull GC是会占用不少的系统资源(主要是CPU),影响系统的吞吐量。

使用JDK提供的内存查看工具,好比JConsoleJava VisualVM

致使Full GC通常因为如下几种状况:

旧生代空间不足

调优时尽可能让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要建立过大的对象及数组避免直接在旧生代建立对象

新生代设置太小

一是新生代GC次数很是频繁,增大系统消耗;二是致使大对象直接进入旧生代,占据了旧生代剩余空间,诱发Full GC 

2). 新生代设置过大

一是新生代设置过大会致使旧生代太小(堆总量必定),从而诱发Full GC;二是新生代GC耗时大幅度增长

3). Survivor设置太小

致使对象从eden直接到达旧生代

4). Survivor设置过大

致使eden太小,增长了GC频率

通常说来新生代占整个堆1/3比较合适

 

GC策略的设置方式

1). 吞吐量优先可由-XX:GCTimeRatio=n来设置

2). 暂停时间优先可由-XX:MaxGCPauseRatio=n来设置

JVM内存管理:

1.先讲内存5大模块以及他们各类的做用。

2.将垃圾收集器,垃圾收集算法

3.适当讲讲GC优化,JVM优化

 

JVM的常见的垃圾收集器:

(注:此回答源于杨晓峰的Java核心技术36讲之一)

 

GC调优:

GC日志分析

调优命令

调优工具

 

调优命令

Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo

jpsJVM Process Status Tool,显示指定系统内全部的HotSpot虚拟机进程。

jstatJVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它能够显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

jmapJVM Memory Map命令用于生成heap dump文件

jhatJVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dumpjhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,能够在浏览器中查看

jstack,用于生成java虚拟机当前时刻的线程快照。

jinfoJVM Configuration info 这个命令做用是实时查看和调整虚拟机运行参数。

 

调优工具

经常使用调优工具分为两类,jdk自带监控工具:jconsolejvisualvm,第三方有:MAT(Memory Analyzer Tool)GChisto

jconsoleJava Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控

 

GC触发的条件有两种。(1)程序调用System.gc时能够触发;(2)系统自身来决定GC触发的时机。

要彻底回收一个对象,至少须要通过两次标记的过程。

第一次标记:对于一个没有其余引用的对象,筛选该对象是否有必要执行finalize()方法,若是没有执行必要,则意味可直接回收。(筛选依据:是否复写或执行过finalize()方法;由于finalize方法只能被执行一次)。

第二次标记:若是被筛选断定位有必要执行,则会放入FQueue队列,并自动建立一个低优先级的finalize线程来执行释放操做。若是在一个对象释放前被其余对象引用,则该对象会被移除FQueue队列。

Minor GC Full GC 触发条件

Minor GC触发条件:当Eden区满时,触发Minor GC

Full GC触发条件:

1)调用System.gc时,系统建议执行Full GC,可是没必要然执行

2)老年代空间不足

3)方法区空间不足

4)经过Minor GC后进入老年代的平均大小大于老年代的可用内存

5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

java内存模型

JVM 内存模型不一样。

Java内存模型即Java Memory Model,简称JMMJMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工做方式。JVM是整个计算机虚拟模型,因此JMM是隶属于JVM的。

Java内存模型定义了多线程之间共享变量的可见性以及如何在须要的时候对共享变量进行同步。

Java线程之间的通讯采用的是过共享内存模型,这里提到的共享内存模型指的就是Java内存模型(简称JMM)JMM决定一个线程对共享变量的写入什么时候对另外一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每一个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。

线程池的工做原理

1.先讲下做用

减小资源的开销    能够减小每次建立销毁线程的开销

 提升响应速度    因为线程已经建立成功

提升线程的可管理性   

2.讲实现

线程池主要有两部分组成,多个工做线程和一个阻塞队列。

其中工做线程是一组已经处在运行中的线程,它们不断地向阻塞队列中领取任务执行。而阻塞队列用于存储工做线程来不及处理的任务。

3.细分讲下线程的组成

建立一个线程池须要要的一些核心参数。

corePoolSize:基本线程数量它表示你但愿线程池达到的一个值。线程池会尽可能把实际线程数量保持在这个值上下。 

maximumPoolSize:最大线程数量这是线程数量的上界。若是实际线程数量达到这个值:阻塞队列未满:任务存入阻塞队列等待执行阻塞队列已满:调用饱和策略

keepAliveTime:空闲线程的存活时间当实际线程数量超过corePoolSize时,若线程空闲的时间超过该值,就会被中止。 PS:当任务不少,且任务执行时间很短的状况下,能够将该值调大,提升线程利用率。 

timeUnitkeepAliveTime的单位

runnableTaskQueue:任务队列 

这是一个存听任务的阻塞队列,能够有以下几种选择:

ArrayBlockingQueue 它是一个由数组实现的阻塞队列,FIFO 

LinkedBlockingQueue 它是一个由链表实现的阻塞队列,FIFO吞吐量一般要高于ArrayBlockingQueuefixedThreadPool使用的阻塞队列就是它。它是一个无界队列。 

SynchronousQueue 它是一个没有存储空间的阻塞队列,任务提交给它以后必需要交给一条工做线程处理;若是当前没有空闲的工做线程,则当即建立一条新的工做线程。 cachedThreadPool用的阻塞队列就是它。它是一个无界队列。 PriorityBlockingQueue 它是一个优先权阻塞队列。

handler:饱和策略当实际线程数达到maximumPoolSize,而且阻塞队列已满时,就会调用饱和策略。

AbortPolicy 默认。直接抛异常。 CallerRunsPolicy 只用调用者所在的线程执行任务。 DiscardOldestPolicy 丢弃任务队列中最久的任务。 DiscardPolicy 丢弃当前任务。

4.运行机制

当有请求到来时: 

1.若当前实际线程数量少于 corePoolSize,即便有空闲线程,也会建立一个新的工做线程;

2 若当前实际线程数量处于corePoolSizemaximumPoolSize之间,而且阻塞队列没满,则任务将被放入阻塞队列中等待执行; 

3.若当前实际线程数量小于 maximumPoolSize,但阻塞队列已满,则直接建立新线程处理任务; 

4.若当前实际线程数量已经达到maximumPoolSize,而且阻塞队列已满,则使用饱和策略。

ThreadLocal的底层原理

归纳:

该类提供了线程局部 (thread-local) 变量。这些变量不一样于它们的普通对应物,由于访问某个变量(经过其 get set 方法)的每一个线程都有本身的局部变量

使用:

set(obj):向当前线程中存储数据 get():获取当前线程中的数据 remove():删除当前线程中的数据

 

实现原理:

ThreadLocal并不维护ThreadLocalMapThreadLocalMapThread的)并非一个存储数据的容器,它只是至关于一个工具包,提供了操做该容器的方法,如getsetremove等。而ThreadLocal内部类ThreadLocalMap才是存储数据的容器,而且该容器由Thread维护。每个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中全部ThreadLocal对象及其对应的值( ThreadLocalMap 是个弱引用类,内部一个EntryThreadLocal对象和Object构成,

为何要用弱引用呢?

若是是直接new一个对象的话,使用完以后设置为null后才能被垃圾收集器清理,若是为弱引用,使用完后垃圾收集器自动清理key,程序员不用再关注指针。

 

操做细节

进行setget等操做都是首先会获取当前线程对象,而后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key ,再作相应的处理。

内存泄露问题

ThreadLocalMap中,只有key是弱引用,value仍然是一个强引用。

每次操做setgetremove操做时,ThreadLocal都会将keynullEntry删除,从而避免内存泄漏。

固然,当若是一个线程运行周期较长,并且将一个大对象放入LocalThreadMap后便再也不调用setgetremove方法,此时该仍然可能会致使内存泄漏。这个问题确实存在,没办法经过ThreadLocal解决,而是须要程序员在完成ThreadLocal的使用后要养成手动调用remove的习惯,从而避免内存泄漏。

使用场景;

Web系统Session的存储 

当请求到来时,能够将当前Session信息存储在ThreadLocal中,在请求处理过程当中能够随时使用Session信息,每一个请求之间的Session信息互不影响。当请求处理完成后经过remove方法将当前Session信息清除便可。 

voliate 的实现原理

为何volatile能保证共享变量的内存可见性?

volatile变量写 

当被volatile修饰的变量进行写操做时,这个变量将会被直接写入共享内存,而非线程的专属存储空间。 

volatile变量读 

当读取一个被volatile修饰的变量时,会直接从共享内存中读,而非线程专属的存储空间中读。

禁止指令重排序

volatile 

volatile读操做的前一行为volatile/写,则这两行不会发生重排序 volatile读操做和它后一行代码都不会发生重排序 

volatile 

volatile写操做和它前一行代码都不会发生重排序; volatile写操做的后一行代码为volatile/写,则这两行不会发生重排序。

volatile变量写后,线程中本地内存中共享变量就会置为失效的状态,所以线程B再须要读取从主内存中去读取该变量的最新值。

 

NIO底层原理

1概念:

NIO 指新IO,核心是同步非阻塞,解决传统IO的阻塞问题。操做对象是Buffer其实NIO的核心是IO线程池,(必定要记住这个关键点)。 NIO中的IO多路复用调用系统级别的selectpoll模型,由系统进行监控IO状态,避免用户线程经过反复尝试的方式查询状态。

Java NIO 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有I/O请求时才启动一个线程进行处理。

2.工做原理:

由一个专门的线程来处理全部的 IO 事件,并负责分发。

事件驱动机制:事件到的时候触发,而不是同步的去监视事件。

线程通信:线程之间经过 wait,notify 等方式通信。保证每次上下文切换都是有意义的。减小无谓的线程切换。

 

3.通讯模型是怎么实现的呢?

java NIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上能够注册咱们感兴趣的事件。

四种事件

服务端接收客户端链接事件SelectionKey.OP_ACCEPT(16)

客户端链接服务端事件SelectionKey.OP_CONNECT(8)

读事件SelectionKey.OP_READ(1)

写事件SelectionKey.OP_WRITE(4)

服务端和客户端各自维护一个管理通道的对象,咱们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件。咱们以服务端为例,若是服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,若是访问selector时发现有感兴趣的事件到达,则处理这些事件,若是没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。

 

IOC底层实现原理

概念:

IOC 是面向对象编程中的一种设计原则,IOC理论提出的观点大致是这样的:借助于第三方实现具备依赖关系的对象之间的解耦。。所谓IoC,对于spring框架来讲,就是由spring来负责控制对象的生命周期和对象间的关系。是说建立对象的控制权进行转移,之前建立对象的主动权和建立时机是由本身把控的,而如今这种权力转移到第三方。

实现原理:

它是经过反射机制+工厂模式实现的,在实例化一个类时,它经过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。

控制反转就是:得到依赖对象的方式反转了。

 

1、依赖注入发生的时间

(1).用户第一次经过getBean方法向IoC容索要Bean时,IoC容器触发依赖注入。

(2).当用户在Bean定义资源中为元素配置了lazy-init属性,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。

2.依赖注入实如今如下两个方法中:

(1).createBeanInstance:生成Bean所包含的java对象实例。

(2).populateBean :对Bean属性的依赖注入进行处理。

 

 

AOP底层实现原理

概念

AOPAspect-OrientedProgramming,面向方面编程),能够说是OOPObject-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来创建一种对象层次结构,用以模拟公共行为的一个集合。  AOP技术则偏偏相反,它利用一种称为横切的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降模块间的耦合度,并有利于将来的可操做性和可维护性。 

AOP的核心思想就是将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。

 

AOP的实现

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法建立方面,从而使得编译器能够在编译期间织入有关方面的代码。

如何使用Spring AOP 

能够经过配置文件或者编程的方式来使用Spring AOP   配置能够经过xml文件来进行,大概有四种方式: 

       配置ProxyFactoryBean,显式地设置advisors, advice, target 

2.        配置AutoProxyCreator,这种方式下,仍是如之前同样使用定义的bean,可是从容器中得到的其实已是代理对象 3.        经过来配置 

4.        经过来配置,使用AspectJ的注解来标识通知及切入点

Spring AOP的实现

如何生成代理类:

Spring提供了两种方式来生成代理对象: JDKProxyCglib,具体使用哪一种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是若是目标类是接口,则使用JDK动态代理技术,不然使用Cglib来生成代理 

切面是如何织入的?

InvocationHandlerJDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。

 

MyisAMinnodb的有关索引的疑问

二者都是什么索引?汇集仍是非汇集https://www.cnblogs.com/olinux/p/5217186.html

MyISAM非汇集)

使用B+Tree做为索引结构,叶节点的data域存放的是数据记录的地址。

MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,若是指定的Key存在,则取出其data域的值,而后以data域的值为地址,读取相应数据记录。

InnoDB汇集索引)

第一个重大区别是InnoDB的数据文件自己就是索引文件,这棵树的叶节点data域保存了完整的数据记录。

可是辅助索引搜索须要检索两遍索引:首先检索辅助索引得到主键,而后用主键到主索引中检索得到记录。

 

由于InnoDB的数据文件自己要按主键汇集,因此InnoDB要求表必须有主键(MyISAM能够没有),若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形。

简单说:

若是咱们定义了主键(PRIMARY KEY),那么InnoDB会选择其做为汇集索引;若是没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的惟一索引做为主键索引

 

关于面试准备

  先推荐一个写的不错的博客,专门关于面试的,比较详尽仔细:关于面试。我在这里简单总结几点:

  1、简历要用心准备好,我的信息,特别是联系方式必定要清晰明确,自身掌握的技能要完成清晰,项目经历最好按照时间顺序,说明本人在项目中的职责,完成的工做,有什么样的提高或收获;

  2、通常面试流程是电面=HR现场面=》技术面=》结果,并非每个面试结果就能立马有结果,因此当面试官说回去等消息的时候,并不表明没有机会,有时候须要讨论筛选才能最终肯定人选。

  3、关于自我介绍,最好简明扼要,能体现自身的特色,表达流畅、自信,提早最好准备;

  4、准备好扎实的基础知识,以及对经历过的项目要有足够的认识,每个项目都是一次学习、提高的机会,通常JAVA集合类是考察的重点;

  5、通常好一点的面试官会顺着知识点逐渐深刻或者逐渐扩展,因此对于知识点的掌握最好全面深刻,不要蜻蜓点水式的学习;

  6、当遇到一些设计类的问题时,通常面试官考察的是你的思路,对问题的应变能力,对于事物观察的点;

JAVA基础(答案仅供参考,若有不对之处请批评指正)

  1HashMap源码,实现原理,JDK8之后对HashMap作了怎样的优化。

  答:HashMap是基于哈希表的Map接口的非同步实现,提供全部可选的映射操做,并容许使用null值和null键,不保证映射的顺序;HashMap是一个链表散列的数据结构,即数组和链表的结合体;它的底层就是一个数组结构,数组中的每一项又是一个链表,每当新建一个HashMap时,就会初始化一个数组;

可参考博客:完全搞懂JAVA集合HashMap,HashTable,ConcurrentHashMap之关联

  而在JDK8中引入了红黑树的部分,当存入到数组中的链表长度大于(默认)8时,即转为红黑树;利用红黑树快速增删改查的特色提升HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。本文再也不对红黑树展开讨论,想了解更多红黑树数据结构的工做原理能够参考http://blog.csdn.net/v_july_v/article/details/6105630

https://images2018.cnblogs.com/blog/1073968/201805/1073968-20180509120713650-389970359.png

可参考博客:JAVA8系列之从新认识HashMap

 

   2HashMap的扩容是怎样扩容的,为何都是2N次幂的大小。

  答:能够参考上文  JAVA8系列之从新认识HashMap  有详细的讲解

 

  3HashMapHashTableConcurrentHashMap的区别

  答:   

  aHashMap是非线程安全的,HashTable是线程安全的。

  bHashMap的键和值都容许有null值存在,而HashTable则不行。

  c、由于线程安全的问题,HashMap效率比HashTable的要高。

  HashMap:它根据键的hashCode值存储数据,大多数状况下能够直接定位到它的值,于是具备很快的访问速度,但遍历顺序倒是不肯定的。 HashMap最多只容许一条记录的键为null,容许多条记录的值为nullHashMap非线程安全,即任一时刻能够有多个线程同时写HashMap,可能会致使数据的不一致。若是须要知足线程安全,能够用 CollectionssynchronizedMap方法使HashMap具备线程安全的能力,或者使用ConcurrentHashMap

   HashtableHashtable是遗留类,不少映射的经常使用功能与HashMap相似,不一样的是它承自Dictionary类,而且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,由于ConcurrentHashMap引入了分段锁。

 

  4、极高并发下HashTableConcurrentHashMap哪一个性能更好,为何,如何实现的。

  答:固然是ConcurrentHashMap,由于ConcurrentHashMap引入了分段锁,而HashTable则使用的是方法级别的锁;所以在新版本中通常不建议使用HashTable,不须要线程安全的场合能够使用HashMap,而须要线程安全的场合能够使用ConcurrentHashMap

 

  5HashMap在高并发下若是没有处理线程安全会有怎样的隐患,具体表现是什么。

  答:可能形成死循环,具体表现链表的循环指向;

 

  6JAVA中四种修饰符的限制范围。

  private:修饰的成员只能在同类中别访问,而在同包、子类和其余包中都不能被访问

  public:修饰的成员在同类、同包、子类(继承自本类)、其余包均可以访问

  protected:修饰的成员在同类、同包、子类中能够访问,其余包中不能被访问

  default:修饰的成员在同类、同包中能够访问,但其余包中无论是否是子类都不能被访问

 

  7Object中的方法

  构造函数

  hashCode():用户获取对象的hash值,用于检索

  queals():用于确认两个对象是否相等;补充,哈希值相同的对象不必定equals(),但equals()的两个对象,hash值必定相等

  toString():返回一个String对象,用来标识本身

  getClass():返回一个class对象,打印的格式通常为  class package.name.xxx,常常用于java的反射机制

  clone():用来另存一个当前存在的对象

  finalize():垃圾回收的时候回用到,匿名对象回收以前会调用到

  wait():用于让当前线程失去操做权限,当前线程进入等待序列

  wait(long)wait(long,int):用户设定下一次获取锁的距离当前释放锁的间隔时间

  notify():用于随机通知一个持有对象锁的线程获取操做的权限

  notifyAll():用于通知全部持有对象锁的线程获取操做权限

 

  8、接口和抽象类的区别 

  答:一个类能够实现多个接口,但只能继承一个抽象类;抽象类能够包含具体的方法,接口全部的方法都是抽象的(JDK8开始新增功能接口中有default方法);抽象类能够声明和使用字段,接口则不能,但能够建立静态的final常量;抽象类的方法能够是protectedpublicprivate或者默认的package,接口的方法都是public;抽象类能够定义构造函数,接口不能;接口被声明为public,省略后,包外的类不能访问接口;

 

  9、动态代理的两种方式,以及区别

  答:jdk动态代理和cglib动态代理;

  JDK动态代理只能对实现了接口的类生成代理,而不能针对类;cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,由于是继承,因此该类或方法最好不要声明称finalfinal能够阻止继承和多态;

  

  10java序列化的方式

  答:实现Serializable接口、实现Externalizable接口(通常只但愿序列化一部分数据,其余数据都使用transient修饰的话有点麻烦,这时候能够使用externalizable接口,指定序列化的属性)

 

  11、传值和传引用的区别,java是怎么样的,有没有传值传引用

  答:首先,java中是没有指针的,只存在值传递;而咱们常常看到对于对象的传递彷佛有点像引用传递,能够改变对象中的某个属性的值,请不要被这个假象蒙蔽了双眼,实际上这个传入函数的值是对象引用的拷贝,即传递的是引用的地址值,因此仍是按值传递;

  传值调用时,改变的是形参的值,并无改变实参的值,实参的值能够传递给形参,可是这个传递是单向的,形参不能传递会实参;

  传引用调用时,若是参数是对象,不管是对象作了何种操做,都不会改变实参对象的引用,可是若是改变了对象的内容,就会改变实参对象的内容;

 

  12@transactional注解在什么状况下会失效,为何。

  答:一个目标对象的方法调用改目标对象的另一个方法时,即便被调用的方法已使用了@Transactional注解标记,事务也不会有效执行;Spring的官方说明在代理下(默认或者配置为proxy-targer-class="true"),只有当前代理类的外部方法调用注解方法时代理才会被拦截。

数据结构和算法

  1B+

  参考:B+树介绍

  2、八大排序算法

  参考:八大排序算法JAVA实现

  3、一致性Hash算法,一致性Hash算法的应用

  答:一致性hash算法是一个负载均衡算法,能够用在分布式缓存、数据库的分库分表等场景,还能够应用在负载均衡器中做为负载均衡算法。在多台服务器时,对于某个请求资源经过hash算法,映射到某一台服务器,当增长或者减小一台服务器时,可能会改变这些资源对应的hash值,这样可能致使一部分缓存或者数据的丢失。一致性hash就是尽量在将同一个资源请求到同一台服务器中;

JVM

  1JVM的内存结构

  答:主要分为三大块堆内存、方法区、栈;栈又分为JVM栈、本地方法栈

    堆(heap space),堆内存是JVM中最大的一块,有年轻代和老年代组成,而年轻代又分为三分部分,Eden区,From SurvivorTo Survivor,默认状况下按照8:1:1来分配

    方法区(Method area),存储类信息、常量、静态变量等数据,是线程共享的区域

    程序计数器(Program counter Register),是一块较小的内存空间,是当前线程所执行的字节码的行号指示器

    JVM栈(JVM stacks),也是线程私有的,生命周期与线程相同,每一个方法被执行时都会建立一个栈帧,用于存储局部变量表、操做栈、动态连接、方法出口等信息

    本地方法栈(Native Mthod Stacks,为虚拟机使用的native方法服务

  2、关于垃圾回收和常见的GC算法,请参考:GC专家系列-理解java垃圾回收

多线程

  1JAVA实现多线程的几种方式

  a、继承Thread类实现

public class MyThread extends Thread { 

  public void run() { 

   System.out.println("MyThread.run()"); 

  } 

} 

 

MyThread myThread1 = new MyThread(); 

MyThread myThread2 = new MyThread(); 

myThread1.start(); 

myThread2.start();

  b、实现Runnable接口

  若是本身的类已经extends另外一个类,就没法直接extends Thread,此时,必须实现一个Runnable接口,以下:

public class MyThread extends OtherClass implements Runnable { 

  public void run() { 

   System.out.println("MyThread.run()"); 

  } 

} 

 

MyThread myThread = new MyThread(); 

Thread thread = new Thread(myThread); 

thread.start();

  c、使用ExecutorServiceCallableFuture实现有返回结果的多线程

import java.util.concurrent.*; 

import java.util.Date; 

import java.util.List; 

import java.util.ArrayList; 

 

/**

* 有返回值的线程

*/ 

@SuppressWarnings("unchecked") 

public class Test { 

public static void main(String[] args) throws ExecutionException, 

    InterruptedException { 

   System.out.println("----程序开始运行----"); 

   Date date1 = new Date(); 

 

   int taskSize = 5; 

   // 建立一个线程池 

   ExecutorService pool = Executors.newFixedThreadPool(taskSize); 

   // 建立多个有返回值的任务 

   List<Future> list = new ArrayList<Future>(); 

   for (int i = 0; i < taskSize; i++) { 

    Callable c = new MyCallable(i + " "); 

    // 执行任务并获取Future对象 

    Future f = pool.submit(c); 

    // System.out.println(">>>" + f.get().toString()); 

    list.add(f); 

   } 

   // 关闭线程池 

   pool.shutdown(); 

 

   // 获取全部并发任务的运行结果 

   for (Future f : list) { 

    // Future对象上获取任务的返回值,并输出到控制台 

    System.out.println(">>>" + f.get().toString()); 

   } 

 

   Date date2 = new Date(); 

   System.out.println("----程序结束运行----,程序运行时间【" 

     + (date2.getTime() - date1.getTime()) + "毫秒】"); 

} 

} 

 

class MyCallable implements Callable<Object> { 

private String taskNum; 

 

MyCallable(String taskNum) { 

   this.taskNum = taskNum; 

} 

 

public Object call() throws Exception { 

   System.out.println(">>>" + taskNum + "任务启动"); 

   Date dateTmp1 = new Date(); 

   Thread.sleep(1000); 

   Date dateTmp2 = new Date(); 

   long time = dateTmp2.getTime() - dateTmp1.getTime(); 

   System.out.println(">>>" + taskNum + "任务终止"); 

   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】"; 

} 

}

   2CallableFuture

  答:Callable接口相似于Runnable,可是Runnable不会返回结果,而且没法抛出返回结果的异常,而Callable更强大,被线程执行之后,能够返回值,这个返回值就是经过Future拿到,也就是说,Future能够拿到异步执行任务的返回值,能够看如下例子: 

import java.util.Random;

import java.util.concurrent.Callable;

import java.util.concurrent.FutureTask;

 

public class Test {

   

    public static void main(String[] args) {

        Callable<Integer> callable = new Callable<Integer>() {

            @Override

            public Integer call() throws Exception {

                return new Random().nextInt(100);

            }       

        };

        FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);

        new Thread(futureTask).start();

        try {

            Thread.sleep(1000);

            System.err.println(futureTask.get());

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

ExecutorService继承自Executor,目的是为咱们管理Thread对象,从而简化并发变成,Executor使咱们无需显示的去管理线程的声明周期,是JDK5以后启动任务的首选方式。

执行多个带返回值的任务,并取得多个返回值,代码以下:

import java.util.concurrent.Callable;

import java.util.concurrent.CompletionService;

import java.util.concurrent.ExecutorCompletionService;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

 

public class CallableAndFuture {

   

    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newCachedThreadPool();

        CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);

        for( int i = 0; i < 5; i++ ){

            final int taskId = i;

            cs.submit(new Callable<Integer>() {

                @Override

                public Integer call() throws Exception {

                    return taskId;

                }

            });

        }

       

        for( int i = 0; i < 5; i++ ){

            try {

                System.err.println(cs.take().get());

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

}

  

  3、线程池的参数有哪些,在线程池建立一个线程的过程

  corePoolSize:核心线程数,可以同时执行的任务数量

  maximumPoolSize:除去缓冲队列中等待的任务,最大能容纳的任务数(其实就是包括了核心线程池的数量)

  keepAliveTime:超出workQueue的等待任务的存活时间,就是指maximumPoolSize里面的等待任务的存活等待时间

  unit:时间单位

  workQueue:阻塞等待线程的队列,通常使用new LinkedBlockingQueue()这个,若是不指定容量,会一直往里添加,没有限制,workQueue永远不会满,通常选择没有容量上限的队列

  threadFactory:建立线程的工厂,使用系统默认的类

  handler:当任务数超过maximumPoolSize时,对任务的处理策略,默认策略是拒绝添加

  执行流程:当线程数小于corePoolSize时,每添加一个任务,则当即开启线程执行;当corePoolSize满的时候,后面添加的任务将放入缓冲队列workQueue等待;当workQueue满的时候,看是否超过maximumPoolSize线程数,若是超过,则拒绝执行,若是没有超过,则建立线程理解执行;  

 import java.util.concurrent.Executors;

 import java.util.concurrent.LinkedBlockingQueue;

 import java.util.concurrent.ThreadPoolExecutor;

 import java.util.concurrent.TimeUnit;

 

 /**

  * 对线程池进行管理和封装

  * @author guoqing

  *

  */

 public class ThreadPoolManager {

    

     private static ThreadPoolManager mInstance = new ThreadPoolManager();

     private ThreadPoolExecutor executor;

    

     private int corePoolSize;    //核心线程池数量,表示可以同时执行的任务数量

     private int maximumPoolSize;    //最大线程池数量,实际上是包含了核心线程池数量在内的

     private long keepAliveTime = 1;        //存活时间,表示最大线程池中等待任务的存活时间

     private TimeUnit unit = TimeUnit.HOURS;        //存活时间的时间单位

    

     public static ThreadPoolManager getInstance() {

         return mInstance;

     }

    

     private ThreadPoolManager() {

         //核心线程数量的计算规则:当前设备的可用处理器核心数*2+1,可以让cpu获得最大效率的发挥

         corePoolSize = Runtime.getRuntime().availableProcessors()*2+1;

         maximumPoolSize = corePoolSize;    //虽然用不到,可是不能为0,不然会报错

         //线程池机制:领工资的机制

         executor = new ThreadPoolExecutor(corePoolSize,

                 maximumPoolSize,

                 keepAliveTime,

                 unit,

                 new LinkedBlockingQueue<Runnable>(),    //缓冲队列,超出核心线程池的任务会被放入缓冲队列中等待

                 Executors.defaultThreadFactory(),        //建立线程的工厂类

                 new ThreadPoolExecutor.AbortPolicy()    //当最大线程池也超出的时候,则拒绝执行

                 );   

     }

     

     /**

      * 往线程池中添加任务

      * @param r

      */

     public void executor(Runnable r) {

         if(r!=null) {

             executor.execute(r);

         }

     }

    

     /**

      * 从线程池中移除任务

      * @param r

      */

     public void remove(Runnable r) {

         if(r!=null) {

             executor.remove(r);

         }

     }

 } 

  4volatile关键字的做用,原理

  答:保证内存可见性和禁止指令重排。实现原理可参考:JAVA并发变成--valatile关键字剖析

 

  5synchronized关键字的用法,优缺点

  答:java关键字,当它用来修饰一个方法或者代码块的时候,可以保证在同一时刻最多只有一个线程执行该代码段的代码;

    synchronized修饰的方法或者对象,只能以同步的方式执行,会引发性能问题;没法中断一个正在等候得到锁的线程,也没法经过投票得到锁;一个优先级高的线程等待一个优先级低的线程释放锁会致使优先级倒置,引发性能风险;

  

  6Lock接口有哪些实现类,使用场景是什么

  答:Lock接口有三个实现类,一个是ReentrantLock,另两个是ReentrantReadWriteLock类中的两个静态内部类ReadLockWriteLock

  使用场景:通常应用于多度少写,由于读的线程之间没有竞争,因此比起synchronzied,性能要好不少;

   

  7、悲观锁、乐观锁的优缺点,CAS有什么缺陷,该如何解决

  悲观锁:老是假设最坏的状况,每次去拿数据的时候都认为别人会修改,因此每次拿数据的时候都会上锁,这样别人拿数据的时候就会阻塞知道它拿到锁;好比关系型数据库的行锁、表锁、读锁、写锁;好比java里面的同步原语synchronized关键字的实现也是悲观锁;

  乐观锁:每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下再次期间别人有没有更新这个数据。乐观锁适用于多读的应用类型,能够提升吞吐量。javajava.util.conncurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的;

  CASCAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其余线程都失败,失败的线程不会被挂起,而是被告知此次竞争失败,并能够再次尝试;

  CAS的缺陷:ABA问题、循环时间长开销大,只能保证一个共享变量的原子操做;

  

  8ABC三个线程如何保证顺序执行

  答:用Thread.join() 方法,或者线程池newSingleThreadExecutor(原理是会将全部线程放入一个队列,而队列则保证了FIFO,也能够经过ReentrantLockstate整数用阿里判断轮到谁来执行

 

  9、线程的状态都有哪些(五大状态)

  新建状态(new):当用new操做符建立一个线程时,如new Thread(),线程尚未开始运行,此时处于仙剑状态;

  就绪状态(runnable):一个新建立的线程并不自动开始运行,要执行线程,必需要调用线程的start()方法,当线程对象调用start()方法即启动了线程,start()方法建立线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态;

  运行状态(running):当线程得到cpu时间后,他才进入运行状态,真正开始实行run()方法

  阻塞状态(blocked):当线程运行过程当中,可能因为各类缘由进入阻塞状态;

    a.线程经过调用sleep方法进入睡眠状态

    b.线程调用一个在I/O上被阻塞的操做,即该操做在输入输出操做完成以前不会返回到它的调用者

    c.线程试图获得一个锁,而该锁正被其余线程持有

    d.线程正等待某个触发条件

  死亡状态(dead):run方法天然退出而天然死亡,或者一个未捕获的异常终止了run方法而使线程猝死

   https://images2018.cnblogs.com/blog/1073968/201805/1073968-20180510120402733-98466055.png

 

  10sleepwait的区别

  答:首先,sleep()方法属于Thread类的,而wait()方法是属于Object类的;sleep()方法致使了程序暂停执行指定的时间,让出cpu给其余线程,可是他的监控状态依然保持,当指定的时间到了又自动回恢复运行状态,调用了sleep()方法的过程当中,线程不会释放对象锁;而当调用了wait()方法的时候,线程回放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。

 

  11notify()notifyAll()的区别

  答:notify()方法表示,当前线程已经放弃对资源的占有,通知等待的线程来获取对资源的占有权,可是只有一个线程可以从wait状态中恢复;notifyAll()方法表示,当前的线程已经放弃对资源的占有,通知全部的等待线程从wait()方法后的语句开始执行,但最终只有一个线程能竞争得到锁并执行;notify()是对notifyAll()的一个优化,

 

   12ThreadLocal的了解,实现原理。

   答:ThreadLocal,线程本地变量。定义了一个ThreadLocal,每一个线程往这个ThreadLocal中读写都是线程隔离的,互相之间不会影响,他提供了一种将可变数据经过每一个线程有本身的独立副本从而实现线程封闭的机制;实现的思路,Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每一个线程都有一个本身的ThreadLocalMapThreadLocalMap有本身的独立实现,能够简单的将它的key视做ThreadLocalvalue为代码中放入的值(实际上key并非ThreadLocal本省,而是它的一个弱引用)。每一个线程在往ThreadLocalset值的时候,都会往本身的ThreadLocalMap里存,读也是已某个ThreadLocal做为引用,在本身的map里找对应的key,从而实现了线程的隔离。若是想详细了解,能够参考:ThreadLocal源码解读

数据库相关

  1、常见的数据库优化手段

  答:库表优化,表设计合理化,符合三大范式;添加适当的索引(普通索引、主键索引、惟一索引、全文索引);分库分表;读写分离等;sql语句优化,定位执行效率低,慢sql的语句,经过explain分析低效率的缘由;

 

  2、索引的优缺点,什么字段上创建索引

  答:优势方面:第一,经过建立惟一索引能够保证数据的惟一性;第二,能够大大加快数据的检索速度,是主要目的;第三;在使用分组和排序子句进行数据检索时,能够显著减小查询中分组和排序的时间;第四,能够在查询中使用优化隐藏器,提升系统的性能;

  缺点方面:第一,建立索引和维护索引要耗费时间,而且随着数据量的增长而增长;第二,每个索引须要占用额外的物理空间,须要的磁盘开销更大;第三,当对表中的数据进行增长、删除、修改操做时,索引也要动态维护,下降了数据的维护速度;

  通常来讲,在常常须要搜索的列上,强制该列的惟一性和组织表中数据的排列结构的列,在常常用在连接的列上,在常常须要排序的列上,在常用在where字句的列上能够添加索引,以提高查询速度;一样,对于一些甚少使用或者参考的列,只有不多数值的列(如性别),定义为text,image,bit的列,修改性能远远大于检索性能的列不适合添加索引;

 

  3、数据库链接池

  答:数据库链接池(Connection pooling)是程序启动时创建足够的数据库链接,并将这些链接组成一个链接池,由程序动态的对池中的链接进行申请、使用、释放;

  (1)程序初始化时建立链接池

  (2)使用时向链接池申请可用链接

  (3)使用完毕,将链接返还给链接池

  (4)程序退出时,断开全部的链接,并释放资源

计算机网络

  1TCPUDP的区别

  答:TCP(传输控制协议),UDP(用户数据报协议)

  (1TCP面向链接(如打电话先拨号创建链接);UDP是无链接的,即发送数据以前不须要创建链接;

  (2TCP提供可靠的服务。也就是说,经过TCP链接传送的数据,无差错,不丢失,不重复,且按序达到;UDP尽最大努力交付,即不保证可靠交付;

  (3TCP面向字节流,其实是TCP把数据当作一连串无结构的字节流;UDP是面向报文,UDP没有拥塞控制,所以网络出现拥塞不会使源主机的发送速率下降(对实时应用颇有用,如IP电话,实时视频会议等)

  (4)每一条TCP链接只能是点到点的,UDP支持一对一,一对多,多对一和多对多的交互通讯;

  (5TCP首部开销20字节,UDP首部开销8字节;

  (6TCP的逻辑通讯信道是全双工的可靠信道,DUP则是不可靠信道;

 

  2、三次握手,四次挥手,为何要四次挥手。

  答:三次握手的目的是创建可靠的通讯信道,简单来讲就是数据的发送与接收,主要目的是双方确认本身与对方的发送和接收机能正常;    

    第一次握手:Client什么都不能确认,Server确认了对方发送正常;

    第二次握手:Clent确认了,本身发送、接收正常,对方发送、接收正常;Server确认了本身接收正常,对方发送正常;

    第三次握手:Clent确认了,本身发送、接收正常,对方发送、接收正常;Server确认了本身发送、接收正常,对方发送、接收正常;

  因此,通过三次握手以后,就能确认双方收发功能都正常;

https://images2018.cnblogs.com/blog/1073968/201805/1073968-20180510145603128-1725312889.png

  四次挥手:

    A:“喂,我不说了 (FIN)”A->FIN_WAIT1

    B:“我知道了(ACK)。等下,上一句还没说完Balabala…..传输数据”B->CLOSE_WAIT | A->FIN_WAIT2

    B:”好了,说完了,我也不说了(FIN)。”B->LAST_ACK

    A:”我知道了(ACK)。”A->TIME_WAIT | B->CLOSED

    A等待2MSL,保证B收到了消息,不然重说一次我知道了”,A->CLOSED

   https://images2018.cnblogs.com/blog/1073968/201805/1073968-20180510145849143-1912183142.png

  3、长链接和短链接。

  短链接:链接=》传输数据=》关闭链接

  HTTP是无状态的,浏览器和服务器之间每进行一次http操做,就创建一次链接,但任务结束就中断链接;也能够理解为短链接是指socket链接后,发送接收完数据立刻断开链接;

  长链接:链接=》传输数据=》保持链接=》传输数据=》。。。=》关闭链接

  长链接指创建socket链接后无论是否使用都保持链接,但安全性较差;

 设计模式

  此处推荐阅读:java23种设计模式深刻理解

  1、单例模式的几种写法

  懒汉模式

public class Singleton {

 

    private static Singleton instance = null;

private Singleton(){}

 

    public static synchronized Singleton getInstance(){

        //若是尚未被实例化过,就实例化一个,而后返回

        if(instance == null){

            instance = new Singleton();

        }

        return instance;

    }

}

  饿汉模式

public class Singleton {

    //类加载的时候instance就已经指向了一个实例

    private static Singleton instance = new Singleton();

    private Singleton(){}

 

    public static Singleton getInstance(){

        return instance;

    }

}

  双重检验锁

public class Singleton {

   

    private static Singleton instance = null;

    private Singleton(){}

 

    public static Singleton getInstance(){

        if(instance == null){

            synchronized (Singleton.class){

                if(instance == null){

                    instance = new Singleton();

                }

            }

        }

        return instance;

    }

}

  静态内部类:由于JAVA静态内部类的特性,加载的时候不会加载内部静态类,使用的时候才会加载,而使用的时候类加载又是线程安全的,这就完美达到了效果;

public class Singleton {

 

    private static class SingletonHolder{

        private static Singleton instance = new Singleton();

    }

 

    private Singleton(){}

 

    public static Singleton getInstance(){

        return SingletonHolder.instance;

    }

}

  枚举:

public enum Singleton {

    INSTANCE;

}

  

  2Spring使用了哪些设计模式

  (1)工厂模式,在各类BeanFactory以及ApplicationContext建立中都用到了;

  (2)模板模式,也是在各类BeanFactory以及ApplicationContext建立中都用到了;

  (3)代理模式,在AOP实现中用到了JDK的动态代理;

  (4)单例模式,好比建立bean的时候;

  (5)策略模式,第一个地方,加载资源文件的地方,使用了不一样的方法,好比:classPathResource,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的接口Resource;第二个地方就是AOP的实现中,采用了不一样的方式,JDK动态代理和CGLIB代理;

分布式相关

  1、分布式事务的控制

  能够参考分布式系统事务一致性解决方案

 

  2、分布式锁

  答:通常使用zk瞬时有序节点实现的分布式锁,或者利用redissetnx()封装分布式锁;提供思路,具体的能够自行详细理解;

 

  3、分布式session如何设计

  答:一个比较成熟的方案是经过redis进行session共享。详细的原理能够参考一种分布式session实现方案

 

  4、关于dubbo

  能够参考博文:Dubbo学习总结(2——Dubbo架构详解

 

  5、能够了解zk相关知识

 

缓存相关

  1redismemcached的区别

  (1redismemcache都是将数据放入内存中,都是内存数据库。可是memcache能够缓存图片、视频等数据;

  (2redis不只仅支持简单的k/v数据,还提供listsethash等数据结构的存储;

  (3)虚拟内存--redis当物理内存用完时,能够将一些好久没有用到的value交换到磁盘;

  (4)过时策略--memcacheset时就指定,例如set key1008,即永不过时,redis经过expire设定;

  (5)分布式--设定memcache集群,利用magent作一主多从;redis能够作一主多从或一主一从;

  (6)存储数据安全--memcache挂掉后,数据没了,redis能够按期保存到磁盘进行持久化;

  (7)灾难恢复--memcache挂掉后,数据不可恢复。redis数据丢失后能够经过aof恢复;

  (8redis支持数据备份,即master-slave主备模式;

 

  2redis是单线程的么(是的)

  3redis的持久化策略

  答:rdb:快照形式是直接把内存中的数据保存到一个dump文件中,定时保存

    aof:把全部的对redis的服务器进行修改的命令都存到一个文件里,命令的集合

 

框架相关

  1SpringMvc工做原理

  (1)用户发送请求至前端控制器DispatcherServlet

  (2DispatcherServlet收到请求调用HandlerMapping处理映射器

  (3)处理器映射器找到具体的处理器(能够根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(若有则生成)一并返回给DispatcherServlet

  (4DispatcherServlet调用HandlerAdapter处理器映射器

  (5HandlerAdapter通过适配调用具体的处理器(Controller,也叫后端控制器)

  (6Controller执行完成返回ModelAndView

  (7HandlerAdapterController执行结果ModelAndView返回给DispatcherServlet

  (8DispatcherServletModelAndView传给ViewResolver视图解析器

  (9ViewResolver解析后返回具体的view

  (10DispatcherServlet根据view进行试图渲染(即将模型数据填充至视图中)

  (11DispatcherServlet响应用户  

  如下组件一般使用框架提供实现:

  DispatcherServlet:做为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,下降组件之间的耦合性,提升每一个组件的扩展性。

  HandlerMapping:经过扩展处理器映射器实现不一样的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 

  HandlAdapter:经过扩展处理器适配器,支持更多类型的处理器。

  ViewResolver:经过扩展视图解析器,支持更多类型的视图解析,例如:jspfreemarkerpdfexcel等。

  

  2Quartz概念及原理

  org.quartz.Job:它是一个抽象接口,表示一个工做,也是咱们要执行的具体的内容,只定义了一个接口方法:void execute(JobExecutionContext context)

  org.quartz.JobDetailJobDetail表示一个具体的可执行的调度程序,Job是这个可执行调度程序所要执行的内容,它包含了这个调度任务的方案和策略

  org.quartz.TriggerTrigger是一个抽象接口,表示一个调度参数的配置,经过配置他,来告诉调度器何时去调用JobDetail

  org.quartz.Scheduler:一个调度容器,能够注册多个TriggerJobDetail。当TriggerJobDetail组合,就能够被Scheduler容器调度了

  

  3SpringIOC有什么优点

  答:要了解IOC首先要明白依赖倒置原则(Dependency Inversion Principle),就是把本来的高层建筑依赖底层建筑倒置过来,变成底层建筑依赖高层建筑。高层建筑决定须要什么,底层去实现这样的需求,可是高层并不用管底层的是怎么实现的;而控制反转(Inversion of Control)就是依赖倒置原则的一种代码的设计思路;

   https://images2018.cnblogs.com/blog/1073968/201805/1073968-20180510170422044-126446996.png

  IOC思想的核心,资源不禁使用资源的双方管理,由不适用资源的第三方管理。

  优点:资源集中管理,实现资源的可配置和易管理;下降了使用资源双方的依赖程度,也就是下降了耦合度;

 

1. ZooKeeper是什么?

ZooKeeper是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操做。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

分布式应用程序能够基于Zookeeper实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。

Zookeeper保证了以下分布式一致性特性:

·         顺序一致性

·         原子性

·         单一视图

·         可靠性

·         实时性(最终一致性)

客户端的读请求能够被集群中的任意一台机器处理,若是读请求在节点上注册了监听器,这个监听器也是由所链接的zookeeper机器来处理。对于写请求,这些请求会同时发给其余zookeeper机器而且达成一致后,请求才会返回成功。所以,随着zookeeper的集群机器增多,读请求的吞吐会提升可是写请求的吞吐会降低。

有序性是zookeeper中很是重要的一个特性,全部的更新都是全局有序的,每一个更新都有一个惟一的时间戳,这个时间戳称为zxidZookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid

2. ZooKeeper提供了什么?

1、文件系统
2
、通知机制

3. Zookeeper文件系统

Zookeeper提供一个多层级的节点命名空间(节点称为znode)。与文件系统不一样的是,这些节点均可以设置关联的数据,而文件系统中只有文件节点能够存放数据而目录节点不行。
Zookeeper
为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,每一个节点的存放数据上限为1M

4. ZAB协议?

ZAB协议是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。

ZAB协议包括两种基本的模式:崩溃恢复和消息广播。

当整个zookeeper集群刚刚启动或者Leader服务器宕机、重启或者网络故障致使不存在过半的服务器与Leader服务器保持正常通讯时,全部进程(服务器)进入崩溃恢复模式,首先选举产生新的Leader服务器,而后集群中Follower服务器开始与新的Leader服务器进行数据同步,当集群中超过半数机器与该Leader服务器完成数据同步以后,退出恢复模式进入消息广播模式,Leader服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。

5. 四种类型的数据节点 Znode

·      PERSISTENT-持久节点
除非手动删除,不然节点一直存在于Zookeeper

·      EPHEMERAL-临时节点
临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与zookeeper链接断开不必定会话失效),那么这个客户端建立的全部临时节点都会被移除。

·      PERSISTENT_SEQUENTIAL-持久顺序节点
基本特性同持久节点,只是增长了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

·      EPHEMERAL_SEQUENTIAL-临时顺序节点
基本特性同临时节点,增长了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

6. Zookeeper Watcher 机制 -- 数据变动通知

Zookeeper容许客户端向服务端的某个Znode注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,而后客户端根据Watcher通知状态和事件类型作出业务上的改变。

工做机制:

·         客户端注册watcher

·         服务端处理watcher

·         客户端回调watcher

Watcher特性总结:

1.    一次性
不管是服务端仍是客户端,一旦一个Watcher被触发,Zookeeper都会将其从相应的存储中移除。这样的设计有效的减轻了服务端的压力,否则对于更新很是频繁的节点,服务端会不断的向客户端发送事件通知,不管对于网络仍是服务端的压力都很是大。

2.    客户端串行执行
客户端Watcher回调的过程是一个串行同步的过程。

3.    轻量

·         Watcher通知很是简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。

·         客户端向服务端注册Watcher的时候,并不会把客户端真实的Watcher对象实体传递到服务端,仅仅是在客户端请求中使用boolean类型属性进行了标记。

          watcher event异步发送watcher的通知事件从server发送到client是异步的,这就存在一个问题,不一样的客户端和服务器之间经过socket进行通讯,因为网络延迟或其余因素致使客户端在不通的时刻监听到事件,因为Zookeeper自己提供了ordering guarantee,即客户端监听事件后,才会感知它所监视znode发生了变化。因此咱们使用Zookeeper不能指望可以监控到节点每次的变化。Zookeeper只能保证最终的一致性,而没法保证强一致性。

          注册watcher getDataexistsgetChildren

          触发watcher createdeletesetData

          当一个客户端链接到一个新的服务器上时,watch将会被以任意会话事件触发。当与一个服务器失去链接的时候,是没法接收到watch的。而当client从新链接时,若是须要的话,全部先前注册过的watch,都会被从新注册。一般这是彻底透明的。只有在一个特殊状况下,watch可能会丢失:对于一个未建立的znodeexist watch,若是在客户端断开链接期间被建立了,而且随后在客户端链接上以前又删除了,这种状况下,这个watch事件可能会被丢失。

7. 客户端注册Watcher实现

1.    调用getData()/getChildren()/exist()三个API,传入Watcher对象

2.    标记请求request,封装WatcherWatchRegistration

3.    封装成Packet对象,发服务端发送request

4.    收到服务端响应后,将Watcher注册到ZKWatcherManager中进行管理

5.    请求返回,完成注册。

8. 服务端处理Watcher实现

1.            服务端接收Watcher并存储
接收到客户端请求,处理请求判断是否须要注册Watcher,须要的话将数据节点的节点路径和ServerCnxnServerCnxn表明一个客户端和服务端的链接,实现了Watcherprocess接口,此时能够当作一个Watcher对象)存储在WatcherManagerWatchTablewatch2Paths中去。

2.    Watcher触发
以服务端接收到 setData() 事务请求触发NodeDataChanged事件为例:

·         封装WatchedEvent
将通知状态(SyncConnected)、事件类型(NodeDataChanged)以及节点路径封装成一个WatchedEvent对象

·         查询Watcher
WatchTable中根据节点路径查找Watcher

·         没找到;说明没有客户端在该数据节点上注册过Watcher

·         找到;提取并从WatchTableWatch2Paths中删除对应Watcher从这里能够看出Watcher在服务端是一次性的,触发一次就失效了

          调用process方法来触发Watcher
这里process主要就是经过ServerCnxn对应的TCP链接发送Watcher事件通知。

9. 客户端回调Watcher

客户端SendThread线程接收事件通知,交由EventThread线程回调Watcher。客户端的Watcher机制一样是一次性的,一旦被触发后,该Watcher就失效了。

10. ACL权限控制机制

UGOUser/Group/Others

目前在Linux/Unix文件系统中使用,也是使用最普遍的权限控制方式。是一种粗粒度的文件系统权限控制模式。

ACLAccess Control List)访问控制列表

包括三个方面:

·         权限模式(Scheme

o    IP:从IP地址粒度进行权限控制

o    Digest:最经常使用,用相似于 username:password 的权限标识来进行权限配置,便于区分不一样应用来进行权限控制

o    World:最开放的权限控制方式,是一种特殊的digest模式,只有一个权限标识“world:anyone”

o    Super:超级用户

·      受权对象
受权对象指的是权限赋予的用户或一个指定实体,例如IP地址或是机器灯。

·         权限 Permission

o    CREATE:数据节点建立权限,容许受权对象在该Znode下建立子节点

o    DELETE:子节点删除权限,容许受权对象删除该数据节点的子节点

o    READ:数据节点的读取权限,容许受权对象访问该数据节点并读取其数据内容或子节点列表等

o    WRITE:数据节点更新权限,容许受权对象对该数据节点进行更新操做

o    ADMIN:数据节点管理权限,容许受权对象对该数据节点进行ACL相关设置操做

11. Chroot特性

3.2.0版本后,添加了 Chroot特性,该特性容许每一个客户端为本身设置一个命名空间。若是一个客户端设置了Chroot,那么该客户端对服务器的任何操做,都将会被限制在其本身的命名空间下。

经过设置Chroot,可以将一个客户端应用于Zookeeper服务端的一颗子树相对应,在那些多个应用公用一个Zookeeper进群的场景下,对实现不一样应用间的相互隔离很是有帮助。

12. 会话管理

分桶策略:将相似的会话放在同一区块中进行管理,以便于Zookeeper对会话进行不一样区块的隔离处理以及同一区块的统一处理。

分配原则:每一个会话的下次超时时间点ExpirationTime

计算公式:

ExpirationTime_ = currentTime + sessionTimeout
ExpirationTime1 = (ExpirationTime_ / ExpirationInrerval + ) * ExpirationInterval , ExpirationInterval 是指 Zookeeper 会话超时检查时间间隔,默认 tickTime

13. 服务器角色

Leader

·         事务请求的惟一调度和处理者,保证集群事务处理的顺序性

·         集群内部各服务的调度者

Follower

·         处理客户端的非事务请求,转发事务请求给Leader服务器

·         参与事务请求Proposal的投票

·         参与Leader选举投票

Observer

3.3.0版本之后引入的一个服务器角色,在不影响集群事务处理能力的基础上提高集群的非事务处理能力

·         处理客户端的非事务请求,转发事务请求给Leader服务器

·         不参与任何形式的投票

14. Zookeeper Server工做状态

服务器具备四种状态,分别是LOOKINGFOLLOWINGLEADINGOBSERVING

·         LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有Leader,所以须要进入Leader选举状态。

·         FOLLOWING:跟随者状态。代表当前服务器角色是Follower

·         LEADING:领导者状态。代表当前服务器角色是Leader

·         OBSERVING:观察者状态。代表当前服务器角色是Observer

15. Leader 选举

Leader选举是保证分布式数据一致性的关键所在。当Zookeeper集群中的一台服务器出现如下两种状况之一时,须要进入Leader选举。

  (1) 服务器初始化启动。

  (2) 服务器运行期间没法和Leader保持链接。

  下面就两种状况进行分析讲解。

  1. 服务器启动时期的Leader选举

  若进行Leader选举,则至少须要两台机器,这里选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独没法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器能够相互通讯,每台机器都试图找到Leader,因而进入Leader选举过程。选举过程以下

  (1) 每一个Server发出一个投票。因为是初始状况,Server1Server2都会将本身做为Leader服务器来进行投票,每次投票会包含所推举的服务器的myidZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0)Server2的投票为(2, 0),而后各自将这个投票发给集群中其余机器。

  (2) 接受来自各个服务器的投票。集群的每一个服务器收到投票后,首先判断该投票的有效性,如检查是不是本轮投票、是否来自LOOKING状态的服务器。

  (3) 处理投票。针对每个投票,服务器都须要将别人的投票和本身的投票进行PKPK规则以下

    · 优先检查ZXIDZXID比较大的服务器优先做为Leader

    · 若是ZXID相同,那么就比较myidmyid较大的服务器做为Leader服务器。

  对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较二者的ZXID,均为0,再比较myid,此时Server2myid最大,因而更新本身的投票为(2, 0),而后从新投票,对于Server2而言,其无须更新本身的投票,只是再次向集群中全部机器发出上一次投票信息便可。

  (4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader

  (5) 改变服务器状态。一旦肯定了Leader,每一个服务器就会更新本身的状态,若是是Follower,那么就变动为FOLLOWING,若是是Leader,就变动为LEADING

  2. 服务器运行时期的Leader选举

  在Zookeeper运行期间,Leader与非Leader服务器各司其职,即使当有非Leader服务器宕机或新加入,此时也不会影响Leader,可是一旦Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致。假设正在运行的有Server1Server2Server3三台服务器,当前LeaderServer2,若某一时刻Leader挂了,此时便开始Leader选举。选举过程以下

  (1) 变动状态。Leader挂后,余下的非Observer服务器都会讲本身的服务器状态变动为LOOKING,而后开始进入Leader选举过程。

  (2) 每一个Server会发出一个投票。在运行期间,每一个服务器上的ZXID可能不一样,此时假定Server1ZXID123Server3ZXID122;在第一轮投票中,Server1Server3都会投本身,产生投票(1, 123)(3, 122),而后各自将投票发送给集群中全部机器。

  (3) 接收来自各个服务器的投票。与启动时过程相同。

  (4) 处理投票。与启动时过程相同,此时,Server1将会成为Leader

  (5) 统计投票。与启动时过程相同。

  (6) 改变服务器的状态。与启动时过程相同。

  2.2 Leader选举算法分析

  在3.4.0后的Zookeeper的版本只保留了TCP版本的FastLeaderElection选举算法。当一台机器进入Leader选举时,当前集群可能会处于如下两种状态

    · 集群中已经存在Leader

    · 集群中不存在Leader

  对于集群中已经存在Leader而言,此种状况通常都是某台机器启动得较晚,在其启动以前,集群已经在正常工做,对这种状况,该机器试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器而言,仅仅须要和Leader机器创建起链接,并进行状态同步便可。而在集群中不存在Leader状况下则会相对复杂,其步骤以下

  (1) 第一次投票。不管哪一种致使进行Leader选举,集群的全部机器都处于试图选举出一个Leader的状态,即LOOKING状态,LOOKING机器会向全部其余机器发送消息,该消息称为投票。投票中包含了SID(服务器的惟一标识)和ZXID(事务ID),(SID, ZXID)形式来标识一次投票信息。假定Zookeeper5台机器组成,SID分别为12345ZXID分别为99988,而且此时SID2的机器是Leader机器,某一时刻,12所在机器出现故障,所以集群开始进行Leader选举。在第一次投票时,每台机器都会将本身做为投票对象,因而

相关文章
相关标签/搜索