0307面试
ali
spring
aop(jdklib)
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。
静态AOP(AspectJ)编译期织入代码
动态AOP(CGLib,JdkLib)运行期织入代码
JdkLib只能对实现了接口的类进行代理
CGLib可以对没有实现接口的类进行代理
ioc
控制反转(DI依赖注入)
由容器来帮忙创建及注入依赖对象
在客户端类中不再主动去创建这些对象
hashmap实现,扩容机制
hanshmap 1.7 数组+链表 1.8+ 数组+链表+红黑树(>8),<8时会退化为链表 1.7头插法,1.8尾插法
hashTable 线程安全的Map 数组+链表,实现线程安全的方式是在修改数据时锁住整个HashTable
ConcorrectHashMap: 线程安全的HashMap,底层采用分段的数组+链表实现线程安全,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。(ReentranLock可重入锁)1.8之后取消segments 字段,直接采用 transient volatile HashEntry<K,V>[] table 保存数据,采用 table 数组元素作为锁,锁的粒度从多个 Node 级别又减小到一个 Node 级别,再度减小锁竞争,减小程序同步的部分。(Node+CAS+syncronized)
hash原理:高16位和低16位异或
HashMap扩容:
当HashMap.Size >= Capacity * LoadFactor
- 创建一个新的Entry空数组,长度是原数组的2倍
- 遍历原Entry数组,把所有的Entry重新Hash到新数组。
红黑树
二叉查找树,
- 每个节点非红即黑;
- 根节点是黑的;
- 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;
- 如果一个节点是红的,那么它的两儿子都是黑的;
- 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;
- 每条路径都包含相同的黑节点;
AVL为严格的二叉平衡树,查询效率高,红黑树更加适合修改密集型的任务
AVL树插入时为了保证平衡,需要更高的旋转次数才能在修改时正确地重新平衡数据结构。(红黑树最多两次)
sql 乐观锁,悲观锁,索引机制
悲观锁 对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要将数据锁定。(for update)排他锁
乐观锁 对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据是否冲突进行检测。通常由自己实现,通过版本,时间戳等
索引机制
实现:B+树 所有的叶子结点中增加了指向下一个叶子节点的指针,因此InnoDB建议为大部分表使用默认自增的主键作为主索引。
B 每个节点包含了键值和键值对于的数据对象存放地址指针,所以成功搜索一个对象可以不用到达树的叶节点。
B+ 非叶节点中存放的关键码并不指示数据对象的地址指针,非叶节点只是索引部分。所有的叶节点在同一层上,包含了全部关键码和相应数据对象的存放地址指针,且叶节点按关键码从小到大顺序链接。数据对象的插入和删除仅在叶节点上进行。
区别:
- B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
- B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
好处:
保证唯一性,加快检索速度
ACID
- 原子性 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
- 一致性 事务前后数据的完整性必须保持一致
- 隔离性 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
- 持久性 一个事务一旦被提交,它对数据库中数据的改变就是永久性的
隔离级别
原因
- 脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的
- 幻读:指一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。
隔离级别 - 读未提交 一个事务可以读取另一个未提交事务的数据。
- 读已提交(Oracle,Sql server) 只能读到已经提交了的内容 避免“脏读”
- 可重复读(Mysql),针对“不可重复读”这种情况而制定的隔离级别,避免出现“脏读”、“不可重复读”
- 序列化 这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。避免幻读
算法 五亿个数据排序
归并排序 分治 字典树
n个数取前k个(改进快排),快排,复杂度 B+树
java多线程
- Threadlocal 应用场景(保障了线程安全,通过静态map实现)
ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全 - synchronized 和 ReentrantLock
- 底层实现上来说,synchronized 是JVM层面的锁,是Java关键字,ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁(利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能)
- synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用; ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()和unlock()方法配合try/finally语句块来完成,使用释放更加灵活。
- synchronized是不可中断类型的锁,除非加锁的代码中出现异常或正常执行完成; ReentrantLock则可以中断,
- synchronized为非公平锁 ReentrantLock则即可以选公平锁也可以选非公平锁
- synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。
synchronized的实现原理
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
volatile和synchronized区别
synchronized相比(synchronized通常称为重量级锁),volatile更轻量级
栈和队列
队列 先进先出 栈 先进后出
bio,nio,netty的区别,零拷贝
- bio 阻塞io BIO:适用于连接数比较固定的场所,
- nio 同步非阻塞IO处理 适用于连接数比较多但数据比较短,例如聊天服务器,弹幕,服务器间通讯
NIO同步非阻塞:一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。 - Netty 基于 Java NIO 技术封装完善之后得到一个高性能框架
零拷贝 减少IO流程中不必要的拷贝,以及减少用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销。
Netty相较于JDK自带的NIO,更加加单。
支持多种协议,FTP,SMTP,HTTP以及各种二进制和基于文本的传统协议。
简单强大的线程模型
自带多种编解码器
真正的无连接数据包套接字支持
有完整的SSL、/TLS以及StartTLS支持
开发的社区环境好,然后很多开源项目的解决方案。
双亲委派
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
可以避免类的重复加载,
对象的内存布局一般分为三个部分:对象头,实例数据,对齐填充
线程池执行过程,主要的参数
- corePoolSize 线程池核心线程大小(核心线程不会被回收)
- maximumPoolSize 线程池最大线程数量(当线程数量达到corePoolSize,且workQueue队列塞满任务了之后,继续创建线程。)
- keepAliveTime 空闲线程存活时间
- unit 空闲线程存活时间单位
- workQueue 工作队列 当前线程数超过corePoolSize时,新的任务会处在等待状态,并存在workQueue中,BlockingQueue是一个先进先出的阻塞式队列实现
- threadFactory 创建线程的工厂类
- handler 线程池执行拒绝策略
提交任务时,线程池队列已满,会发生什么
- 无界队列 LinkedBlockingQueue ,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
- 有界队列比如 ArrayBlockingQueue ,任务首先会被添加到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来, ArrayBlockingQueue 继续满,那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy(1.添加队列,2。增加线程数量3.拒绝策略)
jvm,堆区,方法区,GC
- 虚拟机栈 描述java方法执行的内存模型:每个方法被执行的时候都会创建一个"栈帧",用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
- 堆 标识当前线程执行的字节码文件的行号指示器。
- 方法区 存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。
- 程序计数器 标识当前线程执行的字节码文件的行号指示器。
- 本地方法栈 与虚拟机栈相似,虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
java类加载机制
- 加载 ClassLoader通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个class对象。
- 验证 确保class文件的字节流中包含信息符合当前虚拟机要求,验证:文件格式的验证,元数据的验证,字节码验证,符号引用验证
- 准备 类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值,final不会,它在编译的时候就初始化了
- 解析 把常量池中的符号引用替换成直接引用
- 初始化 如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量。
实现一个线程安全的单例模式
- sync关键字实现该方法,但效率低下
- sync修饰代码块,可能是线程不安全的
- 双检查锁机制 使用volatile关键字保其可见性,sync修饰代码块,并进行二次检查是否为null
- 枚举数据类型实现单例模式,枚举类的构造方法在类加载是被实例化
volatile和synchronized区别
synchronized Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块。
volatile volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。确保本条指令不会因编译器的优化而省略,且要求每次直接读值。防止重排序,线程可见性,不能保证原子性,非线程安全


乐观锁:每个去拿数据的时候都认为别人不会修改,所以不会都不会上锁,但是在更新的时候会判断一下在此期间有没有去更新这个数据。所以乐观锁使用了多读的场合,这样可以提高吞吐量,像数据库提供的类似write_condition机制,都是用的乐观锁,还有那个原子变量类,在java.util.concurrent.atomic包下
悲观锁:总是假设最坏的情况,每次去拿数据的时候都会认为有人会修改,所以每次在拿数据的时候都会上锁。这样别的对象想拿到数据,那就必须堵塞,直到拿到锁。传统的关系型数据库用到了很多这种锁机制,比如读锁,写锁,在操作之前都会先上锁,再比如Java的同步代码块synchronized/方法用的也是悲观锁。

浙公网安备 33010602011771号