首页 / 面试 / Java高级工程师面试大全
Java高级工程师面试大全
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java高级工程师面试大全,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含54939字,纯文字阅读大概需要79分钟。
内容图文
![Java高级工程师面试大全](/upload/InfoBanner/zyjiaocheng/851/88ca808cdac8455ead140cb83da7b7c5.jpg)
前言答主这段时间呕心沥血的搜罗的面试题,大部分在面试中被问到了,中间蚂蚁金服面试失败了有点遗憾希望对大家有帮助,有的我注明了连接,有的忘了后面也找不到了,如有侵权实在不好意思,联系我删掉!dcr1178890956,对区块链技术感兴趣的开一起交流。
一、Java基础
1.1String类为什么是final的
不可变性支持线程安全,不可变性支持字符串常量池
1.2.HashMap的源码实现原理底层结构
Map或者HashMap的存储原理
https://blog.csdn.net/vking_wang/article/details/14166593
1.3.反射中Class.forName和classloader的区别
java中class.forName()和classLoader都可用来对类进行加载。
class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象
1.4.session和cookie的区别和联系session的生命周期,多个服务部署时session管理
浏览器访问服务器时,服务器会创建一个对象(该对象也称为session对象,该对象有一个唯一的id号与其对应)。然后,服务器会将id号发送给浏览器(默认情况下,使用cookie机制发送)。当浏览器再次访问服务器时,会将id号发送过来。服务器可以依据id号找到对应的session对象。通过这个session对象,来保存状态。
session和cookie的区别和联系
(1)存储数据量方面:session能够存储任意的 对象,cookie 只能存储 String 类型的对象 。
(2) cookie数据存放在客户的浏览器上,session数据放在服务器上。
(3) cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。
(4) session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE。单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
(5)域的支持范围不一样,比方说a.com的Cookie在api.a.com下都能用,而www.a.com的Session在api.a.com下都不能用,解决这个问题的办法是JSONP或者跨域资源共享。
session的生命周期
(1)Session的创建,Session存储在服务器端,一般为了防止在服务器的内存中(为了高速存取),Sessinon在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session。
(2)Session的销毁,服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效。Tomcat中Session的默认失效时间为20分钟。调用Session的invalidate方法。服务器进程被停止 。
保存session id的几种方式
(1)保存session id的方式可以采用cookie
(2)URL地址重写是对客户端不支持Cookie的解决方案。
(3)另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。
多个服务部署时session管理
(1)将Session对象保存在Cookie,然后存放在浏览器端。每次浏览器向服务器发送请求的时候,都会把整个Session对象放在请求里一起发送到服务器,以此来实现Session共享。这样的方案实现起来特别方便,但是由于Cookie的存储容量比较小,所以这个方案只适用于Session数据量小的场景。
(2)Session复制。部分应用服务器能够支持Session复制功能,例如Tomcat。用户可以通过修改配置文件,让应用服务器进行Session复制,保持每一个服务节点的Session数据达到一致。但是这个方案的实现依赖于应用服务器。当应用被大量用户访问时,每个服务器都需要有一部分内存用来存放Session,同时因为大量Session通过网路传输进行复制,将会占用网络资源,还可能因为网络延迟导致程序异常。
(3)Session粘滞。利用负载均衡器的分发能力,将同一浏览器上同一用户的请求,都定向发送到固定服务器上,让这个服务器处理该用户的所有请求,这样只要这个服务器上保存了用户Session,就能保证用户的状态一致性。但是这个方案依赖于负载均衡器,而且只适用于横向扩展的集群场景,不能满足分布式场景。(nginx的ip_hash)
1.5.Java中的队列都有哪些,有什么区别
1.6.Java的内存模型以及GC算法
JVM结构原理、GC工作机制详解
https://blog.csdn.net/moneyshi/article/details/53033577
GC机制及算法
http://blog.chinaunix.net/uid-7374279-id-4489100.html
1.7.Java数组和链表两种结构的操作效率
数组静态分配内存,链表动态分配内存;
数组在内存中连续,链表不连续;
数组元素在栈区,链表元素在堆区;
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
1.8.Java内存泄露的问题调查定位
JVM调优之JConsole和JVisualVM工具使用
https://blog.csdn.net/moneyshi/article/details/81511687
Java内存泄露监控检测工具-- JVM监控工具介绍jstack, jconsole, jinfo, jmap, jdb, jstat
https://blog.csdn.net/chenleixing/article/details/43230099
1.9synchronized static修饰类和方法有什么区别
在static方法前加synchronizedstatic:静态方法属于类方法,它属于这个类,获取到的锁,是属于类的锁。
在普通方法前加synchronizedstatic:非static方法获取到的锁,是属于当前对象的锁。
结论:类锁和对象锁不同,他们之间不会产生互斥。
1.10HashMap的底层工作原理和并发问题
构造函数:HashMap里有一个数组table,它存储的元素类型是HashMapEntry这个HashMapEntry数组里每个存储元素的位置称为bucket,HashMap中分配的数组大小长度一定是2的次方数。每个bucket只能存放一个Entry元素,系统可根据bucket的索引迅速访问其中存储的元素。Put方法如果key不为null,会通过key计算出一个hash值,再利用这个hash值计算出HashMapEntry对应的bucket在数组中的索引:如果找到bucket的位置(hash值相同),然后就沿着里面存放的HashMapEntry开始进行链表遍历(通过next),直到找到key相同的那个HashMapEntry,把value的值进行更改替代。如果没有找到bucket的位置,或者找到了但沿链表遍历没找到key相同的元素,证明现在要put的这个数据的key之前没有出现过,那么就判断添加一个HashMapEntry后大小会不会超过threshold,如果超了就先调用doubleCapacity()进行2倍扩容,最后调用addNewEntry: 这里创建了一个新的HashMapEntry,并把之前这里存储的HashMapEntry作为它的next元素。get方法基本同理这里就不在赘述了
HashMap的数据结构基于数组和链表。用数组存储HashMapEntry元素,当调用put方法去存储数据时,对key调用hashCode()并可能再做进一步加工,得到一个hash值,通过hash值可以找到bucket的位置,如果bucket位置已经有其他元素了(即hash值相同),那么就通过链表结构把hash相同的元素放到链表的下一个节点;当调用get方法去获取数据时,找到bucket以后,会通过key的equals方法在链表中找到目标元素。这里需要注意hashCode()和equals()方法的区别,它们均需保证计算得到的值在插入HashMap后不会发生改变;并需尽可能保证两个不同元素的hashCode方法返回值不同,这样碰撞的几率会小,从而提高HashMap的性能;
当HashMap中已经填充了超过3/4的bucket时,会发生rehash,即会创建原来大小两倍的bucket数组,并将原来的元素放入新的bucket数组中。这里的3/4指的是装填因子(load factor),用户可以自行指定,默认是0.75。增大装填因子可以减少 Hash表(Entry 数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap 的 get() 与 put() 方法都要用到查询);减小装填因子会提高数据查询的性能,但会增加 Hash 表所占用的内存空间。
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
多线程并发引发put失败
1.Hashtable替换HashMap
2.Collections.synchronizedMap将HashMap包装起来
Map m = Collections.synchronizedMap(new HashMap());
synchronized(m) {
......
}
3.ConcurrentHashMap替换HashMap
HashMap的底层工作原理和并发问题
https://blog.csdn.net/whsdu929/article/details/52589001
二、java高级特性
2.1ConcurrentHashMap的数据结构,底层原理,put和get是否线程安全
ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作
ConcurrentHashMap的锁分段技术
HashMap采用链地址法解决哈希冲突,多线程访问哈希表的位置并修改映射关系的时候,后执行的线程会覆盖先执行线程的修改,所以不是线程安全的,多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%。HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁住容器中的一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
get方法无需加锁,由于其中涉及到的共享变量都使用volatile修饰,volatile可以保证内存可见性,所以不会读取到过期数据。来看下concurrentHashMap代理到Segment上的put方法,Segment中的put方法是要加锁的。只不过是锁粒度细了而已。
ConcurrentHashMap源码分析底层实现原理
https://blog.csdn.net/Mr_Chenjie_C/article/details/79678806
2.3基本运算符
与运算符& 两个操作数中位都为1,结果才为1,否则结果为0
或运算符| 两个位只要有一个为1,那么结果就是1,否则就为0
非运算符~ 如果位为0,结果是1,如果位为1,结果是0
异或运算符^ 两个操作数的位中,相同则结果为0,不同则结果为1
2.4Java线程池的构造方法,里面参数的含义,以及原理
public ThreadPoolExecutor(
int corePoolSize, //核心线程的数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //超出核心线程数量以外的线程空余存活时间
TimeUnit unit, //存活时间的单位
BlockingQueue<Runnable> workQueue, //保存待执行任务的队列
ThreadFactory threadFactory, //创建新线程使用的工厂
RejectedExecutionHandler handler // 当任务无法执行时的处理器
) {...}
线程比作员工,线程池比作一个团队,核心池比作团队中核心团队员工数,核心池外的比作外包员工)
有了新需求,先看核心员工数量超没超出最大核心员工数,还有名额的话就新招一个核心员工来做
需要获取全局锁
核心员工已经最多了,HR 不给批 HC 了,那这个需求只好攒着,放到待完成任务列表吧
如果列表已经堆满了,核心员工基本没机会搞完这么多任务了,那就找个外包吧
需要获取全局锁
如果核心员工 + 外包员工的数量已经是团队最多能承受人数了,没办法,这个需求接不了了
然后根据处理的任务类型选择不同的阻塞队列
如果是要求高吞吐量的,可以使用 SynchronousQueue 队列;如果对执行顺序有要求,可以使用 PriorityBlockingQueue;如果最大积攒的待做任务有上限,可以使用 LinkedBlockingQueue。
CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。
FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。
SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。
ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
2.5volatile和ThreadLocal解决了什么问题
1、ThreadLocal是用于解决多线程共享类的成员变量,原理:在每个线程中都存有一个本地ThreadMap,相当于存了一个对象的副本,key为threadlocal对象本身,value为需要存储的对象值,这样各个线程之间对于某个成员变量都有自己的副本,不会冲突。
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。
ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源
原理
ThreadLocal,连接ThreadLocalMap和Thread。来处理Thread的TheadLocalMap属性,包括init初始化属性赋值、get对应的变量,set设置变量等。通过当前线程,获取线程上的ThreadLocalMap属性,对数据进行get、set等操作。
ThreadLocalMap,用来存储数据,采用类似hashmap机制,存储了以threadLocal为key,需要隔离的数据为value的Entry键值对数组结构。
ThreadLocal,有个ThreadLocalMap类型的属性,存储的数据就放在这儿。
ThreadLocal、ThreadLocal、Thread之间的关系
ThreadLocalMap是ThreadLocal内部类,由ThreadLocal创建,Thread有ThreadLocal.ThreadLocalMap类型的属性
ThreadLocal和线程同步机制相比有什么优势呢?
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
数据库连接connection线程共享
不同的线程在使用TopicDao时,根据之前的深挖get具体操作,判断connThreadLocal.get()会去判断是有map,没有则根据initivalValue创建一个Connection对象并添加到本地线程变量中,initivalValue对应的值也就是上述的lamba表达式对应的创建connection的方法返回的结果,下次get则由于已经有了,则会直接获取已经创建好的Connection,这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。
ThreadLoacal内存泄漏
每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal.当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以 threadlocal将会被gc回收. 但是,value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回
线程隔离的秘密
秘密就就在于上述叙述的ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了
2、volatile保证了成员变量在多线程下的可见性和有序性,不保证原子性,java中原子性的操作为读取一个值或者给一个变量赋值(例如 i=2)
3、线程在执行完一行代码,例如修改了一个变量的值,因为工作内存速度小于主存中高速CPU的速度,所以对于其他的线程而言,读取到该变量的值可能不是最新的值。
4、使用volatile关键字的时候,该变量一旦被修改,会立即写入到主存中,同时会让其他线程的工作内存中的缓存失效,这样,其他线程在访问该变量的时候会重新从主存中读取可以获得该变量最新的数据,从而保证的变量的可见性。
2.6CAS在Java中的具体实现
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。是一条CPU的原子指令
存在问题
ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
2.7Java虚拟机的构成,以及一个Java对象的生命周期,还有堆栈和方法区中存储的内容
JVM结构原理、GC工作机制详解
https://blog.csdn.net/moneyshi/article/details/53033577
2.8触发垃圾收集的条件
JVM性能调优
https://blog.csdn.net/rodesad/article/details/51545109
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。
次收集ScavengeGC
一般情况下,当Enden区对象已满,或当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
对Survivor区,当达到设置的预定值(默认50%),则进行一次垃圾次收集。
全收集FullGC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
·年老代(Tenured)被写满
·持久代(Perm)被写满
· System.gc()被显示调用
·上一次GC之后Heap的各域分配策略动态变化
2.9重写equals方法必须重写hashCode方法
hashCode()和equals()保持一致,如果equals方法返回true,那么两个对象的hasCode()返回值必须一样。如果equals方法返回false,hashcode可以不一样,但是这样不利于哈希表的性能,一般我们也不要这样做。重写equals()方法就必须重写hashCode()方法的原因也就显而易见了。
假设两个对象,重写了其equals方法,其相等条件是属性相等,就返回true。如果不重写hashcode方法,其返回的依然是两个对象的内存地址值,必然不相等。这就出现了equals方法相等,但是hashcode不相等的情况。这不符合hashcode的规则。下边,会介绍在集合框架中,这种情况会导致的严重问题。
二、框架
1.struts1和struts2的区别
2.struts2和springMVC的区别
3.spring框架中需要引用哪些jar包,以及这些jar包的用途
4.springMVC的原理
5.springMVC注解的意思
6.spring中beanFactory和ApplicationContext的联系和区别
7.spring注入的几种方式
8.spring如何实现事物管理的
9.springIOC和AOP的原理
10.hibernate中的1级和2级缓存的使用方式以及区别原理
11.spring中循环注入的方式
三、多线程
3.1.Java创建线程之后直接调用start()方法和run()的区别
start方法:
通过该方法启动线程的同时也创建了一个线程,真正实现了多线程。无需等待run()方法中的代码执行完毕,就可以接着执行下面的代码。此时start()的这个线程处于就绪状态,当得到CPU的时间片后就会执行其中的run()方法。这个run()方法包含了要执行的这个线程的内容,run()方法运行结束,此线程也就终止了。
run方法:
通过run方法启动线程其实就是调用一个类中的方法,当作普通的方法的方式调用。并没有创建一个线程,程序中依旧只有一个主线程,必须等到run()方法里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。
3.2.常用的线程池模式以及不同线程池的使用场景
newCachedThreadPool:
底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器
newFixedThreadPool:
底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多
newSingleThreadExecutor:
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景
NewScheduledThreadPool:
底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景
线程池任务执行流程:
当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
3.3.newFixedThreadPool底层原理
说道Java线程池就不得不说ExecutorService接口和Executors类了,从源码上来看Executors类里面封装了线程池的创建,并且定义了各自不同的线程池类型,本文着重讲Executors这个类的newFixedThreadPool方法返回ThreadPoolExecutor
ThreadPoolExecutor的execute从该方法可以看出它的步骤是,如果当前线程数小于指定的最大数量则创建新的线程执行任务,否则加入到缓冲队列workQueue,然后我们来看下它的执行操作
最终是把需要执行的线程放到一个工作线程workers HashSet里面。这里的work与Thread是分离的,这样做的好处是,如果我们的业务代码,需要对于线程池中的线程,赋予优先级、线程名称、线程执行策略等其他控制时,可以实现自己的ThreadFactory进行扩展,无需继承或改写ThreadPoolExecutor。
里面执行的是run方法,很明显我们知道即使创建了线程要是不使用start方法执行的话就只能算是一般的方法执行,我们看到这里就应该思考,这个线程什么时候结束呢?下面我们看下它的getTask方法实现
最终会由 workQueue.take();阻塞,所以当前的线程永远不会退出。
3.4java中synchronized和lock底层原理
synchronized的原理表示:
在java语言中存在两种内建的synchronized语法:1、synchronized语句;2、synchronized方法。对于synchronized语句当Java源代码被javac编译成bytecode的时候,会在同步块的入口位置和退出位置分别插入monitorenter和monitorexit字节码指令。而synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。synchronized采用的便是这种并发策略。
Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制。本质上Lock仅仅是一个接口
Lock有三个实现类,一个是ReentrantLock,另两个是ReentrantReadWriteLock类中的两个静态内部类ReadLock和WriteLock。
使用方法:多线程下访问(互斥)共享资源时, 访问前加锁,访问结束以后解锁,解锁的操作推荐放入finally块中。
在乐观的并发策略中,需要操作和冲突检测这两个步骤具备原子性,它靠硬件指令来保证,这里用的是CAS操作(Compare and Swap)。JDK1.5之后,Java程序才可以使用CAS操作。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState,这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起。
用途比较:
基本语法上,ReentrantLock与synchronized很相似,它们都具备一样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),一个表现为原生语法层面的互斥锁(synchronized)。ReentrantLock相对synchronized而言还是增加了一些高级功能,主要有以下三项:
1、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常上的同步块很有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。
2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。
3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无需这么做,只需要多次调用newCondition()方法即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一起的其他线程)。
Java源文件使用javac编译生成*.class文件,也就是字节码文件,再由各个平台的字节码解释执行
3.5sleep和wait区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
四、网络编程
4.1TCP/IP协议和同步异步
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
一个IP包除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。
端口作用:在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个IP包来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。
同步和异步:同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。
以银行取款为例:
同步 : 自己亲自出马持银行卡到银行取钱(使用同步 IO 时,Java 自己处理IO 读写);
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO 时,Java 将 IO 读写委托给OS 处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS 需要支持异步IO操作API);
阻塞和非阻塞:阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。
以银行取款为例:
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器通知可读写时再继续进行读写,不断循环直到读写完成)
4.2HTTPS和HTTP的区别
工作层:在OSI网络模型中,HTTP工作于应用层,而HTTPS工作在传输层。
连接端口:HTTP标准端口是80,而HTTPS的标准端口是443。
传输方式:HTTP是超文本传输协议,信息是明文传输,而HTTPS是SSL加密传输协议。
工作耗时:HTTP耗时=TCP握手,而HTTPS耗时=TCP握手+SSL握手。
显示形式:HTTP的URL以http://开头,而HTTPS的URL以https://开头。
费用:HTTP无需费用,而HTTPS需要到CA申请证书,一般免费证书较少,需要一定费用。
安全性:HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。
4.3http的请求过程
Http的header会给我们的请求包装,比如在请求设置中的Accept(xml,text,xhtml,html)
域名解析,根据域名找到服务器的IP
发起TCP的三次握手
建立TCP连接后发起http请求
服务器响应http请求,浏览器得到html代码
浏览器解析html代码,并请求html代码中的资源(如js,css,.jpg等)
浏览器对页面进行渲染呈现给用户
4.4TCP的三次握手四次挥手
TCP握手协议
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
SYN:同步序列编号(Synchronize Sequence Numbers)
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手.
完成三次握手,客户端与服务器开始传送数据
A与B建立TCP连接时:首先A向B发SYN(同步请求),然后B回复SYN
第一步:client 发送 syn 到server 发起握手;
第二步:server 收到 syn后回复syn+ack给client;
第三步:client 收到syn+ack后,回复server一个ack表示收到了server的syn+ack(此时client的56911端口的连接已经是established)。
TCP客户端主动关闭连接四次挥手
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
4.5网络基础知识
a两台计算机间进行通讯需要以下三个条件:
IP地址、协议、端口号
B TCP/IP协议:
是目前世界上应用最为广泛的协议,是以TCP和IP为基础的不同层次上多个协议的集合,也成TCP/IP协议族、或TCP/IP协议栈
TCP:Transmission Control Protocol 传输控制协议
IP:Internet Protocol 互联网协议
C TCP/IP五层模型
应用层:HTTP、FTP、SMTP、Telnet等
传输层:TCP/IP
网络层:
数据链路层:
物理层:网线、双绞线、网卡等
D IP地址
为实现网络中不同计算机之间的通信,每台计算机都必须有一个唯一的标识---IP地址。
32位二进制
E 端口
区分一台主机的多个不同应用程序,端口号范围为0-65535,其中0-1023位为系统保留。
如:HTTP:80 FTP:21 Telnet:23
IP地址+端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础
F Socket套接字:
网络上具有唯一标识的IP地址和端口组合在一起才能构成唯一能识别的标识符套接字。
Socket原理机制:
通信的两端都有Socket
网络通信其实就是Socket间的通信
数据在两个Socket间通过IO传输
G Java中的网络支持
针对网络通信的不同层次,Java提供了不同的API,其提供的网络功能有四大类:
InetAddress:用于标识网络上的硬件资源,主要是IP地址
URL:统一资源定位符,通过URL可以直接读取或写入网络上的数据
Sockets:使用TCP协议实现的网络通信Socket相关的类
Datagram:使用UDP协议,将数据保存在用户数据报中,通过网络进行通信。
4.5TCP编程
1、TCP协议是面向连接的、可靠的、有序的、以字节流的方式发送数据,通过三次握手方式建立连接,形成传输数据的通道,在连接中进行大量数据的传输,效率会稍低
2、Java中基于TCP协议实现网络通信的类
客户端的Socket类
服务器端的ServerSocket类
3、Socket通信的步骤
① 创建ServerSocket和Socket
② 打开连接到Socket的输入/输出流
③ 按照协议对Socket进行读/写操作
④ 关闭输入输出流、关闭Socket
4、服务器端:
① 创建ServerSocket对象,绑定监听端口
② 通过accept()方法监听客户端请求
③ 连接建立后,通过输入流读取客户端发送的请求信息
④ 通过输出流向客户端发送乡音信息
⑤ 关闭相关资源
5、 客户端:
① 创建Socket对象,指明需要连接的服务器的地址和端口号
② 连接建立后,通过输出流想服务器端发送请求信息
③ 通过输入流获取服务器响应的信息
④ 关闭响应资源
6、应用多线程实现服务器与多客户端之间的通信
① 服务器端创建ServerSocket,循环调用accept()等待客户端连接
② 客户端创建一个socket并请求和服务器端连接
③ 服务器端接受苦读段请求,创建socket与该客户建立专线连接
④ 建立连接的两个socket在一个单独的线程上对话
⑤ 服务器端继续等待新的连接
4.6 BIO、NIO和AIO的区别
BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。
伪异步IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源。
NIO:一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
BIO是面向流的,NIO是面向缓冲区的;BIO的各种流是阻塞的。而NIO是非阻塞的;BIO的Stream是单向的,而NIO的channel是双向的。
NIO的特点:事件驱动模型、单线程处理多任务、非阻塞I/O,I/O读写不再阻塞,而是返回0、基于block的传输比基于流的传输更高效、更高级的IO函数zero-copy、IO多路复用大大提高了Java网络应用的可伸缩性和实用性。基于Reactor线程模型。
在Reactor模式中,事件分发器等待某个事件或者可应用或个操作的状态发生,事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。如在Reactor中实现读:注册读就绪事件和相应的事件处理器、事件分发器等待事件、事件到来,激活分发器,分发器调用事件对应的处理器、事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权
NIO的服务端建立过程:Selector.open():打开一个Selector;ServerSocketChannel.open():创建服务端的Channel;bind():绑定到某个端口上。并配置非阻塞模式;register():注册Channel和关注的事件到Selector上;select()轮询拿到已经就绪的事件
4.7Netty的特点
心跳,对服务端:会定时清除闲置会话inactive(netty5),对客户端:用来检测会话是否断开,是否重来,检测网络延迟,其中idleStateHandler类 用来检测会话状态
串行无锁化设计,即消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。表面上看,串行化设计似乎CPU利用率不高,并发程度不够。但是,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。
可靠性,链路有效性检测:链路空闲检测机制,读/写空闲超时机制;内存保护机制:通过内存池重用ByteBuf;ByteBuf的解码保护;优雅停机:不再接收新消息、退出前的预处理操作、资源的释放操作。
Netty安全性:支持的安全协议:SSL V2和V3,TLS,SSL单向认证、双向认证和第三方CA认证。
高效并发编程的体现:volatile的大量、正确使用;CAS和原子类的广泛使用;线程安全容器的使用;通过读写锁提升并发性能。IO通信性能三原则:传输(AIO)、协议(Http)、线程(主从多线程)
流量整型的作用(变压器):防止由于上下游网元性能不均衡导致下游网元被压垮,业务流中断;防止由于通信模块接受消息过快,后端业务线程处理不及时导致撑死问题。
TCP参数配置:SO_RCVBUF和SO_SNDBUF:通常建议值为128K或者256K;SO_TCPNODELAY:NAGLE算法通过将缓冲区内的小封包自动相连,组成较大的封包,阻止大量小封包的发送阻塞网络,从而提高网络应用效率。但是对于时延敏感的应用场景需要关闭该优化算法;
Netty实现原理和实现
https://blog.csdn.net/Alex_81D/article/details/79729211
Netty模型
Reactor主从模型
是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接;并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。
4.7TCP 粘包/拆包的原因及解决方法
TCP是以流的方式来处理数据,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。
TCP粘包/分包的原因:
应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包现
进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包
以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。
解决方法
消息定长:FixedLengthFrameDecoder类
包尾增加特殊字符分割:行分隔符类:LineBasedFrameDecoder或自定义分隔符类 :DelimiterBasedFrameDecoder
将消息分为消息头和消息体:LengthFieldBasedFrameDecoder类。分为有头部的拆包与粘包、长度字段在前且有头部的拆包与粘包、多扩展头部的拆包与粘包
4.8nginx的反向代理及其作用
提高访问速度
由于目标主机返回的数据会存放在代理服务器的硬盘中,因此下一次客户再访问相同的站点数据时,会直接从代理服务器的硬盘中读取,起到了缓存的作用,尤其对于热门站点能明显提高请求速度。
防火墙作用
由于所有的客户机请求都必须通过代理服务器访问远程站点,因此可在代理服务器上设限,过滤某些不安全信息。
通过代理服务器访问不能访问的目标站点
互联网上有许多开发的代理服务器,客户机在访问受限时,可通过不受限的代理服务器访问目标站点,通俗说,我们使用的翻墙浏览器就是利用了代理服务器,虽然不能出国,但也可直接访问外网。
反向代理有哪些主要应用?
现在许多大型web网站都用到反向代理。除了可以防止外网对内网服务器的恶性攻击、缓存以减少服务器的压力和访问安全控制之外,还可以进行负载均衡,将用户请求分配给多个服务器。
4.9nginx负载均衡的五种算法
1轮询是默认的,每一个请求按顺序逐一分配到不同的后端服务器,如果后端服务器down掉了,则能自动剔除。
2weight是设置权重,用于后端服务器性能不均的情况,访问比率约等于权重之比
3ip_hash 解决了session问题:每个请求按访问IP的hash结果分配,这样每个访客可以固定一个后端服务器。
4fair (第三方)按后端服务器的响应时间来分配请求,响应时间短的优先分配。
5url_hash (第三方) 按访问URL的hash结果来分配请求,使每个URL定向到同一个后端服务器,后端服务器为缓存时比较适用。另外,在upstream中加入hash语句后,server语句不能写入weight等其他参数。
五、新技术
核心新技术怎么用?新技术解决了什么问题?自身会有什么缺陷?
5.1springboot
反射
Foo的实例对象表示
Foo foo1 = new Foo()
Foo这个类也是一个实例对象,Class类的实例对象
任何一个一个类都是Class的实例对象,这个实例对象有三种表达方式
第一种表达方式 实际告诉我们任何类都有一个隐含的静态的成员变量class
Class c1 = Foo.class
第二种表达方式 已经知道该类的对象通过getClass(方法)
Class c2 = foo1.getClass()
官网c1,c2表示了Foo类的类类型(class type),foo1是Foo类的实例对象
第三种表达方式
Class c3 = null
c3 = Class.forName(“com.imooc.refelect.Foo”)
c1 == c2 ==c3
//通过类的类类型创建类的实例对象 通过c1 c2 c3创建foo1对象
Foo foo = (Foo)c1.newInstance()//需要有无参的构造方法
Java动态加载
成员变量是java.lang.relfelect.Field的对象
java.lang.reflect.Field Field类封装了关于成员变量的操作
Field c.getDeclaredFields(); 获取的是该类自己声明的成员变量的信息
构造方法是 java.lang.Constructor 类的对象
方法反射对象 调用对象的方法
获取方法print(String,String)
Method m1 = c.getMethod(“print”,String.class,String.class)
Object o = m1.invoke(a1,”hello”,”world”)
反射的操作是编译之后的操作
编译之后集合的泛型是去泛型化的
Java中的集合的泛型是防止错误输入,只在编译阶段有效
List<String> list = new ArrayList<String>()
Class c1 = list.getClass();
Method m = c1.getMethod(“add”,Object.class);
m.invoke(add,20);
成功加入20,说明我们可以通过方法的反射来操作,饶过编译。
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
SpringMVC的实现过程
①:DispatcherServlet是springmvc中的前端控制器(front controller),负责接收request并将request转发给对应的处理组件.
②:HanlerMapping是springmvc中完成url到controller映射的组件.DispatcherServlet接收request,然后从HandlerMapping查找处理request的controller.
③:Cntroller处理request,并返回ModelAndView对象,Controller是springmvc中负责处理request的组件(类似于struts2中的Action),ModelAndView是封装结果视图的组件.
④ ⑤ ⑥:视图解析器解析ModelAndView对象并返回对应的视图给客户端.
Servlet
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
典型的 Servlet 生命周期方案。
第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。
Servlet 容器在调用 service() 方法之前加载 Servlet。
然后 Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法
Servlet 生命周期
Servlet 通过调用 init () 方法进行初始化。
Servlet 调用 service() 方法来处理客户端的请求。
Servlet 通过调用 destroy() 方法终止(结束)。
最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的
解决Servlet单例线程不安全问题.
尽量使用局部变量
使用全局变量会出现线程安全问题,所以我们可以尽量使用局部变量.在Servlet中,负责保存上下文ServletContext和负责处理Session对象的HttpSession是线程不安全的,而负责处理请求的ServletRequest是线程安全的.
加锁
用synchronized进行保护,但是要尽量的缩小保护范围.
ThreadLocal
ThreadLocal为每一个线程提供一个变量副本,线程之前该变量是独立的.
Spring源码剖析——核心IOC容器原理
所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。
所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
Map的key就是Bean 的 Id ,Map 的value是这个Bean
依赖注入
其实依赖注入的思想也很简单,它是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。再通过类的类类型实现方法反射对象,将对象返回给调用方。
构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
通过上面的接口设计图我们可以看到,在Spring当中最基本的IOC容器接口是BeanFactory,这个接口当中定义作为一个IOC容器最基本的一些操作和功能
创建Ioc配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息
创建一个BeanFactory,这里使用了DefaultListableBeanFactory
创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition
然后将上面定位好的Resource,通过一个回调配置给BeanFactory
从定位好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader完成
完成整个载入和注册Bean定义之后,需要的Ioc容器就初步建立起来了
5.2springcloud
Spring Cloud的架构图
1、不管是什么客户端或者内部的非springcloud项目都统一通过API网关(Zuul)来访问内部服务;
2、网关接收到请求后,从注册中心(Eureka)获取可用服务;
3、由Ribbon进行均衡负载后,分发到后端的具体实例;
4、微服务之间通过Feign进行通信处理业务;
5、Hystrix负责处理服务超时熔断;
6、Turbine监控服务间的调用和熔断相关指标
7、Spring Cloud Config是一个解决分布式系统的配置管理方案
8、Refresh方案虽然可以解决单个微服务运行期间重载配置信息,Spring Cloud Bus多服务更新配置
Spring Cloud各个组件相互配合,合作支持了一套完整的微服务架构。
其中Eureka负责服务的注册与发现,很好将各服务连接起来
Hystrix 负责监控服务之间的调用情况,连续多次失败进行熔断保护。
Hystrix dashboard,Turbine 负责监控 Hystrix的熔断情况,并给予图形化的展示
Spring Cloud Config 提供了统一的配置中心服务
当配置文件发生变化的时候,Spring Cloud Bus 负责通知各服务去获取最新的配置信息
所有对外的请求和服务,我们都通过Zuul来进行转发,起到API网关的作用
最后我们使用Sleuth+Zipkin将所有的请求数据记录下来,方便我们进行后续分析
SpringCloud解决的问题
SpringCloud是基于SpringBoot之上的用来快速构建微服务系统的工具集,拥有功能完善的轻量级微服务组件,例如服务治理(Eureka),声明式REST调用(Feign),客户端负载均衡(Ribbon),服务容错(Hystrix),服务网关(Zuul)以及服务配置(Spring Cloud Config),服务跟踪(Sleuth)等等。
5.3dubbo+zookeeper
Dubbo架构
节点角色说明:
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。
调用关系说明:
0. 服务容器负责启动,加载,运行服务提供者。
1. 服务提供者在启动时,向注册中心注册自己提供的服务。
2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
zookeeper用来注册服务和进行负载均衡,哪一个服务由哪一个机器来提供必需让调用者知道,简单来说就是ip地址和服务名称的对应关系。zookeeper通过心跳机制可以检测挂掉的机器并将挂掉机器的ip和服务对应关系从列表中删除。
ZooKeeper可以简单启动奇数个进程,来形成一个小的目录服务集群。这个集群会提供给所有其他进程,进行读写其巨大的“配置树”的能力。这些数据不仅仅会存放在一个ZooKeeper进程中,而是会根据一套非常安全的算法,让多个进程来承载。这让ZooKeeper成为一个优秀的分布式数据保存系统。
由于ZooKeeper的数据存储结构,是一个类似文件目录的树状系统,所以我们常常会利用它的功能,把每个进程都绑定到其中一个“分枝”上,然后通过检查这些“分支”,来进行服务器请求的转发,就能简单的解决请求路由(由谁去做)的问题。另外还可以在这些“分支”上标记进程的负载的状态,这样负载均衡也很容易做了
分布式系统所依赖的基础设施包括服务框架、消息中间件、数据访问中间件、配置中心、分布式缓存系统、持久化存储(关系数据库、nosql数据库)、搜索引擎、CDN网络、负载均衡系统、运维自动化系统、硬件虚拟化及镜像管理系统、分布式文件系统、日志收集系统、监控系统、离线计算、实时计算、数据仓库
5.4docker
Docker组成部分:
Docker Client 客户端
Docker daemon 守护进程
Docker Image 镜像
Docker Container 容器
Docker Registry 仓库
客户端和守护进程:
Docker是C/S(客户端client-服务器server)架构模式。
docker通过客户端连接守护进程,通过命令向守护进程发出请求,守护进程通过一系列的操作返回结果。
docker客户端可以连接本地或者远程的守护进程。
docker客户端和服务器通过socket或RESTful API进行通信。
docker image 镜像
镜像是容器的基石,容器基于镜像启动和运行。镜像就好像容器的源代码,保存了容器各种启动的条件。镜像是一个层叠的只读文件系统。
docker container 容器
容器通过镜像来启动,容器是docker的执行来源,可以执行一个或多个进程。镜像相当于构建和打包阶段,容器相当于启动和执行阶段。每一个Docker容器都是独立和安全的应用平台。
docker registry 仓库
docker仓库用来保存镜像。
docker解决了什么问题
快速构建自动化环境,解决了运行环境和配置问题,方便做持续集成
隔离性,更轻量的虚拟化,节省了虚拟机的性能损耗
5.5RabbitMQ
AMQP协议模型
RabbitMQ的整体架构
RabbitMQ核心概念
Server:又称Broker,接受客户端的连接,实现AMQP实体服务
Connection:连接,应用程序与Broker的网络连接
Channel:网络信道
几乎所有的操作都在Channel中进行
Channel是进行消息读写的通道
客户端可建立多个Channel
每个Channel代表一个会话任务
Message:消息
服务器和应用程序之间传送的数据,由Properties和Body组成
Properties可以对消息进行修饰,比如消息的优先级、延迟等高级特性
Body则就是消息体内容
Virtual host:虚拟机
用于进行逻辑隔离,最上层的消息路由
一个Virtual host里面可以有若干个Exchange和Queue
同一个Virtual host里面不能有相同名称的Exchange或Queue
Exchange:交换机,接收消息,根据路由键转发消息到绑定的队列
Binding:Exchange和Queue之间的虚拟连接,binding中可以包含routing key
Routing key:一个路由规则,虚拟机可用它来确定如何路由一个特定消息
Queue:也称为Message Queue,消息队列,保存消息并将它们转发给消费者
RabbitMQ消息的流转过程
RabbitMQ解决了什么问题
1.同步变异步
可以使用线程池解决,但是缺点很明显:要自己实现线程池,并且强耦合
大多数是使用消息队列来解决
2.低内聚高耦合:解耦----减少强依赖.
3.流量削峰---秒杀系统
通过消息队列设置请求最大值,超过阀值的抛弃或者转到错误界面.
4.rabbitmq采用信道通信。不采用tcp直接通信
tcp的创建和销毁开销大,创建3次握手,销毁4四次分手
高峰时成千上万条的链接会造成资源的巨大浪费,而且操作系统没秒处理tcp的数量也是有数量限制的,必定造成性能瓶颈
一条线程一条信道,多条线程多条信道,公用一个tcp连接。一条tcp连接可以容纳无限条信道(硬盘容量足够的话),不会造成性能瓶颈。
消息队列
解耦异步削峰
解决问题
系统间耦合性太强,如上图所示,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦
中间件模式的的优点:
将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。
传统模式的缺点:
一些非必要的业务逻辑以同步的方式运行,太耗费时间
中间件模式的的优点:
将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
传统模式的缺点:
并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常
中间件模式的的优点:
系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的
解决消息重复消费
比如,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。
如何保证消费的可靠性传输
生产者丢数据
RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。
transaction机制就是说,发送消息前,开启事物(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。然而缺点就是吞吐量下降了。
按照博主的经验,生产上用confirm模式的居多。一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了.如果rabiitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。处理Ack和Nack的代码如下所示
2)消息队列丢数据
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。
那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步
1、将queue的持久化标识durable设置为true,则代表是一个持久的队列
2、发送消息的时候将deliveryMode=2
这样设置以后,rabbitMQ就算挂了,重启后也能恢复数据
(3)消费者丢数据
消费者丢数据一般是因为采用了自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时rahbitMQ会立即将消息删除,这种情况下如果消费者出现异常而没能处理该消息,就会丢失该消息。
至于解决方案,采用手动确认消息即可。
参考链接 分布式之消息队列精讲
http://www.cnblogs.com/rjzheng/p/8994962.html
5.6 redis
为什么使用redis
使用redis,主要是从两个角度去考虑:性能和并发。当然,redis还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件(如zookpeer等)代替,并不是非要使用redis。因此,这个问题主要从性能和并发两个角度去答。
性能
我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。
并发
在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库
使用redis有什么缺点
主要是四个问题
缓存和数据库双写一致性问题
缓存雪崩问题
缓存击穿问题
缓存的并发竞争问题
单线程的redis为什么这么快
redis内部机制
纯内存操作
单线程操作,避免了频繁的上下文切换
采用了非阻塞I/O多路复用机制
我们的redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
需要说明的是,这个I/O多路复用机制,redis还提供了select、epoll、evport、kqueue等多路复用函数库
redis的数据类型,以及每种数据类型的使用场景
String
最常规的set/get操作,value可以是String也可以是数字。做一些复杂的计数功能的缓存。
hash
这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。
list
使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。
set
因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。另外,参照另一篇《分布式之延时任务方案解析》,该文指出了sorted set可以用来做延时任务。最后一个应用就是可以做范围查找。
redis的过期策略以及内存淘汰机制
redis采用的是定期删除+惰性删除策略
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。
redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力
当服务器发生故障或者意外关机等情况时,redsi会把内存中的数据备份到硬盘中
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步,当前 Redis的应用已经非常广泛,国内像新浪、淘宝,国外像 Flickr、Github等均在使用Redis的缓存服务。
f多样的数据模型
持久化
主从同步
六、数据库
6.1mysql
Mysql默认的事务隔离级别为repeatable_read
解决第一类丢失更新,脏读、不可重复读、第二类丢失更新的问题,但会出幻读。
InnoDB引擎的锁机制
(之所以以InnoDB为主介绍锁,是因为InnoDB支持事务,支持行锁和表锁用的比较多,Myisam不支持事务,只支持表锁)
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
说明:
1)共享锁和排他锁都是行锁,意向锁都是表锁,应用中我们只会使用到共享锁和排他锁,意向锁是mysql内部使用的,不需要用户干预。
2)对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁,事务可以通过以下语句显示给记录集加共享锁或排他锁。
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。
3)InnoDB行锁是通过给索引上的索引项加锁来实现的,因此InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!。
6.2oracle
truncate和delete区别
1)Truncate和delete都可以将数据实体删掉,truncate操作不记录到rollback日志,同时数据不能恢复
2)Truncate是数据定义语言(DDL),delete是数据操作语言(DML)
3)Truncate不能对视图进行操作,delete操作不会腾出表空间的内存
非关系型数据库的优势:1. 性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。2. 可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
关系型数据库的优势:1. 复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。2. 事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然
6.3索引
索引的原理:根据建立索引的字段建立索引表,存放字段值以及对应记录的物理地址,从而在搜索的时候根据字段值搜索索引表的到物理地址直接访问记录。
引入索引虽然提高了查询速度,但本身占用一定的系统存储容量和系统处理时间,需要根据实际情况进行具体的分析.
索引的类型有:B树索引,位图索引,函数索引等
为什么mysql innodb索引是B+树数据结构
B+树只有叶节点存放数据,其余节点用来索引
文件很大,不可能全部存储在内存中,故要存储到磁盘上
B+树除了叶子节点其它节点并不存储数据,节点小,磁盘IO次数就少。B+树所有的Data域在叶子节点,一般来说都会进行一个优化,就是将所有的叶子节点用指针串起来。这样遍历叶子节点就能获得全部数据。
索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数
内容总结
以上是互联网集市为您收集整理的Java高级工程师面试大全全部内容,希望文章能够帮你解决Java高级工程师面试大全所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。