• 面经三


    说一下Spring中bean的加载过程,BeanFactory和FactoryBean有什么区别

    概述

    Spring 作为 Ioc 框架,实现了依赖注入,由一个中心化的 Bean 工厂来负责各个 Bean 的实例化和依赖管理。各个 Bean 可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果。

    我们对 Spring 的工作流进行一个粗略的概括,主要为两大环节:

    • 解析,读 xml 配置,扫描类文件,从配置或者注解中获取 Bean 的定义信息,注册一些扩展功能。
    • 加载,通过解析完的定义信息获取 Bean 实例。我们假设所有的配置和扩展类都已经装载到了 ApplicationContext 中,然后具体的分析一下 Bean 的加载流程。
    • 作用域。单例作用域或者原型作用域,单例的话需要全局实例化一次,原型每次创建都需要重新实例化。
    • 依赖关系。一个 Bean 如果有依赖,我们需要初始化依赖,然后进行关联。如果多个 Bean 之间存在着循环依赖,A 依赖 B,B 依赖 C,C 又依赖 A,需要解这种循环依赖问题。

    2. 总体流程

    • 获取 BeanName,对传入的 name 进行解析,转化为可以从 Map 中获取到 BeanDefinition 的 bean name。
    • 合并 Bean 定义,对父类的定义进行合并和覆盖,如果父类还有父类,会进行递归合并,以获取完整的 Bean 定义信息。
    • 实例化,使用构造或者工厂方法创建 Bean 实例。
    • 属性填充,寻找并且注入依赖,依赖的 Bean 还会递归调用 getBean 方法获取。
    • 初始化,调用自定义的初始化方法。
    • 获取最终的 Bean,如果是 FactoryBean 需要调用 getObject 方法,如果需要类型转换调用 TypeConverter 进行转化。

    BeanFactory与FactoryBean的区别

    BeanFactory 以Factory结尾,表示它是一个工厂类,用于管理Bean的一个工厂

                 在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。

          但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,

                 它的实现与设计模式中的工厂模式和修饰器模式类似。

    Synchronized的底层原理字节码层面如何实现加锁

    应用方式

    synchronized 是解决Java并发最常见的一种方法,也是最简单的一种方法。关键字 synchronized 可以保证在同一时刻,只有一个线程可以访问某个方法或者某个代码块。同时 synchronized 也可以保证一个线程的变化,被另一个线程看到(保证了可见性)
    这里要注意:synchronized是一个互斥的 重量级锁 (细节部分后续会讲)

    synchronized的作用主要有三个:

    1. 确保线程互斥的访问代码
    2. 保证共享变量的修改能够及时可见(可见性)
    3. 可以阻止JVM的指令重排序

    在Java中所有对象都可以作为锁,这是synchronized实现同步的基础。
    synchronized主要有三种应用方式:

    1. 普通同步方法,锁的是当前实例的对象
    2. 静态同步方法,锁的是静态方法所在的类对象
    3. 同步代码块,锁的是括号里的对象。(此处的可以是实例对象,也可以是类的class对象。)

    原理概要

    Java虚拟机中的同步(Synchronization)都是基于进入和退出Monitor对象实现,无论是显示同步(同步代码块)还是隐式同步(同步方法)都是如此。

    • 同步代码块
      monitorenter指令插入到同步代码块的开始位置。monitorexit指令插入到同步代码块结束的位置。JVM需要保证每一个monitorenter都有一个monitorexit与之对应。
      任何对象,都有一个monitor与之相关联,当monitor被持有以后,它将处于锁定状态。线程执行到monitorenter指令时,会尝试获得monitor对象的所有权,即尝试获取锁。

    虚拟机规范对 monitorenter 和 monitorexit 的行为描述中,有两点需要注意。首先 synchronized 同步快对于同一条线程来说是可重入的,也就是说,不会出现把自己锁死的问题。其次,同步快在已进入的线程执行完之前,会阻塞后面其他线程的进入。(摘自《深入理解JAVA虚拟机》)

    • 同步方法
      synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。

    原理详解

    要理解低层实现,就需要理解两个重要的概念 MonitorMark Word

    • Java对象头

    synchronized用到的锁,是存储在对象头中的。(这也是Java所有对象都可以上锁的根本原因)
    HotSpot虚拟机中,对象头包括两部分信息:
    Mark Word(对象头)和 Klass Pointer(类型指针)

    • 其中类型指针,是对象指向它的类元素的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
    • 对象头又分为两部分:第一部分存储对象自身的运行时数据,例如哈希码,GC分代年龄,线程持有的锁,偏向时间戳等。这一部分的长度是不固定的。第二部分是末尾两位,存储锁标志位,表示当前锁的级别。

    对象头的长度一般占用两个机器码(32位JVM中,一个机器码等于4个字节,也就是32bit),但如果对象是数组类型,则需要三个机器码(多出的一块记录数组长度)。

    下图是对象头运行时的变化状态
    锁标志位是否偏向锁 确定唯一的锁状态
    其中 轻量锁 和 偏向锁 是JDK1.6之后新加的,用于对synchronized优化。稍后讲到

    java对象头
    • Monitor

    Monitor是 synchronized 重量级 锁的实现关键。锁的标识位为 10 。当然 synchronized作为一个重量锁是非常消耗性能的,所以在JDK1.6以后做了部分优化,接下来的部分是讲作为重量锁的实现。

    Monitor是线程私有的数据结构,每一个对象都有一个monitor与之关联。每一个线程都有一个可用monitor record列表(当前线程中所有对象的monitor),同时还有一个全局可用列表(全局对象monitor)。每一个被锁住的对象,都会和一个monitor关联。

    当一个monitor被某个线程持有后,它便处于锁定状态。此时,对象头中 MarkWord的 指向互斥量的指针,就是指向锁对象的monitor起始地址
    monitor是由 ObjectMonitor 实现的,其主要数据结构如下:(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)

    ObjectMonitor() {
        _header       = NULL;
        _count        = 0; //记录个数
        _waiters      = 0,
        _recursions   = 0;
        _object       = NULL;
        _owner        = NULL;
        _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _Responsible  = NULL ;
        _succ         = NULL ;
        _cxq          = NULL ;
        FreeNext      = NULL ;
        _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
        _SpinFreq     = 0 ;
        _SpinClock    = 0 ;
        OwnerIsThread = 0 ;
      }
    

    object monitor 有两个队列 _EntryList_WaitSet ,用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装成ObjectWaiter对象)_owner 指向持有 objectMonitor的线程。

    当多个线程同时访问一个同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor后,会进入_owner 区域,然后把monitor中的 _owner 变量修改为当前线程,同时monitor中的计数器_count 会加1。

    根据虚拟机规范的要求,在执行monitorenter指令时,会尝试获取对象的锁。如果对象没有被锁定(获取锁),获取对象已经被该线程锁定(锁重入)。则把计数器加1(_count 加1)。相应的,在执行monitorexit指令时,会讲计数器减1。当计数器为0时,_owner指向Null,锁就被释放。(摘自《深入理解JAVA虚拟机》)

    如果线程调用 wait() 方法,将释放当前持有的monitor,_owner变量恢复为null,_count变量减1,同时该线程进入_WaitSet 等待被唤醒。


    底层实现

    • synchronized 代码块低层原理

    从Javac编译成的字节码可以看出(具体编译文件看参考链接),同步代码块使用的是monitorentermonitorexit指令,其中monitorenter指向同步代码块的开始位置,monitorexit指向同步代码块的结束位置。

    在线程执行到monitorenter指令时,当前线程将尝试获取锁,即尝试获取锁对象对应的monitor的持有权。当monitor的count计数器为0,或者monitor的owner已经是该线程,则获取锁,count计数器+1。
    如果其他线程已经持有该对象的锁,则该线程被阻塞,直到其他线程执行完毕释放锁。

    线程执行完毕时,count归零,owner指向Null,锁释放。

    值得注意的是,编译器将会确保,无论通过何种方法完成,方法中的每一条monitorenter指令,最终都会有monitorexit指令对应,不论这个方法正常结束还是异常结束,最终都会配对执行。
    编译器会自动产生一个异常处理器,这个处理器声明可以处理所有的异常,它的目的就是为了确保monitorexit指令最终执行。

    • synchronized 方法低层原理

    方法级的同步是隐式,即无需通过字节码来控制的,它实现在方法调用和返回操作中。
    在Class文件方法常量池中的方法表结构(method_info Structure)中, ACC_SYNCHRONIZED 访问标志区分一个方法是否为同步方法。在方法被调用时,会检查方法的 ACC_SYNCHRONIZED 标记是否被设置。如果被设置了,则线程将持有该方法对应对象的monitor(调用方法的实例对象or静态方法的类对象),然后再执行该方法。
    最后在方法执行完成时,释放monitor。
    在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。
    以下是字节码实现:

    public class SyncMethod {
    
       public int i;
    
       public synchronized void syncTask(){
               i++;
       }
    }
    

    使用javap反编译后的字节码如下:

    Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncMethod.class
      Last modified 2017-6-2; size 308 bytes
      MD5 checksum f34075a8c059ea65e4cc2fa610e0cd94
      Compiled from "SyncMethod.java"
    public class com.zejian.concurrencys.SyncMethod
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool;
    
       //省略没必要的字节码
      //==================syncTask方法======================
      public synchronized void syncTask();
        descriptor: ()V
        //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=3, locals=1, args_size=1
             0: aload_0
             1: dup
             2: getfield      #2                  // Field i:I
             5: iconst_1
             6: iadd
             7: putfield      #2                  // Field i:I
            10: return
          LineNumberTable:
            line 12: 0
            line 13: 10
    }
    SourceFile: "SyncMethod.java"
    

    从字节码可以看出,synchronized修饰的方法并没有monitorentermonitorexit指令。而是用ACC_SYNCHRONIZED的flag标记该方法是否是同步方法,从而执行相应的同步调用。

    ReentrantLock如何实现非公平锁的“非公平锁”与“公平锁”区别

    1 ReentrantLock和synchronized区别

      (1) synchronized 是Java的一个内置关键字,而ReentrantLock是Java的一个类。
      (2) synchronized只能是非公平锁。而ReentrantLock可以实现公平锁和非公平锁两种。
      (3) synchronized不能中断一个等待锁的线程,而Lock可以中断一个试图获取锁的线程。
      (4) synchronized不能设置超时,而Lock可以设置超时。
      (5) synchronized会自动释放锁,而ReentrantLock不会自动释放锁,必须手动释放,否则可能会导致死锁。

    2 公平锁和非公平锁的区别

    (1) 线程在获取锁调用lock()时,非公平锁首先会进行一次CAS尝试抢锁,如果此时没有线程持有锁或者正好此刻有线程执行完释放了锁(state == 0),那么如果CAS成功则直接占用锁返回。
    (2) 如果非公平锁在上一步获取锁失败了,那么就会进入nonfairTryAcquire(int acquires),在该方法里,如果state的值为0,表示当前没有线程占用锁或者刚好有线程释放了锁,那么就会CAS抢锁,如果抢成功了,就直接返回了,不管是不是有其他线程早就到了在阻塞队列中等待锁了。而公平锁在这里抢到锁了,会判断阻塞队列是不是空的,毕竟要公平就要讲先来后到,如果发现阻塞队列不为空,表示队列中早有其他线程在等待了,那么公平锁情况下线程会乖乖排到阻塞队列的末尾。
      如果非公平锁 (1)(2) 都失败了,那么剩下的过程就和非公平锁一样了。
    (3) 从(1)(2) 可以看出,非公平锁可能导致线程饥饿,但是非公平锁的效率要高。

    concurrentHashmap是安全的吗,concurrentHashmap的size怎么求

    并发场景下,需要怎么设定锁

    mysql用什么索引

    Mysql目前主要有以下几种索引类型:FULLTEXT,HASH,BTREE,RTREE。
    1. FULLTEXT

    即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。

    全文索引并不是和MyISAM一起诞生的,它的出现是为了解决WHERE name LIKE “%word%"这类针对文本的模糊查询效率较低的问题。
    2. HASH

    由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。

    HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。
    3. BTREE

    BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。
    4. RTREE

    RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。

    相对于BTREE,RTREE的优势在于范围查找。

    B+树为什么索引快(B+树优点)

    1. B+树的高度一般为2-4层,所以查找记录时最多只需要2-4次IO,相对二叉平衡树已经大大降低了。
    2. 范围查找时,能通过叶子节点的指针获取数据。例如查找大于等于3的数据,当在叶子节点中查到3时,通过3的尾指针便能获取所有数据,而不需要再像二叉树一样再获取到3的父节点。

    幻读是什么,Innodb如何避免幻读

    innodb下的幻读是由MVCC 或者 GAP 锁 或者是next-key lock 解决的

    间隙锁的锁定范围是多少

    JVM内存模型

    gc的种类 mingc和fullgc的区别,什么时候发生gc

         新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具
        备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。

         老年代 GC(Major GC  / Full GC):指发生在老年代的 GC,出现了 Major GC,经常
        会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里
        就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10
        倍以上。

    Minor GC触发机制:
    当年轻代满时就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。
    Full GC触发机制:
    (1)调用System.gc时,系统建议执行Full GC,但是不必然执行
    (2)老年代空间不足
    (3)方法区空间不足
    (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    (5)由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

    当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载。

    volatile底层实现

    多核cpu在cpu级别怎么做到可见性

    hashmap和concurrenthashmap的区别与实现

    Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。

      ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment(段),默认16个(concurrency level),然后每次操作对一个segment(段)加锁,避免多线程锁的几率,提高并发效率。

    HashMap不是线程安全的,而ConcurrentHashMap是线程安全的

    string stringbuffer stringbuilder区别及实现

  • 相关阅读:
    链表-(1)
    爬虫(10-3)验证码图片识别
    爬虫10-2(多线程爬虫)
    分布式爬虫系统的架构(19)
    pipenv管理Python虚拟环境
    peewee-async集成到tornado
    Python3笔记051
    Python3笔记050
    Python3笔记049
    Python3笔记048
  • 原文地址:https://www.cnblogs.com/ashin1997/p/11602979.html
Copyright © 2020-2023  润新知