0%

SpringMVC的工作原理

SpringMVC启动流程

  1. Tomcat启动
  2. 解析web.xml listener 创建Spring容器(父容器) 存入servletContext
  3. DispatcherServlet实例化
  4. DispatcherServlet对象.init() 创建Spring容器(子容器) wac.setParent(parent) 完成父子关系绑定
  5. 接收请求

mvc-context-hierarchy

工作流程

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

2.DispatcherServlet收到请求对URL进行解析,调用HandlerMapping处理器映射器

3.处理器映射器找到具体的处理器,生成处理器对象以及处理器拦截器,一并返回给DispatcherServlet

4.DispatcherServlet调用HandlerAdapter处理器适配器

5.HandlerAdapter经过适配调用具体的处理器(Controller)

6.Controller执行完成返回ModelAndView

7.HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet

8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器

9.ViewReslover解析后返回具体View

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

11.DispatcherServlet响应用户

阅读全文 »

AOP

概念

AOP,Aspect Oriented Programming,面向切面编程

AOP的理念:将分散在各个业务逻辑代码中相同的代码通过横向切面的方式,抽取到一个独立的模块中

SpringAOP底层原理就是动态代理

代理的意义:增强对象的行为,使用动态代理实质上就是调用时拦截对象方法对方法进行改造、增强

Spring AOP使用纯JAVA实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标织入增强代码

阅读全文 »

锁的分类

  • 阻塞性
    • 自旋锁
    • 阻塞锁
  • 排他性
    • 共享锁(读锁)
    • 排他锁
  • 可重入锁
  • 公平性
    • 公平锁
    • 非公平锁
  • 中断性
    • 可中断锁
    • 不可中断锁
阅读全文 »

HashMap面试题

HashMap的数据结构?

  • JDK 1.7的数据结构是

    1
    数组 + 链表
  • JDK 1.8的数据结构是

    1
    数组 + 链表 + 红黑树

    其中,桶数组是用来存储数据元素,链表是用来解决冲突,红黑树是为了提高查询的效率。

  • 数据元素通过映射关系,也就是散列函数,映射到桶数组对应索引的位置

  • 如果发生冲突,从冲突的位置拉一个链表,插入冲突的元素

  • 如果链表长度>8&数组大小>=64,链表转为红黑树

  • 如果红黑树节点个数<6 ,转为链表

阅读全文 »

思想

枚举根节点,做可达性分析

  • 根节点
    类加载器、Thread、虚拟机栈的本地变量表、static 成员、常量引用、本地方法栈的变量等等

算法

引用计数法

JVM中没有使用

在创建对象和将对象赋值给某个变量时,将对象的引用计数+1,在移除对象和某个变量的引用关系时,将对象的运营计数-1,当对象的引用计数变为0时,递归地将该对象引用的子对象的引用计数器减1,并把该对象的内存块加入空闲链表中。在通过增减对象的引用计数器来判别活跃对象和非活跃对象,然后在计数器值为0的时候回收对象,这种做法可以在对象不活跃的时候立即回收它。

  • 优点

    • 可以立即回收垃圾。因为每个对象在被引用次数为0的时候,是立即就可以知道的。
    • 没有暂停时间。这个很容易理解,对象的回收根本不需要另外的GC线程专门去做,业务线程自己就搞定了。所以不需要stop the world,当然,在多线程的情况下,必要的同步和互斥操作还是需要的。
  • 缺点

    • 在每次赋值操作的时候都要做相当大的计算,尤其这里面还有递归调用。这是比较麻烦的。
    • 一个致命缺陷是循环引用,就是, objA引用了objB,objB也引用了objA,但是除此之外,再没有其他的地方引用这两个对象了,这两个对象的引用计数就都是1。这种情况下,这两个对象是不能被回收的。
阅读全文 »

参数调优

  • -Xms堆初始值

    默认值:物理内存的1/64(<1GB)

  • -Xmx

    堆最大值,默认值:物理内存的1/4(<1GB)

  • -Xmn

    新生代堆最大可用值,一般设置为整个堆的1/3-1/4

    新生代大小官网推荐的大小是3/8, 如果设置太小,比如1/10会导致Minor GCMajor GC次数增多。

  • -XX+PrintGC

    每次处罚GC的时候打印相关日志

  • -XX:SurvivorRatio

    设置新生代中eden区和from/to空间的比例关系n/1

  • -XX:MaxTenuringThreshold=n

    其中 n 的大小为区间为 [0,15], 如果高于 15,JDK7 会默认 15,JDK 8 会启动报错

阅读全文 »

GC Roots

In Java, there are special objects called Garbage Collection Roots (GC roots). They serve as a root objects for Garbage Collection marking mechanism (see picture).

Classloaders, effectively - via other GC roots.

image

GC Root对象

  • java虚拟机栈引用对象

  • 方法区类的静态成员引用的对象static variables

  • 方法区常量引用的对象

  • java native方法栈中JNI引用对象JNI references

    image

    本地方法就是一个 java 调用非 java 代码的接口,该方法并非 Java 实现的,可能由 C 或 Python等其他语言实现的, Java 通过 JNI 来调用本地方法, 而本地方法是以库文件的形式存放的(在 WINDOWS 平台上是 DLL 文件形式,在 UNIX 机器上是 SO 文件形式)。通过调用本地的库文件的内部方法,使 JAVA 可以实现和本地机器的紧密联系,调用系统级的各接口方法

    当调用 Java 方法时,虚拟机会创建一个栈桢并压入 Java 栈,而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不会在 Java 栈祯中压入新的祯,虚拟机只是简单地动态连接并直接调用指定的本地方法。

  • synchronized锁引用对象

  • 类元

  • JMXBean

来源:

https://rumenz.com/rumenbiji/what-is-gc-roots.html

https://stackoverflow.com/questions/27186799/what-are-gc-roots-for-classes

栈(虚拟机栈VM Stack)

堆是存储的单元(堆只保存对象信息),栈是运行时的单位;在整个JVM的内存之中,栈内存是一个非常重要的的概念;栈里面存储的都是与当前线程相关的信息,包括:局部变量、程序运行状态、方法返回地址等。

虚拟机栈

栈中保存的是一个又一个栈帧

栈帧入栈与出栈

一个栈帧对应一个未运行完的函数;当某一个函数被调用一次时,就会产生一个栈帧(记录着该函数的相关信息),并入栈;当该函数运行完毕之后,其对应的栈帧会出栈。

注:函数的一次调用就会产生一个对应的栈帧,而不是一个函数本身对应一个栈帧;如:递归调用就会产生无数个栈帧。

栈的溢出

从栈的结构可知:如果栈帧数量过多(n多次调用方法)或某个(些)栈帧过大会导致栈溢出引发SOE(Stack Overflow Error)。

注:如果允许虚拟机栈动态扩展,那么当内存不足时,会导致OOM(OutOfMemoryError)

阅读全文 »

J.U.C

J.U.Cjava.util.concurrent的简写,里面提供了很多线程安全的集合。

  • CopyOnWriteArrayList
  • CopyOnWriteArraySet
  • ConcurrentSkipListSet
  • ConcurrentHashMap
  • ConcurrentSkipListMap
  • BlockingQueue
  • BlockingDeque
阅读全文 »

JUC

J.U.Cjava.util.concurrent的简写,里面提供了很多线程安全的集合。

常用工具类

  • atomic(内部原理unSafe. 实现 : 死循环)
    • AtomicInteger
    • AtomicDouble
    • AtomicLong
    • AtomicReference
    • LongAcc
  • lock
    • AQS
    • LOCK
    • Condition
    • LockSupport
      • 两大方法:Park/unPark
      • 暂停线程方式
        • LockSupport.park();
        • Object.wait();
        • Thread.Sleep();
    • ReadWriteLock
    • ReentrantLock
      • 锁对变量加锁后,线程是否可再次执行判断是否可重入
  • 工具
    • CountdownLatch
    • CyclicBarrier
    • Semaphore
    • FutureTask
    • Callable
  • 集合
    • ConcurrentHashMap
    • CopyOnWriteArrayList
    • BlockingQueue
    • BlockingDeque
  • 线程池
    • Executors

线程安全

通过加锁保证数据的一致性.也就是说当一个线程访问某个数据时,通过加锁操作对数据进行保护,其它线程在加锁期间不能对其访问

线程封闭

当多个线程访问共享变量时,需要通过加锁来保证数据同步,增加了程序的复杂性. 避免数据同步的一种方式是不共享变量,比如使用局部变量和ThreadLocal

线程调度

系统为线程分配CUP使用权的过程

  • 协同式线程调度

线程的执行时间由线程自己控制,当自己执行完后,主动通知操作系统切换到另外一个线程上执行. 好处是实现简单,线程对自己的操作是可知道的,没有什么线程同步问题.缺点是线程执行时间不可控,如果一个线程有问题,可能会一致阻塞在那里.

  • 抢占式线程调度

每个线程的执行时间有操作系统分配,线程的切换不由线程本身决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)线程执行时间系统可控,也不会有一个线程导致进程阻塞.

java线程调度就是抢占式调度

可以通过设置线程的优先级让一些线程尽可能的先执行多执行(Java一共有10个线程优先级从Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于ready时,优先级越高越容易被执行.但优先级并不靠谱,因为Java线程时通过映射到原生线程来实现的,所以线程调度还是取决于操作系统.

状态转换

  • 新建(New)创建后尚未启动的线程
  • 运行(Runnable):Runnable包括操作系统中的Running和Ready. 处于此状态的线程有可能在运行,也有可能在等待CPU为它分配执行时间.线程创建后,其它线程调用了该线程的start方法,那么该线程就位于可运行线程池中,变得可运行,就差CPU分配执行时间,其它运行所需要的资源都已经获得.
  • 无限期等待(Waiting):该状态下的线程不会被分配CPU执行时间,要等待被其它线程进行显示唤醒. 如没有设置timeout的Object.wait()方法和Thread.join()方法,以及LockSupport.park()方法
  • 限时等待(Timed Waiting):该状态下的线程不会被分配CPU执行时间,只不过不需要被显示的唤醒,在一定时间后会被系统自动唤醒. 如Thread.sleep(),设置了timeout的Object.wait()和Thread.join(),LockSupport.parkNanos()以及LockSupport.parkUntil()方法
  • 阻塞(Blocked):线程被阻塞了,与等待的区别是:阻塞线程在等待一个排它锁.

阻塞状态是因为某种原因放弃CPU使用权,暂时停止执行,直到线程进入就绪状态,才有机会转到运行状态.

  • 结束(Terminated):线程执行完了或者异常退出了run()方法,该线程结束生命周期

阻塞常见的三种情况

1.等待阻塞(无限期等待):运行的线程执行wait()方法,该线程会释放占用的资源,JVM会把该线程放入等待池.进入这个状态后,线程不会自动唤醒,必须依靠其它线程调用notify()或notifyAll()方法才能会被唤醒.

2.同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其它线程占用,则JVM会把该线程放入锁池.
3.其它阻塞(限时等待):运行的线程执行了join()或者sleep()方法,或者发起了I/O请求,JVM会把该线程置为阻塞状态,当sleep()状态超时,join()等待线程终止或者超时,I/O处理完成,该线程重新转入就绪状态.

同步容器

同步容器通过synchronized关键字修饰容器,保证同一时刻只有一个线程使用容器,从而使容器线程安全. synchronized的意思的同步.

1.Vector和ArrayList都实现了List接口,Vector对数组的操作和ArrayList都一样,区别在于Vector在可能出现线程安全的方法上都加了synchronized关键字修饰.

2.Stack是Vector的子类,Stack实现的是先进后出,在出栈入栈都进行了synchronized修饰.

3.HashTable:它实现了Map接口,操作和HashMap一样(区别:HashTable不能存null,HashMap键值都可以为null),HashTable的所有操作都加了synchronized修饰.

4.Collections提供了线程同步集合类

1
List list=Collections.synchronizedList(new ArrayList());Set set=Collections.synchronizedSet(new HashSet());Map map=Collections.synchronizedMap(new HashMap());

并发容器

并发容器是指允许多线程访问的容器,并保证线程安全.为了尽可能提高并发,Java并发工具包中采用多种优化方式来提高并发容器的执行效率,核心就是锁,CAS(无锁),COW(读写分离),分段锁.

1.CopyOnWriteArrayList

CopyOnWriteArrayList相当于实现了线程安全的ArrayList,在对容器写入时,Copy出一份副本数组,完成操作后把副本数组的引用赋值给容器,底层是通过ReentrantLock来保证同步. 但它通过牺牲容器的一致性来换取容器的并发(在Copy期间读取的还是旧数据),所以不能在强一致的场景下使用.

2.CopyOnWriteArraySet

CopyOnWriteArraySet和CopyOnWriteArrayList的原理一样,它是实现了CopyOnWrite机制的Set集合.

3.ConcurrentHashMap

ConcurrentHashMap相当于实现了线程安全的HashMap,Key是无序的,并且key和value都不能为null,在JDK8之前,采用分段锁的机制来提高并发,只有在操作同一段键值对是才需要加锁.JDK8以后才用CAS算法提高容器的并发.

4.ConcurrentSkipListMap

ConcurrentSkipListMap相当于实现了线程安全的TreeMap,key是有序的,key和value不允许为null,它采用跳跃表的来替代红黑树,原因是红黑树在插入或者删除节点时需要做旋转调整,导致要控制的粒度太大.而跳跃表使用的是链表,利用CAS算法实现高并发线程安全.

5.ConcurrentSkipListSet

ConcurrentSkipListSet和ConcurrentListMap的原理一样,它是实现了线程安全的TreeSet

强一致性

系统中某个数据更新后,后续任何对该数据的读取都将获取到最新的值,在任意时刻,所有节点中的数据是一样的。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。

弱一致性

系统中某个数据被修改后,后续对该数据的读取有可能获得更新之后的值,可能获得更新前的数据,但经过不一致的窗口这段时间,后续对该数据的读取将获得更改之后的值.

最终一致性

是弱一致性的特殊形式,存储系统在保证没有更新的情况下,最总所有对该数据的访问都会得到更新后的数据.不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。

来源:

https://www.bilibili.com/video/BV1G44y1q7qu

https://rumenz.com/rumenbiji/java-Multithreading.html

https://rumenz.com/rumenbiji/java-synchronized-concurrent-container.html