中级-面试题目整理

基础:

1、抽象类和接口

  1. 首先从继承角度来看,子类继承抽象类要么重写父类的全部方法,要么把自己也定义成抽象类;接口是可以实现多个的。
  2. 从实例化角度看,抽象类有构造方法,但是不能实例化;接口没有构造方法,不能实例化。
  3. 第二从方法观察,抽象类没有方法体,是依靠子类实现方法;接口在jdk1.7之前不能有方法的实现,在jdk1.8之后可以定义静态方法、default方法和其实现。
  4. 第三从变量声明来看,抽象方法和普通类一样;接口只能声明public static final类型的静态变量。

2、int 和 Integer、拆箱装箱?

  1. int是基本数据类型;Integer是对象,有方法和字段,调用时通过引用对象的地址。
  2. int保存在栈中,Integer保存在堆中
  3. int默认值是0,Integer默认值是null

拆箱装箱

  1. 装箱:int -> Integer
  2. 拆箱:Integer -> int

3、== 和equals

==比较基本数据类型的时候,比较的是值是否相等;比较引用数据类型的时候,比较的是引用对象指向的内存地址是否相等。

equals默认比较的是对象的内存地址是否相同,在String、Date类重写后比较的是对象的属性和内容。

4、重写equals为什么还要重写hashcode

java规定两个对象equals相等的时候,两个对象的hashcode也必须相等。

那么当equals被重写了以后,s1和s2的equals相等,但是他俩的hashcode很可能不同,因此需要通过重写hashcode来满足equals

5、String StringBuilder StringBuffer

String:String内部的value数组使用的是private final作为修饰的,指向的地址和内容都不能被修改,所以String是一个不可变的类,我们每次修改String变量的时候都会在堆中重新创建一个新的对象。线程安全。性能最低。存储在字符串常量池里面。

StringBuilder:变量可以被修改,线程不安全,适用于单线程,性能最高。存储在堆内存。

StringBuffer:变量可以被修改,方法都使用了synchronized加锁,适用于多线程,但是会影响速度。性能高于String。存储在堆内存。

6、静态变量能被序列化吗

不能。序列化的作用范围是对象,静态变量属于类不属于对象;
使用transient修饰的瞬态变量也不能被序列化

集合:

1、ArrayList、LinkedList

ArrayList底层数据结构是数组,数组的优势在于查询的时候通过索引可以直接定位,速度快,删除的时候需要移动元素位置,速度慢。ArrayList的默认容量是10,按照1.5倍扩容。

  • 扩容机制:先创建一个新的数组,长度是旧数组的1.5倍;使用Arrays.copyOf(),将旧数据复制到新数组中,再将要添加的新数据添加到新数组中。

然后,动态扩容就意味着,数组永远不可能被填满。假如容量为10,现在有11条数据要插入数组,那么数组就会自动扩容到15,那么还有4个空间就处于空闲。那在序列化的时候是不是要多序列化4个空间,这样就会造成闲置空间过大。那么ArrayList就十分巧妙的使用实际大小 size 而不是数组的长度进行序列化。

LinkedList底层是双向链表结构,查询的时候需要从头结点开始遍历,速度慢,删除的时候只需要找到节点,将头指针和尾指针重新指向即可。

2、HashMap、linkedhashmap

jdk1.8以后,底层数据结构是数组+链表+红黑树,默认大小是16,扩容机制是2的次幂,负载因子是0.75。当HashMap数组长度达到64位,并且链表长度大于等于8,此时链表会转化为红黑树。因为根据官方测试,当链表的长度等于8时,发生hash碰撞的概率接近为0。如果8个节点,链表的平均查找效率是8/2=4。红黑树的查找效率为logn,所以红黑树的平均查找效率是3。虽然转换成红黑树的查询效率变高了,但是链表和红黑树的转化也影响效率;另外当链表节点 选择6和8,是为了防止链表和红黑树频繁转换,假设有一个链表一直增加删除,那么链表和红黑树就会一直进行转换,非常影响效率。

哈希碰撞:两个不同的key,经过hash算法生成了同一个hashcode,这样两个key就会存放在同一个数组索引上,则该索引的位置形成链表结构,等到查询key的时候,找到在数组中的位置然后再遍历链表,最终得到匹配的key信息。

扩容机制:当hashmap的容量大于数组长度*负载因子的时候,hashmap就会执行扩容2n倍。扩容会重建hash表,非常影响性能。

3、ConcurrentHashmap

jdk1.8的数据结构是Node数组+链表+红黑树。
初始化Node数组采用CAS+volatile
put数据采用的是synchronized

多线程:

1. 谈谈你对线程安全的理解

当多个线程访问同一个对象,在不进行线程同步控制的情况下,使用这个对象都可以获得正确的结果,那么这个对象就是线程安全的

2. 什么时候考虑线程安全

多个线程访问同一个对象的时候

3. 如何做到线程安全

使用synchronized关键字给方法或者代码块加锁,例如StringBuffer中,所有的方法都加了synchronized锁,
但synchronized是互斥锁,当一个线程持有了对象的锁,那么其他线程只能阻塞挂起,或者自旋等待。
只有当持有锁的线程释放锁以后,其他线程才能竞争对象的锁。这样带来的问题就是程序性能会受影响。
但是jdk1.6之后jvm对synchronized的锁机制进行了优化。无锁、偏向锁、轻量级锁、重量级锁。

4.线程创建方式、线程池

线程池创建:

  1. 继承Thread类
public class AchieveThread extends Thread {
	@Override
	public void run() {
		System.out.print(Thread.currentThread.getName());
	}

	public static void main(String[] args) {
		AchieveThread thread = new AchieveThread();
		thread.start();
	}
}
  1. 实现Runnable接口
public class AchieveRunnable implements Runnable{
	@Override
	public void run() {
		System.out.print(Thread.currentThread.getName());
	}
	public static void main(String[] args) {
		AchieveRunnable runnable= new AchieveRunnable ();
		new Thread(runnable).start();
	}
}
  1. 实现Callable接口
public class AchieveCallable {

    public static void main(String[] args) {
        Callable call = () -> {
            System.out.println("我是子线程");
            return "sss";
        };

        FutureTask task = new FutureTask<>(call);
        Thread thread = new Thread(task);
        thread.start();
        doSome();
        try {
            task.get();
        }
         catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
         }
    }

    public static void doSome() {
        System.out.println("线程中间执行代码");
    }
}

线程池:

核心参数
ThreadPoolExecutor(2, 20, 30L, TimeUnit.SECONDS,new LinkedBlockingQueue)
、拒绝策略

5. synchronized、Lock、reentrantLock

synchronized:是一个关键字,适用范围是修饰方法、代码块等。synchronized是实现了一种互斥锁。在编译之后会产生monitorenter和monitorexit两种指令,依靠着两个字节码指令进行线程同步,实现了原子性。底层是依靠Monitor实现,是重量级锁。

Lock:

ReentrantLock:基于AQS,可以实现公平锁和非公平锁

6. ThreadLocal
每个线程的变量是相互隔离的。
7. volatile和synchronized的区别

我们知道并发编程的三个特性,原子性,有序性,可见性。
1、那么synchronized可以保证原子性,因为是排他锁,synchronized修饰的代码无法被中断;volatile不具备原子性。
2、synchronized是通过程序的串行化保证有序性的,但在代码块中可以重排序;volatile禁止JVM编译器指令重排序。
3、volatile是借助操作系统的lock指令来保证变量更新的同时,刷新主内存中的变量,其他线程的工作内存中的副本置为无效,重新从主内存中读取变量;synchronized借助的是monitorenter和monitorexit使代码串行化,在monitorexit的时候所有的共享变量都刷新到主内存,保证了可见性。

8. 无锁、偏向锁、轻量级锁、重量级锁

无锁就是没有给对象加锁。

偏向锁是说对象的锁对某个线程偏爱,线程只要请求就立刻把锁交出去,对象的前23bit存放着偏向线程的ID。

轻量级锁:假如目前有多个线程来请求对象资源,线程发现对象的锁标志位是00,即轻量级锁,那么线程会在自己的栈帧中开辟一块LockRecord空间,用来存放对象头中的Markword副本以及owner指针,线程通过CAS机制尝试获得锁,一旦获得锁将把对象头中的Markword副本复制到LockRecord中,然后owner指针指向该对象。对象的Markword前30bit会生成一个指针,指向线程的LockRecord。

自旋锁:假如对象的锁是轻量级锁,线程已经获得了对象的锁,那么假如还有另一个线程也在尝试获取锁,那么这个线程会进入自旋状态,不断循环尝试获取锁,直到达到自旋限制。这样的话线程就不用被操作系统挂起阻塞,因为如果对象资源被释放的话,线程就不用进行系统中断和恢复,所以效率更高。但是自旋相当于是CPU在空转,所以长时间的自旋会耗费资源,因此又出现了一种适应性自旋的优化。

重量级锁:如果超过自旋等待的线程超过1个,那么轻量级锁会升级为重量级锁。此时需要通过Monitor进行线程控制,此时资源将被完全锁定。

9. 乐观锁和悲观锁

如果多个线程想要操作一个对象,怎么做才能保证线程安全?

我的第一反应是可以给这个对象加synchronized互斥锁,就是说当一个线程拿到了这个对象的锁,其他的线程就会被阻塞,只有当拥有对象锁的线程释放了这个锁,其他的线程才有机会去竞争拿到这个对象的锁。但是这种操作有个缺点,假如大部分操作都是读操作,那就没有必要给这个对象加互斥锁,这样会大大影响程序的性能。

  • 乐观锁(CAS)
    • 乐观锁实际上是一种无锁,能够实现不对共享资源锁定,也能实现线程同步。在操作数据的时候不会加锁,因为乐观地认为不会有其他线程去修改数据,而是在修改数据的时候判断当前数据和一开始拿到的数据作比对,判断数据是不是被改动了
  • 悲观锁(synchronized)
    • 操作数据的时候会加互斥锁,因为悲观锁会悲观的认为数据会被修改,所以别的线程想要操作数据只能等待拥有这个对象的锁的线程释放这个对象的锁
10. CAS

Compare And Swap

假如有a、b两个线程竞争同一个对象的锁,对象有一个状态值0,a线程先请求到对象并且拿到状态值0,然后和自己的目标值进行比较compare,发现一致后,将对象的状态值0改为1(swap);这时b线程也请求到了对象。但是拿到的状态值是1,和自己的目标值比较conpare后发现不一致,因此b线程只能在此进行自旋循环尝试获取对象资源,配置自旋次数防止出现死循环。

但是有没有可能出现这种情况,假如a在请求到对象的时候拿到状态值是0,在即将进行compare的时候,b线程及时赶到,也拿到了状态值0,并且先一步把对象的状态值改成了1,此时a线程并不知道状态值已经被改成了1,也就是对象的锁已经被b抢占了,这就出现了a、b线程同时获得了资源的情况。

所以核心问题就是,在比较(compare)状态值并且修改(swap)状态值的这个动作,在同时只能有一条线程执行。也就是说:CAS必须是原子性的。

CAS的原子性实现原理:x86提供了cmpxchg指令支持CAS

代码实现:

/**
 * 使用CAS实现三个线程同步累计加到1000
 */
public class ThreeThread {

    static AtomicInteger num = new AtomicInteger(0);
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (num.get() < 1000) {
                        System.out.println(Thread.currentThread().getName() + ":" + num.incrementAndGet());
                    }
                }
            });
            thread1.start();
        }
    }
}
incrementAndGet里面调用了Unsafe类的getAndAddInt方法,getAndAddInt调用了Unsafe的compareAndSwapInt方法,这是一个native本地方法,也就是说是根据操作系统实现的,
AQS

抽象队列同步器
原理:FIFO队列和state标志。
过程:线程竞争锁,调用acquire方法,tryAcquire和acquireQueued方法,addWaiter方法就是把线程封装成Node,插入FIFO队尾;acquireQueued是唤醒队列中的每一个线程尝试获取锁,否则挂起。

公平锁、非公平锁

公平锁:按照请求锁的顺序分配,比如AQS中的FIFO队列,性能较低。

非公平锁:就是不按照请求锁的顺序分配,性能可能比公平锁更高(在线程从阻塞被唤醒切换的短暂空闲时间,有可能后来的线程抢到时间片就执行完了)

死锁:遇见过吗,怎么排查的(jps排查方式)
单机情况下并发控制、失效情况

框架:

spring框架、三级缓存、循环依赖
AOP和IOC 在工作中的实际应用

AOP:切面编程。
IOC:控制反转

spring事务传播机制
  1. REQUIRED:如果当前存在事务,那么则加入这个事务去执行;如果不存在事务,那么就新建一个事务。会随着父级事务提交和回滚。
  2. REQUIRED_NEW:不管当前有没有事务,都会自动创建一个新的事务执行,新老事务之间是独立的。外部事务异常不会影响内部事务的提交。
  3. NESTED:如果当前存在事务,则嵌套进事务;如果不存在事务,则创建一个事务。子级事务失败子级回滚,父级事务失败父子全部回滚,父级事务提交父子全部提交。
  4. SUPPORTS:如果当前有事务,那么在事务中执行;如果没有事务,就以非事务的方式执行。
  5. NOT_SUPPORTS:如果有事务,则把事务挂起来;如果没有事务,以非事务的方式执行。
  6. MANDATORY:如果不存在事务,抛出异常。
  7. NEVER:如果当前存在事务,抛出异常。
Bean生命周期

1、方法入口:doCreateBean()
2、实例化Bean:createBeanInstance()
3、对象书属性填充:populateBean()
4、初始化Bean:initializeBean()
4.1 实现invokeBeanAware方法
4.1.1
5、销毁Bean:DispoableBean的destroy()方法

Bean的作用域

我们知道spring的IOC容器可以方便的帮助我们去管理Bean的实例,那么实例存放在IOC中必定存在生命周期和作用域。一般来说常见的作用域包括:

  • Singleton:单例,也就是说整个IOC容器内只有一个Bean对象的实例
  • ProtoType:原型,是指每一次从IOC容器获取实例的时候,都会返回一个新的Bean实例对象。

但是,在基于spring框架下的web应用里面,增加了一个会话维度,来控制Bean的生命周期:

  • request:就是说每一次的http请求都会创建一个新的Bean实例;
  • session:以session会话为维度,同一个session共享一个Bean实例,不同的session产生不同的实例;
  • globalsession:全局session使用同一个Bean实例。
Spring MVC请求流程

1、用户请求发送到前端控制器DispatcherServlet
2、DispatcherServlet收到请求后调用HandlerMapping处理器映射器
3、根据请求找到具体的处理器,生成处理器对象返回给DispatcherServlet
4、DispatcherServlet将处理器对象发送给HandlerAdapter处理器适配器
5、HandlerAdapter根据处理器对象找到合适的处理器(Controller)
6、Controller执行完成返回ModelAndView给HandlerAdapter
7、HandlerAdapter将ModelAndView返回给DispatcherServlet
8、DispatcherServlet将ModelAndView发送到ViewResolver视图解析器
9、ViewResolver解析之后返回View给DispatcherServlet
10、DispatcherServlet将View发送给View渲染
11、View返回渲染的视图结果给DispatcherServlet
12、DispatcherServlet返回响应给用户

Spring MVC核心注解
 @Component:类上使用@Conponent注解,会自动注入Spring容器
 @Controller:控制器
 @Service:业务逻辑层
 @Repository:持久层
 @RequestMapping:控制器可以处理哪些请求
springboot核心注解、启动流程

1、@SpringBootConfiguration:标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。
2、@EnableAutoConfiguration:开启自动装配机制,将所有符合自动配置条件的bean定义加载到Ioc容器。
3、@ComponentScan:组件扫描,可自动发现和装配Bean,默认扫描SpringBootApplication注解类所在的包路径下文件。

springboot自动装配

@EnableAutoConfiguration注解。
有一个注解,名为@Import(AutoConfigurationImportSelectors.class)
springcloud核心组件、理解、使用
Nacos:注册中心
Gateway:网关
feign:远程调用
ribbon:负载均衡
zookeeper、nacos、dubbo区别、注册中心
CAP理论
zk是遵循的CP理论
nacos是AP
seata:AT TCC 
mybatis缓存(一级、二级缓存)
 一级缓存:属于sqlSession级别的缓存,也叫做本地缓存。就是说sqlSession会把用户查询的数据缓存起来,
 如果下次查询缓存命中的话,直接从缓存中读取数据。
 二级缓存:但是如果想要跨sqlSession会话,就要用到二级缓存,也就是说多个用户查到的数据,其他用户可以共享。
mybatis设计模式
动态代理模式
RPC框架的了解

消息中间件:

你们公司是怎么选型的?
mq理解、使用过的
怎么保证保证信息不丢失、重复消费

rabbitmq
保证信息不丢失:
1、生产者发送到队列可能会丢失:mq开启confirm机制,消息接收到了给生产者一个ack,失败了则回调nack函数
2、mq收到消息,发送给消费者之前宕机,可能会丢失:mq开启持久化。消息到达mq之后,先到exchange交换机中,然后路由到queue队列,最后发送给消费者。因此exchange、queue、message都需要开启持久化,这样宕机之后才能恢复消息。
3、消费者接收消息途中丢失:关闭rabbitmq的ack模式

怎么实现顺序消费

现在有一个队列queue,里面有3条消息,data1/data2/data3,又有3个消费者consumer,queue将消息分别发送给consumer,
但是consumer消费的顺序是data2/data3/data1,这样就破坏了消息的顺序性。

Redis:

你们公司为什么用redis
redis持久化机制、缓存淘汰策略
RDB:在指定的时间内,执行指定次数的操作,则将内存中的数据全部写入磁盘。适合大规模的数据恢复;
缺点是数据完整性不高,有可能在最后一次备份的时候宕机;备份时占用内存。
AOF:默认每秒将写操作日志追加到AOF文件中(everysec)。也可以选择每次数据发生变化就写入磁盘(always)。	
有重写触发机制,当文件大小是上一次重写文件大小的一倍且文件大于某个值的时候触发。
优点是数据完整性和一致性更好,缺点是AOF文件会越来越大,数据恢复也会越来越慢。
怎么实现redis高可用
缓存击穿、缓存穿透、缓存雪崩
缓存击穿:击穿的是缓存
	当有一个热点数据在redis中丢失,那么大量的请求在缓存中拿不到数据,全部打向DB,此时数据库难以承受大量的访问可能会宕机。
缓存穿透:穿透的是缓存和DB
	1、恶意请求。
	表里大部分id使用递增id来定义的,假如有人获取到了某个查询接口,故意使用表里不存在的id发送恶意请求,
	那么缓存和数据库都会有宕机的风险。
缓存雪崩:
	是指大量的热点key丢失,导致大量的请求直接发送到DB,导致DB宕机。
缓存和数据库的双写一致性
redis分布式锁

1、setnx命令
2、redisson

数据库:

数据库性能优化方式
sql调优、索引失效、索引结构

type:表示查询类型,下面标志从效率由低到高说明

ALL:全表扫描

index:跟全表扫描一样,

range:范围扫描是一个有限制的全表扫描,一般有between或者where子句,不用遍历全部索引。

ref:索引查询。

eq_ref:类似与ref,区别是使用的是唯一索引

const、system:系统会把匹配行中的其他字段作为常数处理,

NULL:MySQL不访问任何表或索引,直接返回结果

一般查询要达到range以上

索引失效的几种情况:
1、在索引上使用负向查询:IN、NOT IN、NOT LIKE、NOT EXISTS; !=、<>
2、组合索引不满足最左匹配原则
3、隐式转换:字段是varchar,匹配的是数字
4、使用LIKE 不满足左边关闭,会导致索引失效
5、使用OR可能会导致索引失效,但是OR两侧都分别建立了索引,那么索引不失效。
6、在索引上进行(+、-、*、/)计算,索引一定失效。
7、在索引上使用内置函数,例如abs(age) = 80,索引一定失效。
8、在可为NULL的索引上使用 IS NULL或者 IS NOT NULL,可能会导致索引失效。
哪些情况不适合建索引

1、不用在where条件中的列
2、列有大量的重复数据
3、CLOB/BLOB类型的列
4、数据量小的表不适合建索引

存储引擎innodb MyISAM

MyISAM索引结构使用的是B+树,叶子节点的data域存放的是数据的磁盘地址,根据索引找到数据存放的地址,再根据地址去磁盘上找到数据;因此索引和数据文件是分开存储的,也叫做非聚集索引。
innodb索引结构也是B+树,叶子节点存放的是主键索引和data域完整的数据,非叶子节点存放的是索引。叶子节点使用的是指针连接,提高区间访问的性能。支持事务、MVCC、外键。

事务:

分布式事务、解决方案
分布式session方案

其他:

Linux命令

安装jdk
1、修改profile文件配置环境变量
2、source命令使环境变量生效

zookeeper使用和原理

设计模式:

项目里用到的

策略模式+工厂模式,优化if-else
代理模式:保护真正的代码实现类,起到一个中介的作用;另外可以在代理的中给代码添加一些自定义的操作。

开放性问题:

1、项目里复杂的问题

动态切换数据源
preHandler方法,获取租户名
postHandler方法,释放数据源

2、解决的生产问题

JVM内存不足,单体项目,服务器资源紧张,tomcat设置参数 -Xmx;用户密集操作,请求量挡不住,多加了一台服务器,使用nginx负载到两台机器上。

3、幂等问题
4、短信注册接口、生产问题
5、表单订单重复提交问题
6、OutOfMemoryError遇到过吗?怎么解决的?

JVM内存不足,单体项目,服务器资源紧张,tomcat设置参数 -Xmx;用户密集操作,请求量挡不住,多加了一台服务器,使用nginx负载到两台机器上。
三种方式:
轮询
权重
ip_hash

7、JVM查看参数、线上调优?
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值