java 后台开发 面试题教程汇总

面试主要考点:

集合:

ArrayList LinkedList Vector 的区别

LinkedList,ArrayList,HashSet HashMap非线程安全

HashTable Vector线程安全

StringBuilder 非线程安全,StringBuffer线程安全

ArrayList Vector 都是数组结构 LinkedList 是基于双向链表结构

ArrayList 查询快,插入慢,查询快是因为有索引所以查询快,插入慢是因为要移动元素的索引位置,还要进行数组复制操作,把添加前索引后面的元素追加到新元素的后面 LinkedList插入快,查询慢,插入快是因为插入时,只需要移动元素的前后两个元素建立关系就可以实现插入操作,查询慢 因为LinkedList中的get方法是按照顺序访问从列表的一端开始检查,直到另外一端,要移动指针

ArrayList数据结构图
在这里插入图片描述

LinkedList数据结构图
在这里插入图片描述

HashMap HashTable 区别

HashMap 底层实现的机制:哈希表 链表散列=数组+单向链表 key value都允许为null hashMap线程不安全 hashMap无序

通过hash(key)方法计算hash值,然后通过indexFor(hash,length)求该key-value对的index索引位置,然后迭代链表,put方法 判断key是否存在链表中,如果不存在,则把这个key-value对插入链表头,如果存在则覆盖之前的value值,get方法迭代链表,返回匹配的key对应的value,找不到则返回null

HashMap数据结构图
在这里插入图片描述

HashTable 线程安全 HashTable key-value都不允许为null 不允许重复,HashTable直接使用key对象的hashCode(),HashMap重新计算hash值

HashSet 底层是用hashMap实现的 key为对象 +一个不变的Object常量, hashSet实现了Set接口 hashSet仅仅存储对象 hashSet比hashMap慢

LinkedHashMap 是有序的 底层使用哈希表和双向链表来保存所有元素

LinkedHashMap数据结构图

这里写图片描述

LinkedHashSet 和HashSet不同之处

ConcurrentHashMap

底层实现:包含了一个Segment数组,Segment类上包含了一个HashEntry的数组,HashEntry包含了key和value以及next指针,HashEntry构成了一个链表

数据结构图:

图3

List Set 区别

List,Set都继承Collection接口,Map不是

List特点:元素有序,可重复,支持for循环遍历,通过数组下标来遍历,也可用迭代器遍历

Set特点:无素无序,不可重复,重复的会被覆盖掉,set只能用迭代,因为无序,无法用下标取得想要的值 (元素在set中的位置是有该元素的hashCode决定的,加入set的object对象必须定义equals()方法)

set 和List对比:

List: 底层数组实现,可以动态增长,查找元素效率高,插入删除效率低,因为会引起其他位置改变。

Set: 检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。

数据结构:
堆 类似一棵树 如:堆排序 在程序运行时,而不是在编译时,申请某个大小的内存空间

栈 就是一个桶先进后出 只能在栈顶做插入和删除操作 LIFO

Queue 队列 先进先出 FIFO 队头做删除,队尾做插入

JVM:

JVM调优

JAVA_OPTS参数说明:

-server 启用jdk的server版

-Xms java虚拟机初始化时的最小内存 默认物理内存的1/64

-Xmx java虚拟机可使用的最大内存 默认物理内存的1/4 最小内存和最大内存可以设置相同,以避免垃圾回收完成重新分配内存大小

-Xmn 年轻代大小 占整个堆内存的3/8

-Xss1m 每个线程的栈大小

-XX:PermSize 内存永久代大小

-XX:MaxPermSize 内存最大永久代大小

-XX:NewSize 设置年轻代初始化大小

-XX:MaxNewSize 设置年轻代最大值

-XX:NewRatio=4 设置年轻代与年老代的比值 年轻代包括一个Eden区和两个Survivor区 from Survivor, to Survivor

-XX:SurvivorRatio=4 设置年轻代中Eden与Suvivor区的大小比值

垃圾回收GC

jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的.
整个JVM内存大小=年轻代大小 (复制回收算法 叫 minor GC) + 年老代大小 (标记-整理回收算法 叫major GC) + 持久代大小

当触发minor GC时,会先把Eden区存活的对象复制到to Survivor区

然后再看from Survivor,如果次数达到年老代的标准,就复制到年老代中,如果没有达到则复制到to Survivor中,如果 to Survivor满了,则复制到年老代中。

然后调换from Survivor 和 to Survivor的名字,保证每次to Survivor都是空的等待对象

判断对象是否存活有两种方式:

有两种方式:
一种是引用计数 ,每一个对象有一个引用计数属性,新增一个引用计数加1,释放一个引用计数减1,计数为0可以回收。但无法解决循环引用的问题
另一种是可达性分析 从GC roots开始向下搜索,当一个对象到GC Roots没有任何引用链相连时,则证明对象是不可用的
判断对象可以回收的情况:
显式的把某个引用置为NULL或指向别的对象
局部引用指向的对象
弱引用关联的对象

垃圾回收的方法:
标记-清除算法 优点减少停顿时间,缺点是会造成内存碎片
复制算法 这种方法不涉及对象的删除,只是把可用的对象从一个地方拷贝到另一个地方,适合大量对象回收的场景,比如新生代的回收。
标记-整理算法 优点可以解决内存碎片问题,但是会增加停顿时间
分代收集思想 是把JVM分成不同的区域,每种区域使用不同的垃圾回收方法

GC策略:

G1收集器:
优点:空间整合 标记-整理算法,可预测停顿 Gegion独立区域概念 分4步:初始标记、Root Region Scanning,Concurrent Marking,重新标记, Copy/Clean up 复制/清除
参数设置:
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #开启
-XX:MaxGCPauseMillis =50 #暂停时间目标
-XX:GCPauseIntervalMillis =200 #暂停间隔目标
-XX:+G1YoungGenSize=512m #年轻代大小
-XX:SurvivorRatio=6 #幸存区比例

CMS收集器:Concurrent Mark Sweep
优点:并发收集、低停顿 保证系统的响应时间,减少垃圾收集时的停顿时间 基于标记-清除算法实现,分4步:初始标记、并发标记,重新标记,并发清除。
缺点:产生大量空间碎片,并发阶段会降低吞吐量
参数设置:
-XX:+UseConcMarkSweepGC 设置年老代为并发收集

-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。

-XX:CMSFullGCsBeforeCompaction=5 设置运行多少次垃圾收集之后对内存空间进行压缩整理

-XX:ParallelGCThreads=n 设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

Parallel收集器:
参数设置:
-XX:+UseParallelGC 设置并行收集器+年老代串行

Parallel Old收集器(java 1.6之后提供):
优点:吞吐量优先并行收集器

参数设置:

-XX:+UseParallelOldGC 设置年老代为并行收集+年老代并行 多线程+“标记-整理算法”

-XX:ParallelGCThreads=20 并行收集时线程数

-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

Serial收集器:

特点:新生代,老年代都串行收集,新生代复制算法,年老代标记-压缩算法 会Stop the world 服务暂停

参数设置:

-XX:+UseSerialGC 设置串行收集器

ParNew收集器:

特点:新生代并行,年老代串行 新生代复制算法,年老代标记-压缩算法

参数设置:

-XX:+UseParNewGC ParNew收集器

-XX:+ParallelGCThreads 限制线程数量

垃圾回收统计信息

-XX:+PrintGC

-XX:+PrintGCDetails

年轻代大小选择

响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

年老代大小选择

响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

并发垃圾收集信息

持久代并发收集次数

传统GC信息

花在年轻代和年老代回收上的时间比例

减少年轻代和年老代花费的时间,一般会提高应用的效率

吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。

minor GC/full GC触发条件 OOM触发条件 降低GC的调优策略?

eden区满了触发 minorGC ,升到老年代的对象大于老年代剩余空间触发full GC, 连续大内存的对象,会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况会触发full GC

GC与非GC时间耗时超过了GCTimeRatio的限制引发OOM

调优通过NewRate控制新生代与老年代的比例,通过MaxTenuringThreshold控制进入老年前生存次数

JVM内存模型:

运行时数据区 包括 (方法区 虚拟机栈 本地方法栈 堆 程序计数器)

堆和栈中存的是什么?

堆里面存放各种对象实例数据:堆最小内存由-Xms指定 最大内存由-Xmx指定

栈里面放的是基本的数据类型和对象的引用,

默认空余的堆内存小于40%时,就会增大,直到-Xmx设置的内存,具体的比例可以由-XX:MinHeapFreeRatio指定

空余的内存大于70%,就会减少内存,直到-Xms设置的内存,具体由-XX:MaxHeapFreeRatio指定

一般建议两者设置一样大,避免JVM在不断调整大小

程序计数器

这里记录了线程执行字节码的行号,在分支,循环,跳转,异常、线程恢复都依赖这个计数器

Perm Space 永久代又称为方法区 Method Area

存放类信息,字段信息,方法信息、其他信息,字符串,static修饰的变量存在方法区, 数据量大也会引起内存溢出:字符串过多。

内存溢出

有哪几种情况会引起内存溢出?

堆满 栈满 方法区运行时常量溢出

内存泄漏
对象可达但不可用,是指程序在申请内存后,无法释放己申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。

ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

如何指定一个线程的栈大小? jvm参数 -Xss 可以设置 ,new Thread()可以设置栈大小。

栈溢出StackOverFlow

一般是在递归情况下会出现此错误。

如何避免内存泄漏和溢出?

1.尽早释放无用对象的引用, 好的办法是使用临时变量的时候,让引用变量退出活动域时自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄漏。

2.程序进行字符串处理时,尽量避免使用String,而应该使用StringBuffer.因为String是不可变的,每一个String对象都会独立占用内存一块区域

3.尽量少用静态变量,因为静态变量是全局的,存在方法区,GC不会回收,(用永久代实现的方法区,垃圾回收行为在这个区域是很少出现的,垃圾回收器的主要目标是针对常量池和类型的卸载)

4.避免集中创建对象,尤其是大对象。JVM会突然需要大量内存,这时会触发GC优化内存环境。

  1. String.substring存在内存泄漏的危险。

  2. 采用新建字符串和String.intern()的方法可以优化直接调用String.substring。

首先选择的是新建字符串。其次才是选择通过intern()方法。intern()方法使用有其局限性。这个只有在从大字符串中截取比较小的子字符串,并且原来的字符串不需要再继续使用的场景下有较好的作用。

JVM性能调优监控工具

terminal 输入 jconsole 会打开java gui 监视和管理控制台分析
jps 显示jvm中运行的进程状态信息 jps -q -m -l -v

jstat 显示各个区内存和gc的情况 jstat -gc pid

jinfo 显示jvm配置信息

jmap 用来查看进程堆内存使用情况(GC算法,堆配置参数,各代中堆内存使用情况): jmap -heap pid

查看堆内存中的对象数量、大小统计 jmap -histo[:live 只查活对象] pid

jmap -dump:format=b,file=/home/dump.dat pid

jstack 显示进程内的线程堆栈信息 jstack -F -l -m pid

jhat 用于分析堆内存文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果

多线程:

一个线程的生命周期图
在这里插入图片描述

三种创建线程的方法:
继承Thread类 实现Runnable接口 通过Callable call()方法和Futrue创建有返回值的线程

线程同步
synchronized称为重量级的锁
对于普通方法同步,锁是当前实例对象
对于静态方法同步,锁是当前类的Class对象
对于方法块同步,锁是Synchronized括号里的对象
同步代码块 非静态方法用 synchronized(this) 静态方法用 synchronized(类.class)进行同步
synchronized 又叫内置锁,其实是通过锁对象的monitor的取用与释放来实现的,互斥锁mutex的机制 ,monitor内置与每一个Object中,synchronized通过Monitor来实现加锁解锁。synchronized 底层是由Monitor先获得许可,然后执行同步代码块,然后释放许可。
Monitor同步机制
为了达到同步,java在一个监视器上(Monitor)的基础上实现了一个巧妙的方案。
监控器是一个控制机制,可以认为是一个很小的,只能容纳一个线程的盒子。一旦一个线程进入监视器,其他线路必须等待,直到那个线程退出监控为止,通过这种方式,一个监控器可以保证共享资源在同一时刻只能被一个线程使用,这种方法称为同步。一旦一个线程进入实例的任何一个同步方法,别的线程不能进入该同一实例的其他同步方法,该实例的其他非同步方法可以被使用。

线程间通信 方法:条件判断wait 生产者通知消费者、消费者通知生产者用notify/notifyAll 这些方法必须放在synchronized块中使用
synchronized,Object.wait(),Object.notify()/Object.notifyAll()实现线程同步时,用到两种机制:线程互斥锁mutex和条件变量condition

DCL失效解决办法
方法一:关于单例模式的DCL机制 Singleton静态变量前加 volatile, 方法内要加 synchronized(Singleton.class)两次 if(instance==null)判断
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
returninstance ;
}
}
方法二:最简单而且安全的解决方法是使用static内部类的思想,它利用的思想是:一个类直到被使用时才被初始化,而类初始化的过程是非并行的,这些都有JLS保证。 如下:
public class Singleton {
private Singleton() {}
private static class InstanceHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InstanceHolder.instance;
}
}

volatile机制

底层是通过内存屏障实现的 比Synchronized轻量,常用场景:状态标记、double check 双重检查
允许线程访问共享变量,为了确保共享变量能够准确和一致地更新,线程应该确保排他锁单独获得这个共享变量,
volatile 保证多线程下的可见线、顺序性、一致性(数据缓存一致性)

happen-before原则

指令重排序会影响多线程的执行正确性,happen-before 可以保证程序的有序性,它规定如果两个操作的执行顺序无法从Happen-before中推导出来,那么它就不能保证有序性,可以随意进行重排序。其定义如下:
1.同一个线程中 前面的操作 happen-before后面的操作
2.监视器monitor上的解锁操作 happen-before其后续的加锁操作,(Synchronized规则)
3.对volatile的写操作happen-before后续的读操作 (volatile规则)
4.线程的start()方法 happen-before 该线程的后续操作 (线程启动规则)
5.线程所有的操作happen-before其他线程在该线程上调用join返回成功后操作
6.如果 a happen-before b, b happen-before c,则 a happen-before c (传递性)

高级多线程控制类:

容器类
CurrentHashMap 原理
CopyOnWriteArrayList 原理
add remove 方法 内部都有重入锁 ReentrantLock

ThreadLocal类
保存线程的独立变量,ThreadLocal为每个使用该变量的线程提供独立的副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程的副本。
线程+ThreadLocalMap 常用于数据库连接和Session管理
原子类
AtomicInteger,AtomicBoolean
Lock类
ReentrantLock
ReentrantReadWriteLock lock 可重入读写锁 写写,写读互斥;读读不互斥,可以实现并发读的高效线程安全代码。
ReadLock r = lock.readLock(); WriteLock w = lock.writeLock();
Lock和synchronized的区别是什么?

lock锁的多条件(condition)阻塞控制。

阻塞队列

大体实现一个阻塞队列?

阻塞队列常用方法:

add 增加一个元素 队列满 则抛出IIIegalSlabExceiption异常

remove 移除并返回队列头部的元素 队列为空 则抛出NoSuchElementException异常

element 返回队列头部的元素 队列为空 则抛出NoSuchElementException异常

offer 添加一个元素并返回true 队列满 则返回false

poll 移除并返回队列头部的元素 队列空,则返回null

peek 返回队列头部的元素 如果队列空,则返回null

put 添加一个元素 队列满 则阻塞

take 取出队列头部的元素 队列空 则阻塞

BlockingQueue 阻塞队列 queue是单向队列,可以在队列头部加入元素和在队列尾部删除或取出元素,先进先出策略。
queue队列

阻塞队列:用于实现生产者,消费者队列。

ArrayBlockingQueue 基于数组实现的有界队列,必须设置容量

LinkedBlockingQueue 基于链表实现的阻塞队列,容量不设置 是一个无边界阻塞队列。

PriorityBlockingQueue 无界阻塞

DelayQueue

SynchronousQueue

ConcurrentHashMap
非阻塞队列
PriorityQueue ConcurrentLinkedQueue

线程池

ThreadPoolExecutor

ThreadPoolExecutor最核心的构造方法参数:

corePoolSize 核心线程池大小

maximumPoolSize 最大线程池大小

keepAliveTime 线程池中超过corePoolSzie数目的空闲线程最大存活时间,

TimeUnit keepAliveTime时间单位

workQueue 阻塞任务队列

threadFactory 新建线程工厂

RejectedExecutionHandler 当提交任务数超过maxnumPoolSize+workQueue之和时,任务会交给RejectedExcecutionHandler来处理

ExecutorService newFixedThreadPool(int nThreads){

return new ThreadPoolExcecutor(nThreads,nThreads,0L,TimeUnit.MILISECONDS, new LinkedBlockingQueue<Runnable>());

}

ExecutorService newCacheThreadPool(){

return new ThreadPoolExcecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILISECONDS, new SynchonousQueue<Runnable>());

}

ExecutorService newSingleThreadExecutor(){

return new ThreadPoolExcecutor(1,1,0L,TimeUnit.MILISECONDS, new LinkedBlockingQueue<Runnable>());

}

定时器线程池: ScheduledExecutorService、大规模定时器TimerWheel

ExecutorCompletionService

实现了CompletionService,将执行完成的任务放到阻塞队列中,通过take/put获得执行结果

用线程池创建n个固定数量的线程,如何保证所有线程执行完,才能执行下下一步操作?

并发流程控制手段:CountDownlatch、用于多个线程,CyclcBarrier 可以重复调用,用于多个线程

线程间如何通信?

线程间的协调手段:lock、condition、wait、notify、notifyAll

synchronized/wait()实现生产者,消费者模式 wait() notifyAll()

lock / condition 实现生产者,消费者模式 new ReentrantLock() lock.newCondition() lock() await() signalAll()

AQS原理:

什么是AQS?

全称AbastractQueuedSynchronizer 抽象队列式同步器

许多同步类的实现都依赖它,如常用的ReentrantLock/ Semaphore / CountDownLatch
在这里插入图片描述

实现是依赖一个volatile int state(共享资源)和一个FIFO的双向队列(多线程争用资源被阻塞时会进入此队列)来完成同步状态的管理

当前线程获取同步状态失败时,同步器AQS会将当前线程和等待状态等信息构造成一个节点加入到同步队列,同时阻塞当前线程。

当同步状态释放的时候,会把首节点的线程唤醒,使首节点的线程再次尝试获取同步状态。

AQS是独占锁和共享锁的实现的父类。

AQS定义两种资源共享方式:

Exclusive 独占锁,锁在一个时间点只能一个线程占有,如ReentrantLock,ReentrantReadWriteLock.writeLock是独占锁,

锁的获取机制可分为:公平锁和非公平锁

Share 共享锁,多个线程可以同时获取的锁,如 ReentrantReadWriteLock.readLock,CyclicBarrier,CountDownLatch,Semapoore

JUC(lock)和Object( Synchronized monitor)机制区别是什么

CAS原理:

什么是CAS?

全称是CompareAndSwap 内存值 预期值和新值比较 当切仅当预期值和内存值相同时,将内存值修改为新值,否则什么都不做

Atomic 实现了CAS算法

CAS算法ABA问题解决:

AtomicStampedReference支持在两个变量上进行原子的条件更新,可以使用该类进行更新操作。

Lock-Free算法的三个组成部分是什么?

使用线程的经验:设置名称、响应中断、使用ThreadLocal

Executor :ExecutorService和Future ThreadPoolExecutor

Lock-free: atomic、concurrentMap.putIfAbsent、CopyOnWriteArrayList

关于锁使用的经验介绍

并发三大定律:Amdahl、Gustafson、Sun-Ni

java 死锁产生原因及如何解锁 ?

锁未释放,解锁方式:超时判断,设置有效期

动态代理原理

spring aop 动态代理模式实现原理?

Spring AOP 面向切面编程原理 动态代理模式实现思想

如果被代理对象是接口形式:

则使用jdk 动态代理机制 Proxy.newProxyInstance() 获取代表对象 ,反射机制 Class.forName() 获取被代理类 InvocationHandler.invoke(target,args) 执行代理对象的切面方法

非接口形式:

则使用cglib动态代理机制 是通过动态生成代理类的子类方式,实现的代理,其中 代理类要继承 MethodInterceptor类中的重写Intercept()方法

调用methodProxy.invokeSuper(o,objects); Enhancer setSuperClass() setCallBack() enhancer.create()

创建代理对象的步骤:

生成代理类的二进制字节码文件

加载二进制字节码,生成Class对象 使用Class.forName()方法

通过反射机制获得实例构造,并创建代理类对象。

设计模式:

常用的设计模式有哪些?开源框架常用的设计模式有哪些?

工厂方法模式,单例模式,适配器模式,包装器模式,代理模式(AOP面向切面编程),观察者模式,策略模式,模板方法模式(HttpServlet)

设计一个商品定时抢购的业务流程图,保障保证用户能正常购买,防止商品被机器人刷走,同时减少对应用服务器的压力?

框架组件:

(spring springmvc redis task tomcat quartz mycat nginx lvs keepalived druid)

如何实现单点登录,以及单点登录各服务对session的管理

分布式开发框架

分布式框架原理 dubbo
容错机制
分布式统一管理 Zookeeper

分布式锁实现方式和原理
分布式锁是为了解决数据一致性问题
分布式的CAP理论:任何一个分布式系统都无法同时满足 一致性、可用性、分区容错性,最多只能同时满足两项
解决方案:
基于缓存实现分布式锁
redis分布式锁 setnx方法

缓存问题

redis缓存失效及热点key解决方案:
缓存击穿的解决方案:
当通过某一个key去查询数据时,如果对应在数据库中的数据都不存在,我们将此key对应的value设置一个默认的值,比如null,并设置一个缓存的失效时间,这时在缓存失效之前,所有通过此key的访问都被挡住了,后面如果此key对应的数据在DB中存在时,缓存失效之后,通过此key再去访问数据库,就能拿到新的value了。
缓存雪崩的解决方案:
保证缓存的高可用性 Redis Sentinel Redis Cluster都实现了高可用
赖隔离组件为后端限流并降级
对重要的资源(如:redids,mysql, hbase,外部接口)都进行隔离,让每种资源都单独运行在自己的线程池中
Hystrix是解决依赖隔离的利器
缓存失效的解决方案:
将系统中key的缓存失效时间均匀地铺开,防止同一时间有大量的key对应的缓存失效。
重新设计缓存的使用方式,当我们通过key去查询数据时,首先查询缓存,如果此时缓存中查不到,就通过分布式锁进行加锁,取得锁的进程查DB并设置缓存,然后解锁,其他进程如果发现有锁就等待,然后等解锁后返回缓存数据或者再次查询DB
redis代码:
String get(String key){
String value = redis.get(key);
if(value==null){
if(redis.setnx(key_mutex,“1”){
redis.expire(key_mutex,3*60)
value = db.get(key);
redis.set(key,value);
redis.delete(key_mutex);
}else{
Thread.sleep(50);
get(key);
}
}
}

热点key的解决方案:

解决方案 优点 缺点
简单分布式锁(Tim yang)

  1. 思路简单

  2. 保证一致性

  3. 代码复杂度增大

  4. 存在死锁的风险

  5. 存在线程池阻塞的风险

加另外一个过期时间(Tim yang) 1. 保证一致性 同上
不过期(本文)

  1. 异步构建缓存,不会阻塞线程池

  2. 不保证一致性。

  3. 代码复杂度增大(每个value都要维护一个timekey)。

  4. 占用一定的内存空间(每个value都要维护一个timekey)。

资源隔离组件hystrix(本文)

  1. hystrix技术成熟,有效保证后端。

  2. hystrix监控强大。

  3. 部分访问存在降级策略。

客户端热点key缓存:将热点key和value缓存到客户端本地,并且设置一个失效时间,对于每次请求首先检查key是否存在与本地缓存中,如果存在则直接返回,如果不存在则访问分布式缓存的机器。
将热点key分散为多个子key,然后存储到缓存集群的不同机器上,这些子key对应的value和热点key是一样的,当通过热点key去查询数据时,通过某种hash算法随机选择一个子key,然后再去访问机器,将热点key分散到多个子key上。
永远不过期:
为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。缺点:构建缓存时,可能访问的还是老数据。
String get(final String key){
V v = redis.get(key);
String value = v.getValue();
long timeout = v.getTimeout();
if(v.timeout <= System.currentTimeMillis()){
threadPool.execute(new Runable(){
public void run(){
String keyMutex = “mutex”+key;
if(redis.setnx(keyMutex,“1”){
redis.expire(keyMutex,3*60);
String dbValue = db.get(key);
redis.set(key,dbValue);
redis.delete(keyMutex);
}
}
});
}
}
缓存预热:

直接写个缓存刷新页面,上线时刷新一下

数据量不大,可以在web启动时加载

定时刷新缓存

缓存更新:

定时去清理过期的缓存

当有用户请求时,先判断缓存是否过期,过期的话就去底层系统获得数据并更新缓存。

分布式消息通信实现机制和原理

kafka RocketMQ ActiveMQ

Spring原理

Spring IOC 控制反转原理

Spring DI 依赖注入原理

Mybatis原理

mybatis配置了xml过后是如何完成数据库操作的?

工作流原理 Activiti5
Shiro工作原理

认证流程:

在这里插入图片描述

授权流程:
在这里插入图片描述

token 携带请求的用户信息

Subject 是shiro管理的用户,

Principal 与用户信息,权限控制相关的信息配置

SecurityManager 安全控制器 管理多个Realm,

Realm 身份验证(登录),授权(访问控制) 与数据库用户,菜单,权限查询

Filter 配置url权限控制。

四种权限方式:

在程序中,通过Subject以编程方式进行权限控制。

通过配置Filter,实现URL级别粗粒度的权限控制 。

通过配置代理,基于注解实现细粒度的权限控制。

在页面中使用shiro自定义标签实现页面显示的权限控制

服务器:

Tomcat调优

Nginx 负载均衡方式:1.轮询 2.随机 3.最小响应时间 4.最小并发数 5.IP哈希

设计一个分页式负载均衡缓冲系统,如何快速定位到哪个服务器 (使用key分段,一致性hash)

如何保证缓冲区和数据库之间的强一致性 (加锁)

Linux下如何查看网络端口状态(netstat)如何查看内存使用情况(top)

tomcat与Nginx的区别

Nginx主要做代理服务器,负载均衡,处理静态资源

反向代理:以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,

Tomcat 是支持运行Servlet/JSP应用程序的容器,支持动态处理http请求

动静分离 运用Nginx的反向代理功能分发请求,所有动态资源请求交给tomcat,而静态资源的请求(图片,视频,css,js)则直接由Nginx返回到浏览器,这样能大大减轻tomcat的压力

负载均衡,当业务压力增大时,一个tomcat并发量有限,可以启动多个tomcat实例进行水平扩展,而Nginx的负载均衡功能可以把请求通过算法分发到各个不同的实例进行处理。

数据库:

Mysql调优

查询大量数据的慢查询问题的优化? 加索引,尽量避免全表扫描,多表联合查询时,优先查询数据量少的表,避免使用or 用union all ,字段值默认值尽量不为null

分库分表 分表可以分为水平切分 和 纵向切分,常用查询字段建索引。尽量使用 exist not exist ,

数据库内存优化配置

如何防止SQL注入机制 ibtis 如何防止SQL注入 前端检验,后台检验,sql语句为预编译模式,用# 尽量不用$

Mysql执行计划:

id:select查询的索引号

select_type: select查询的类型,主要区别:

SIMPLE 查询中不包含子查询和union

查询中若包含任何复杂的子部分,最外层查询被标记为:PRIMARY

在select 和where 列表中包含子查询 该子查询被标记为 SUBQUERY

在from 列表中包含的子查询,被标记为:DERIVED (衍生)

从union获取结果的select 被标记为 UNION RESULT

table:输出所引用的表

type:联合查询所使用的类型 又称访问类型

NULL>system>const>eq_ref>ref>range>index>ALL 一般来说,得保证查询至少达到range级别,最好能达到ref

possible_keys: 指出mysql哪个索引在该表中找到行

key:显示mysql实际决定使用的键,如果没有索引被选择,则是NULL

key_len:表示索引中的字节数 索引长度

ref: 显示哪个字段或常数与key一起被使用

rows:表示mysql要遍历多少数据才能找到

extra:额外信息 only index Using where impossible where Using filesort Using temporary

Redis

五种数据类型

String 字符串 hash 键值对 List set无序集合 zset 有序集合

cache机制

基础:

内部类:

静态内部类:
可以有静态成员(方法,属性),只能够访问外部类的静态成员

实例化一个静态内部类的方法:不依赖外部类的实例,直接实例化内部类对象。

非静态内部类:
不能有静态成员(方法,属性),可以自由访问外部类的所有方法

实例化一个非静态内部类的方法:先生成一个外部类对象实例,通过外部类的对象实例生成内部类对象

定时器类:

Timer TimerTask 有缺陷,是单线程的,一个Timer执行多个timerTask时,当上一任务执行时间间隔超过了两个任务间的间隔,就必须等到上一个完成之后,才能执行下一个。

new Timer().schedule(new TImerTask(){

@Overrride

public voi run(){

    

}

},long delay,long period);

最好使用 ScheduleExecutorService 支持并发执行。

ScheduleExecutorService newScheduledThreadPool = Executors.newScheduleThreadPool(2);

TimerTask task = new TImerTask(){

@Overrride

public voi run(){



}

newScheduledThreadPool.schedule(task,1000,Timunit.MILISECONDS);

NIO原理:

IO:

阻塞 / 非阻塞描述的是函数,指访问某个函数时是否会阻塞线程(block,线程进入阻塞状态)。

同步 / 异步描述的是执行IO操作的主体是谁,同步是由用户进程自己去执行最终的IO操作。异步是用户进程自己不关系实际IO操作的过程,只需要由内核在IO完成后通知它既可,由内核进程来执行最终的IO操作。

这两组概念交集在一起参生的非阻塞同步IO和非阻塞异步IO的概念就不难理解。

非阻塞同步IO指的是用户调用读写方法是不阻塞的,立刻返回的,而且需要用户线程来检查IO状态。需要注意的是,如果发现有可以操作的IO,那么实际用户进程还是会阻塞等待内核复制数据到用户进程,它与同步阻塞IO的区别是后者全程等待。

非阻塞异步IO指的是用户调用读写方法是不阻塞的,立刻返回,而且用户不需要关注读写,只需要提供回调操作,内核线程在完成读写后回调用户提供的callback。

高性能NIO框架 netty实现方式和原理?

普通I/O为阻塞式同步IO
有通道流Chanel和缓冲区Buffer组成 非阻塞式异步I/O

非阻塞的原理:

把整个过程切分成小的任务,通过任务间协作完成。

Reactor 反应器模式 单线程模拟多线程

来处理所有的IO事件,并负责分发。

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

线程通讯:线程间通过wait,notify等方式通讯。

异步IO核心API

Selector 选择器

相当于一个观察者,用来监听通道感兴趣的事件,一个选择器可以绑定多个通道。

它能检测一个或多个通道上的事件,并将事件分发出去。

SelectionKey 包含了事件的状态信息和时间对应的通道的绑定。

类加载机制

类加载过程图
在这里插入图片描述
ClassLoader调用类的顺序结构
在这里插入图片描述

类加载器的双亲委派机制:
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈无法完成这个请求的时候(在它的加载路径下没有找到所需加载的class),子类才会尝试去加载。
类加载器为什么要使用双亲委派机制?
如果不采用此机制,如果用户自定义编写了一个java.lang.Object类,多个类加载器把这个类加载到内存中,则系统中将会出现多个不同的Object类,那么类之间的比较结果及类的唯一性就无法保证,也会给虚拟机带来安全隐患

类初始化顺序:

非继承关系
非继承关系初始化顺序
在这里插入图片描述
继承关系
继承关系初始化顺序

在这里插入图片描述

基础:
运行时异常如果不处理会怎么样?应该怎么处理运行时异常?

空指针异常 数组越界异常 非法参数异常 数字格式化异常

算法:

Hash算法

一致性Hash算法

Spring Cloud 组件介绍:

Spring Cloud技术应用从场景上可以分为两大类:润物无声类和独挑大梁类。

Eureka,服务注册中心,特性有失效剔除、服务保护。

Zuul,API服务网关,功能有路由分发和过滤。

Config,分布式配置中心,支持本地仓库、SVN、Git、Jar包内配置等模式。

Ribbon,客户端负载均衡,特性有区域亲和、重试机制。

Hystrix,客户端容错保护,特性有服务降级、服务熔断、请求缓存、请求合并、依赖隔离。

Feign,声明式服务调用,本质上就是Ribbon+Hystrix。

Stream,消息驱动,有Sink、Source、Processor三种通道,特性有订阅发布、消费组、消息分区。

Bus,消息总线,配合Config仓库修改的一种Stream实现。

Sleuth,分布式服务追踪,需要搞清楚TraceID和SpanID以及抽样,如何与ELK整合。

Dashboard,Hystrix仪表盘,监控集群模式和单点模式,其中集群模式需要收集器Turbine配合。

每个组件都不是平白无故的产生的,是为了解决某一特定的问题而存在。

Eureka和Ribbon,是最基础的组件,一个注册服务,一个消费服务。

Hystrix为了优化Ribbon、防止整个微服务架构因为某个服务节点的问题导致崩溃,是个保险丝的作用。

Dashboard给Hystrix统计和展示用的,而且监控服务节点的整体压力和健康情况。

Turbine是集群收集器,服务于Dashboard的。

Feign是方便我们程序员些更优美的代码的。

Zuul是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,加强安全保护的。

Config是为了解决所有微服务各自维护各自的配置,设置一个统一的配置中心,方便修改配置的。

Bus是因为config修改完配置后各个结点都要refresh才能生效实在太麻烦,所以交给bus来通知服务节点刷新配置的。

Stream是为了简化研发人员对MQ使用的复杂度,弱化MQ的差异性,达到程序和MQ松耦合。

Sleuth+Zipkin是因为单次请求在微服务节点中跳转无法追溯,解决任务链日志追踪问题的。

特殊成员Zipkin,之所以特殊是因为从jar包和包名来看它不属于Spring Cloud的一员,但是它与Spring Cloud Sleuth的抽样日志结合的天衣无缝。乍一看它与Hystrix的Dashboard作用有重叠的部分,但是他们的侧重点完全不同。Dashboard侧重的是单个服务的统计和是否可用,Zipkin侧重的监控环节时长。简言之,Dashboard侧重故障诊断,Ziokin侧重性能优化。

Spring 事务传播机制:

深入理解事务–Spring事务的传播机制

事务传播行为类型

说明

PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

当使用 PROPAGATION_NESTED 时, 底层的数据源必须基于 JDBC 3.0 ,并且实现者需要支持保存点事务机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值