初级Java面试就看这一篇就够了!!!

面试问题

基础篇

1. java语言的特点
简单易学(Java吸收了C++的各种优点,摒弃了C++里面复杂的多继承、指针概念)
支持多线程
面向对象
与平台性无关,一次编译到处运行(Java虚拟机实现与平台性无关)
2. 封装继承多态
封装:将对象的属性隐藏在对象内部,不允许外部的程序直接访问对象内部的信息,而是通过对象提供的方法实现对对象的操作和访问,这样做的好处有便于修改,提高代码的可维护性,安全,让使用者只能通过预定的方法来访问数据,从而可以在方法里加入逻辑控制。
继承:已有的类创建新的类,在原有的类的基础上扩展功能,继承分为子类和父类,其中子类继承了父类的所有属性和方法,但是对于private的属性和方法,子类虽然继承了,但是不能直接访问父类,所以相当于没有继承到,想要继承父类方法,在父类中写一个public的get()方法来获取父类中的private属性,子类就调用父类的get()来获取private属性。Java允许多层继承。
多态:父类的引用指向子类的对象,指的是父类引用持有子类对象,这时候只能调用父类方法,因为编译的时候,我们把它看作是父类的原因,但是到了运行的时候,编译器就会发现父类引用的是子类对象,所以如果父类和子类都有相同的方法时,调用的是子类的方法,而不是父类的。多态提高了代码的拓展性。
3. equals与==区别
equals是比较两个对象的内容是否相等,==比较的是变量内存中存放对象的内存地址,判断两个对象的内存地址是否相同,是否指向同一个对象。在阿里的代码规范中只使用equals,因为阿里插件会默认识别,并可以快速修改,推荐使用把==替换成equals,并且在常量进行相比较时,把常量写在前面,因为使用Object的equals,object可能为null
4. 重写重载
重写:发生在子类和父类,子类继承了父类原有的方法,并且在方法名,返回类型,参数列表都相同(除子类返回值是父类返回值的子类时)的情况下,对方法体进行重写,需要注意的是,子类的权限修饰符不得小于父类,并且重写方法一定不能抛出新的检查异常或者比被重写方法声明更大的检查异常
重载:在一个类中,同名的方法,不同的参数列表(参数类型不同,顺序不同,个数不同),则是重载,并且对返回类型没有要求,但不能通过返回类型是否相同来判断是否为重载,例如void f ()和int f(),这两个方法虽然同名,但是很容易区分,int x = f()这样完全没问题,因为编译器会根据上下文进行判断,但是如果并不关心方法的返回值,而是想调用方法的其他效果,那么就判断不了调用的是哪个f()方法了
5. 集合
常见的集合有List、Map、Set
List和Set集合都继承Collection(单列集合)
List一般分为:ArrayList、LinkedList、Vector,并且List接口有序可重复
  • ArrayList:底层数据结构是数组,所以ArrayList查询快,增删慢(因为进行修改操作,数组所有数据都要改变,牵一发而动全身),线程不安全(执行add操作,多条数据添加可能会丢失数据),效率高。扩容约是每次的1.5倍

为什么线程不安全:ArrayList add操作源码有两部分:
ensureCapacityInternal(size + 1);扩容
      elementData[size++] = e;新增
不安全性主要体现在两个方面:1.不是一个原子操作,是分两步执行的,第一步elementData[size] = e再执行size++,单线程执行没问题,但是多线程执行,就可能会发生一个线程覆盖另一个线程的问题,例如size = 0,当线程A执行完第一步之后挂起,线程A把元素放在下标为0的位置,此时size = 0,然后CPU调度线程A暂停,线程B执行elementData[size]=0,因为此时size=0,所以B也把元素放在下标0的位置,线程A和B都将执行size++,size为2,但元素只有一个;2.ArrayList默认数组大小是10,假如现在已经添加了9个元素,size=9,线程A执行完ensureCapacityInternal(size + 1)挂起了,线程B开始执行,但是它发现数组容量不需要扩容,于是把数据放在下标为9的位置上,并且size+1,此时size=10,接着线程A开始执行,尝试把数据放在下标为10的位置,但是size在B线程执行的时候已经为10了,由于数组并没有扩容,所以会抛出数组越界异常
如果需要在多线程中使用,可以采用list list =Collections.synchronizedList(new ArrayList)来创建一个ArrayList对象。
  • LinkedList:底层数据结构是双链表,所以增删快,查询慢,线程不安全,效率高

  • Vector:底层数据结构也是数组,线程安全,因为Vector加了同步锁

ArrayList扩容机制:

ArrayList有三个构造函数,不同的构造函数会影响扩容机制判断

1、默认的无参构造,初始容量为10的空列表

2、指定初始容量的构造函数,就是给定一个具有指定长度的空数组

3、包含特定集合元素的构造函数,把传入的集合转为数组,通过Arrays.copyOf方法把集合中的元素拷贝到elementData中

ArrayList扩容的核心方法grow(),下面将针对三种情况对该方法进行解析:

1、当前数组是由默认构造方法生成的空数组并且第一次添加数据此方法会返回DEFAULT_CAPACITY(值为10)和minCapacity(calculateCapacity方法,在调用ensureCapacityInternal(size + 1)之前执行)的最大值,因此,最终会返回固定值10。而之约后的数组扩容按照当前容量的1.5倍进行扩容; 2、当前数组是由自定义初始容量构造方法创建并且指定初始容量为0,则此方法会返回1, minCapacity变量只是第一次调用add方法时值为1,那么他它会根据算法前四次扩容每次都 +1,在第5次添加数据进行扩容的时候才是按照当前容量的1.5倍进行扩容。 3、当扩容量(newCapacity)大于ArrayList数组定义的最大值后会调用hugeCapacity(grow()中的)来进行判断。如果minCapacity已经大于Integer的最大值(溢出为负数)那么抛出OutOfMemoryError(内存溢出)否则的话根据与MAX_ARRAY_SIZE的比较情况确定是返回Integer最大值还是MAX_ARRAY_SIZE,最后,通过Arrays.copyOf方法把原数组的内容放到更大容量的数组里面。

Set一般分为HashSet,LinkedHashSet、TreeSet,并且Set不允许重复集合,且唯一
  • HashSet底层数据结构是Hash表,可以储存null元素,元素的唯一性是通过存储元素类型是否重写了hashCode和equals来保证的,如果没有重写这两个方法,则无法保证元素的唯一性

  • LinkedHashSet底层数据结构是链表加Hash表共同实现,链表保证了元素的顺序与储存顺序一致,哈希表保证了元素的唯一性,线程不安全

  • TreeSet底层是红黑树实现的,TreeSet的数据是自动排序好的,不允许有null值

Map(双列集合)使用键值对保存数据,Map会维护与Key有关联的值,两个Key可以引用相同的对象,但Key不能重复,常见的有HashMap、HashTable、TreeMap,也有ConcurrentHashMap(concurrentHashMap是使用了锁分段技术来保证线程安全的)、LinkedHashMap...
  • HashMap:基于哈希表实现,允许储存null值和null键,线程不安全

  • HashTable:基于哈希表实现,不允许储存null值 ,线程安全,效率低(可能是因为HashTable继承Dictionary类,而Dictionary已经被废弃了,而且HashTable是线程安全的)

  • TreeMap:底层基于红黑树的NavigableMap 实现,该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

6. java创建对象的几种方式
  1. new创建新对象

  2. 通过反射机制: 反射是对于任意一个正在运行的类,都能动态获取到他的属性和方法。反射一般分为两种方式,一是使用Class类的new Instance() 方法,二是使用Constructor类的new Instatnce() 方法。

两者区别在于: Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数; Constructor.newInstance() 可以根据传入的参数,调用任意构造函数。

  1. 采用clone机制:要拷贝的对象需要实现Cloneable类,并重写clone()方法。

  2. 通过序列化机制:当序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数,需要让类实现Serializable接口。

7. 线程

Runnable和Callable的区别

Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。 Callable的任务执行后可返回值,而Runnable的任务是没有返回值的。 Call方法可以抛出异常,run方法不可以。

相同点:两者都需要调用Thread.start()启动线程

线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。

ThreadPoolExecutor的重要参数
    1、corePoolSize:核心线程数
        * 核心线程会一直存活,及时没有任务需要执行
        * 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
        * 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
​
    2、queueCapacity:任务队列容量(阻塞队列)
        * 当核心线程数达到最大时,新任务会放在队列中排队等待执行
        因为阻塞队列会保留任务,当核心线程数满了,它会把任务阻拦在外面并且保留,如果普通队列,当满了就会进行销毁
​
    3、maxPoolSize:最大线程数
        * 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
        * 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
​
    4、 keepAliveTime:线程空闲时间
        * 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
        * 如果allowCoreThreadTimeout=true,则会直到线程数量=0
​
    5、allowCoreThreadTimeout:允许核心线程超时

java 提供了一个 java.util.concurrent. 接口的实现用于创建线程池。

9、四种线程池的创建:

1)newCachedThreadPool创建一个可缓存线程池

2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。

3)newScheduledThreadPool 创建一个大小无限的线程池,支持定时及周期性任务执行。

4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。

并发三要素:

原子性、可见性、有序性

线程池中的submit()和execute()有什么区别

两者都是将一个线程任务添加到线程池中并执行; 1、execute没有返回值,submit有返回值,并且返回执行结果Future对象,根据Future判断是否执行成功 2、execute不能提交Callable任务,只能提交Runnable任务,submit两者任务都可以提交 3、在submit中提交Runnable任务,会返回执行结果Future对象,但是Future调用get方法将返回null(Runnable没有返回值)

volatile关键字的作用

被volatile修饰的变量就具备了两层意思:

保证了不同线程对这个变量操作时的可见性,和禁止进行指令重排序,保证了可见性

volatile和synchronized的区别:

volatile仅能实现变量修改的可见性,并不能保证原子性,而synchronized不仅可以保证变量修改的可见性,还可以保证原子性

volatile不会造成线程的阻塞,而synchronized可能会造成线程阻塞

volatile标记的变量不会被编译器优化,synchronized会被编译器优化

线程和进程相似,但线程是一个比进程更小的执行单位,一个进程在执行的过程中可以产生多个线程。
为什么等待和通知中是在Object中而不是在Thread中

任何的对象都可以作为锁对象,每个对象都有对象头 Mark Word信息,存放锁信息,而Java的每个类都会继承Object类,所以等待和通知是在Object类定义

8. IO流
按照流的流向分,可分为输出流、输入流
按照操作单元分,可分为字符流、字节流
按照流的角色分,可分为节点流、处理流
io流一共涉及40多个类,这些类彼此之间都有紧密的联系,这40多个类都是从4个抽象类基类中派生出来的
  • InputStream/Reader:字节输入流/字符输入流

  • OutInputStream/Write:字节输出流/字符输出流

AIO(异步非阻塞)、BIO(同步阻塞)、NIO(同步非阻塞)的区别:

BIO:同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。

AIO:是在NIO的基础上引入异步通道的概念,实现异步非阻塞式的IO处理

NIO:是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。

同步与异步

  • 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。

  • 异步: 异步就是发起一个调用后,不用等待响应,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。

同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。

阻塞和非阻塞

  • 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。

  • 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

那么同步阻塞、同步非阻塞和异步非阻塞又代表什么意思呢?

举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(同步阻塞)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,但是需要时不时来看看水开了没有(同步非阻塞)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(异步非阻塞)。

NIO和IO的区别:

IO流是阻塞的,NIO流是不阻塞的,例如使用NIO,一个线程写入数据,它不需要等待完全写完,期间可以作别的事情

而使用IO流,当线程调用读或者写的时候,它会被阻塞,直到一些数据被读取,或写入。在这期间它不能做任何事情

IO面向流,NIO面向缓冲区,NIO类库加入了Buffer对象,读写数据时,它可以直接督导Buffer中进行操作,常见的缓冲区有byteBuffer

NIO有选择器,IO没有

NIO和AIO NIO:会等数据准备好后,再交由应用进行处理,数据的读取/写入过程依然在应用线程中完成,只是将等待的时间剥离到单独的线程中去,节省了数据准备时间,因为多路复用机制,Selector会得到复用,对于那些读写过程时间长的,NIO就不太适合。

AIO:读完(内核内存拷贝到用户内存)了系统再通知应用,使用回调函数,进行业务处理,AIO能够胜任那些重量级,读写过程长的任务。

NIO 读数据和写数据方式

通常来说NIO中的所有IO都是从 Channel(通道) 开始的。

  • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。

  • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据

NIO核心组件:Channel(通道)Buffer(缓冲区)Selector(选择器)

为什么要用NIO

首先看一下BIO,如果有一台服务器,能承受简单的客户端请求,那么使用io和net中的同步、阻塞式API应该是可以实现了。但是为了一个用户的请求而单独启动一个线程,开销会很大,虽然可以引用线程池,但是不适合大量的连接数,NIO采用的是一种多路复用的机制,利用单线程轮询事件,高效定位就绪的Channel来决定做什么,只是Select阶段是阻塞式的,能有效避免大量连接数时,频繁线程的切换带来的性能或各种问题。

9. 反射
概念:在运行状态中,对于任意一个类,都能知道它的属性和方法;对于任意一个对象,都能调用它的任意一个方法。在java中,只要给类定义一个名字,就可以通过反射机制来获取类的所有信息
常见的反射实现方式有:
  1. Class.forName (jdbc)

  2. 类名.class

  3. 对象名.getClass

  4. 基本类型的包装类,可以调用包装类的Type属性来获取该包装类的Class对象

哪里用到了反射机制:
  1. Web服务器中利用反射调用了Sevlet的服务方法。

  2. jdbc中,利用反射获取数据库驱动

  3. 很多框架都用到反射机制,注入属性,调用方法,如Spring

反射优缺点:

优点:能够运行时动态获取类的实例,提高灵活性,解耦

缺点:性能较低,需要解析字节码,将内存中的对象进行解析

相对不安全,破坏了封装性,因为通过反射机制可以获取私有属性和方法

10. 23种设计模式

单例模式、工厂模式、观察者模式、代理模式

web篇

1. servlet
servlet是服务器端的程序,动态生成html页面发送到客户端,但是会产生很多out.println(),java语言,html语言,混在一起很混乱,所以后来sun公司推出了JSP,JSP就是servlet,每次运行的时候,JSP都会首先被编译成.servlet文件,然后再被编译成.class文件,有了JSP之后,在MVC项目中servlet就不在负责动态生成页面,而去负责控制程序逻辑,控制JSP与javaBean之间的流转。
2. session与cookie
Session:服务端会话技术,在一次会话中的多次请求中共享数据,将数据保存在服务器中,当浏览器访问时,只需要从Session中查询客户的状态就可以了
如何保证是同一个Session:

在客户端第一次发送请求时,是没有请求头的,这时服务器会在内存中创建一个session对象,session对象会有唯一id,在服务器响应浏览器时,会发生送一个响应头和包含的session对应的SESSIONID,当下次访问当前服务器时,则会携带带有SESSIONID的请求头到服务器,服务器会自动获取信息,然后根据id查看有没有这个信息。一个浏览器只有一个session,session默认30分钟。

Cookie:客户端会话技术,服务器请求通知客户端保存键值的一种方式,由W3C组织提出,最早由Netscape社区发展的一种机制。

Cookie实际上是一小段的文本信息。客户端请求服务器,使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。一般浏览器限制一个站点保存20个cookie。

session与cookie有什么区别:
  1. cookie数据存放在浏览器上,session数据存放在服务器上

  2. cookie不是很安全,别人可以分析存在本地的cookie并进行cookie欺骗

  3. session会在一定时间内保存数据,当访问增多时,会占用性能

  4. 单个cookie存放的数据大小不超过4k

JVM篇

1. JVM内存结构

1、方法区:存放已被加载的类信息、常量、静态变量

2、虚拟栈:存放基本数据类型、对象的引用

3、堆:存放所有对象的实例、数组,GC回收的地方

4、计数器:用于记录当前线程执行的字节码

2. JVM一次完整的GC流程的组成,对象如何晋升到老年代

堆分为新生代、老年代、:其中新生代分为Eden、幸存者0区、幸存者1区,当Eden的空间满了,Java虚拟机会触发一次轻GC,收集新生代的垃圾,存活下来的,会转入到幸存者区,如果每次在Eden初始,并经过了轻GC仍然存活,并且被幸存者区容纳,年龄设为1.每次熬过一次轻GC,年龄+1,如果超过年龄限制(15),就会被晋升到老年区重GC发生在老年代

Java内存为什么要分成新生代、老年代、元数据、新生代中为什么又要分为Eden和Survivor

共享内存区=堆+元数据

元数据=方法区+其他

Java堆=老年代+新生代

新生代=Eden+幸存0+幸存1

为什么要分新生代和老年代

(1)新生代主要是为了存放新创建的对象,内存大小相对比较小,垃圾回收会比较频繁

(2)老年代主要存放JVM认为生命周期比较长的对象(经过几次轻GC仍然存活的对象),内存大小相对会比较大,垃圾回收也没有那么频繁

为什么新生代又要分为Eden和Survivor,为什么要设置两个幸存区

1、如果没有幸存区,Eden区每进行一次轻GC,存活的对象就会进入老年代,老年代很快就会被存满,触发重GC,老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比轻GC长的多,所以需要分为Eden和幸存区(由于Major GC除并发GC外均需对整个堆进行扫描和回收,因此又称为Full GC。)

2、幸存区的意义是在于减少被送到老年代的对象,进而减少Full GC的发生,只有经历16次轻GC还存活的对象,才会被送到老年代,设置两个幸存区最大的好处就是解决碎片化,在第一次进行轻GC后,存活下来的对象会进入到0区,eden清空,当满了之后再进行一次轻GC,Eden和0区存活下来的对象,又会被复制送入到1区,而这种复制算法保证了0区和1区占用连续的内存空间,避免碎片化

常见的垃圾收集器:

1、Seria收集器:新生代单线程收集器,标记和清理都是单线程,高效

2、CMS收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发。低停顿的特点

3、G1收集器(标记-整理算法):Java堆并行收集器,JDK1.7提供的新收集器,基于标记-整理的算法实现,减少产生内存碎片。而且G1的回收范围是整个Java堆

区别一: 使用范围不一样

CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用 G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用

区别二: STW的时间

CMS收集器以最小的停顿时间为目标的收集器。

G1收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)

区别三: 垃圾碎片

CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片

G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

Java类加载机制

虚拟机把描述的类数据从Class文件加载到内存中,进行校验,解析,最终可以被虚拟机直接使用的Java类型

什么是类加载器

实现通过类的权限定名获取类的二进制字节流的代码

类加载器有哪些

1、启动类加载器:用来加载Java核心类库,无法被程序直接使用

2、扩展类加载器:用来加载Java的扩展库

3、系统类加载器:根据Java应用的类路径来加载Java类,一般来说,Java应用的类都是由它加载的

4、自定义类加载器:通过继承lang包下的ClassLoad类的方法实现

JVM性能调优

1、设定堆内存大小 :-Xmx

2、设定新生代大小,新生代不宜太小,否则会有大量对象进入老年代:-XX:NewSize

3、设定垃圾回收器

JVM调优工具

常用调优工具分为两类:JDK自带监控工具:jconsole和jvisualvm,第三方的有:MAT,GChisto

jconsole:用于对JVM中的内存、线程和类进行监控

jvisualvm:可以分析:内存快照、线程快照;监控内存变化,GC变化等

MAT:基于Eclipse的内存分析工具,可以查找内存泄漏和减少内存消耗

GChisto:分析GC日志的工具

JVM结构:方法区(类加载的值放里面,常量,静态的东西),堆栈,本地方法区,计数器,本地方法栈(底层)

一个JVM只有一个堆,JVM调优针对的是堆,

框架篇

1. spring
a.什么是spring
2002年首次推出了Spring的雏形框架 interface21框架
2004年3月24日诞生Spring1.0
spring是最受欢迎的一款企业级java应用程序开发框架,是一款轻量级开源框架,基础版本只有2MB大小,创建性能好,易于测试, 可重用,它的核心特性是是可以开发任意Java程序,常见的配置方式有XML配置、注解配置、基于Java配置。

Spring主要由以下几个模块组成:

Spring Core:核心类库,提供IOC服务

Spring Context:提供框架式的Bean访问方式

Spring AOP

Spring Dao

Spring ORM

Spring Web

Spring MVC

b .AOP与IOC

在使用Spring框架过程中,主要就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的重要部分,主要用到的设计模式有工厂模式个代理模式。

IOC:控制反转也叫依赖注入,工厂模式+反射机制,通过sqlsessionfactory来注入实例,IOC容器是Spring的核心,将对象交给容器管理,只需要在spring配置文件中配置对应的bean,以及设置对应的属性,让spring容器来生成类的实例对象以及管理对象。在容器启动的时候,spring会把配置文件初始化,当需要调用的时候,就把它初始化的那些bean分配给你,就不需要去new对象了

为什么不直接用工厂模式而用IOC:因为IOC是通过反射机制来实现的。当我们的需求出现变动时,工厂模式会需要进行相应的变化。但是IOC的反射机制允许我们不重新编译代码,因为它的对象都是动态生成的。以创建手机对象作为例子,假设在没有使用反射的情况下,在创建对象时我们需要判断创建16G的手机对象还是32G的手机对象。但是用了反射机制之后,我们可以通过配置文件实现具体内存的手机,而不需要在代码中修改,而且此时再增加128G内存的手机也可以得到很好的扩展,代码维护更加方便。

AOP:AOP是对OOP的补充和完善,OOP是面向对象,它定义了纵向的关系,但不利于定义横向的关系,这导致了大量代码的重复,不利于各个模块的重用,AOP则是横向,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

实现AOP技术主要分为两大类:动态代理和静态织入

动态代理是利用截取消息的方式,对该消息进行装饰,然后取代原有对象;

静态织入是引入特定的语法创建“方面”,从而使编译器在编译期间织入

c. ioc依赖注入的几种方式
  1. 构造器注入:将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入

  2. setter方法:IOC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类

  3. 接口注入:依赖类必须要实现指定的接口,然后实现该接口的一个函数,这个函数就是用于依赖注入,函数的参数就是注入的对象

d. 如何加载Bean (【Spring】详解Spring中Bean的加载 - Natee - 博客园 (cnblogs.com)

加载bean主要步骤有:

  1. 转换beanName,因为平时开发中传入的name可能是别名,也可能是FactoryBean,所以需要进行解析转换。(消除修饰符、去alias表示的beanName)

  2. 从缓存中加载实例,实例在srping容器中只会被创建一次,如果再想获取bean时,就会尝试从缓存中获取,获取不到的话再从singletonFactories中加载

  3. 实例化bean,缓存记录中的bean一般只是最原始的bean状态,所以需要进行实例化

  4. 检测parentBeanFactory,beanName检测如果当前加载的配置文件中不包含beanName对应的配置,就只能到parentBeanFactory去尝试加载bean

  5. GernericBeanDfinition转换成RootBeanDefinition,因为配置文件中读取到的bean信息时存在GernericBeanDfinition中的,但bean的后续处理是针对RootBeanDefinition,所以需要转换才能进行后续操作

  6. 初始化依赖的bean的属性,就是bean中可能依赖了其他bean属性,在初始化bwan之前会先初始化bean所依赖的bean属性

  7. 创建bean,Spring容器根据不同scope创建bean实例

Spring AOP里面的几个名词的概念: (1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。

(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。

在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。

(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。

切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add、search。annotation方式可以指定被哪些注解修饰的代码进行拦截。

(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。

(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。

(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。

(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

Spring容器加载流程:

1、初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中

2、将配置类的BeanDefinition注册到容器中

3、调用refresh()方法刷新容器

BeanFactory和ApplicationContext有什么区别:

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当Spring的容器

1、BeanFactory是Spring里面最底层的接口,是IOC的核心,定义的IOC的基本功能,包含了对各种Bean的定义、加载、实例化、依赖注入等,ApplicationContext接口作为BeanFactory的子类,处理提供BeanFactory的所有功能,还提供了更完整的框架,例如可以在监听器中注册Bean的事件,而且继承了MessageSource,因此支持国际化

2、BeanFactory需要手动注册,ApplicationContext则是自动注册

3、BeanFactory采用的是延迟加载来注入Bean,只有在调用某个Bean时,才会进行实例化,ApplicationContext是在容器启动的时候,一次性创建所有的Bean,所以它的不足就在于占内存,当有多个Bean的时候,程序启动比较慢

Bean的生命周期:

1、实例化Bean

2、设置依赖注入,实例化后的对象被封装在BeanWrapper对象里,通过BeanWrapper提供接口完成依赖注入

3、处理Aware接口,Spring会检测对象是否实现的Aware类型的接口,通过这个接口,可以拿到Spring容器的一些资源:例如实现了BeanNameAware接口,会调用它的setBeanName方法,传入Bean的名字,实现了ApplicationContextAware接口。会调用setApplicationContext方法,传入Spring上下文

4、BeanPostProcessor前置处理和后置处理:如果想对Bean进行自定义的前置处理,可以实现BeanPostProcessor接口

5、InitializinaBean:如果Bean实现的InitializinaBean接口,执行afeterPropertiesSet()方法

6、DisposableBean:当Bean不再需要时,会经过清理阶段,如果实现了DisposableBean接口,会调用实现的destory()方法

7、销毁:配置destory-method,会自动进行销毁

Spring基于注解的自动装配:

可以使用@Autowried和@Resource

@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。

(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

Spring中bean的作用域: (1)singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。

(2)prototype:为每一个bean请求创建一个实例。

(3)request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

(4)session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。

(5)global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

2. SpringMVC
a. 流程
  1. 用户发送请求到前端控制器(DispatcherServlet)

  2. DispatcherServlet收到请求后调用处理器映射器(HandlerMapper)

  3. 处理器映射器找到具体的处理器(可以根据xml配置或注解查找),生成处理器对象及处理拦截器,一起返回给DispatcherServlet

  4. DispatcherServlet调用处理器适配器(HandlerAdaper)

  5. 处理器适配器经过适配调用Controller

  6. Controller执行完后返回ModelAndView

  7. 处理器适配器将Controller执行结果ModelAndView返回给前端控制器

  8. 前端控制器将ModelAndView传给(视图解析器)ViewReslover

  9. 视图解析器解析返回具体View

  10. 前端控制器根据View进行渲染视图

  11. 最后前端控制器响应给用户

b. 常用注解作用
@RequestMapping:用于处理请求url映射的注解,可用于类或方法上,用在类上,就表示类中的所有请求都以这个地址作为父路径

RequestMapping注解含有6个属性:

  1. value:指定请求的地址

  2. method:指定method类型,例如Get、Post、Put、Delete等

  3. consumes:指定请求提交内容的类型,例如json、text

  4. produces:指定返回的内容类型

  5. params:指定请求中包含的参数,只有在request中必须要某些参数时可使用

  6. headers:指定request中必须包含某些指定的header值

@RequestBody:实现接受http请求的json数据,将json转换成java对象
@ResponseBody:通过springmvc提供的HttpMessageConverter接口实现,将controller方法返回对象转换为json对象响应
到用户端
@RestController:包含了@controller和@responseBody
@Autowired:这个注解帮我们省略了setter、getter方法,它会自动帮我们配置
3. mybatis
a. 什么是mybatis

1、MyBatis是半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,而不用去加载驱动、创建连接、创建statement等一些繁琐的操作

2、MyBatis可以使用XML或注解来配置和映射原生信息,将POJO映射成数据库的记录,避免了几乎所有的JDBC代码和手动设置参数

b. type与map

MyBatis在查询过程中,有两个返回类型,分别是resultType和resultMap,resultType是直接表示这个方法的返回类型,resultMap是 将数据库中列数据复制到对象的相应属性上,可以用于复制查询,两者不能同时用。

resultType适合返回值是JDK提供类型的情况,resultMap适合自定义实体类的情况

4. springBoot
a. springboot核心注解 (Spring Boot 最核心的 25 个注解,都是干货! - 知乎 (zhihu.com)
  1. @SpringBootApplication:这是springboot最核心的注解,用在springboot主类上,标识这是一个springboot类

  2. EnableAutoConfiguration:自动配置注解,使用这个注解后,springboot就可以当前类路径下的包或者类配置spring bean

  3. @ComponentScan:主键扫描,用来代替配置文件中的component-scan,可以自动扫描包路径下的@Component注解进行实例化

  4. @Configuration:一般用于类上,标识该类是一个配置类,可以把它当成applicationContext.xml配置文件

b. springboot starters

springboot启动器,SpringBoot官方的启动器都是以spring-boot-stater命名的,它包含了一系列可以集成到应用里面的依赖,可以一 站式集成spring及其他技术,而不需要到处去找依赖,例如你想用spring JPA 访问数据库,只要加入spring-boot-data-jpa依赖包就能 用了,stater包含了许多项目中需要用到的依赖,这些都是支持的

数据库篇

1. 事务

数据库事务简单来说就是:多条sql语句,要么全部成功,要么全部失败

数据库事务包含四大特性:

  1. 原子性:这个特性指包含事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须全部应用到数据库,如果失败也不能影响数据库

  2. 一致性:指事务必须使数据库从一个一致性状态转为另一个一致性状态,就是说事务执行前到执行后都得是一致性,例如转账,加入A和B两个人账户加起来是5000,那么不管A和B之间怎么转账,总数都不会变

  3. 隔离性:指当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户同时开启事务,不能被其他的事务所干扰,多个并发事务之间需要隔离,如果不考虑隔离性,就会发生脏读,不可重复读,丢失修改,幻读等情况。

  4. 持久性:指一个事务一旦提交成功了,那么对这个数据的改变就是永久性的,即便数据库遇到故障了也不会发生丢失提交事务的问题

数据库事务常用的四种隔离级别:

  1. 读取未提交:在这种级别下,select语句不加锁。但是可能会读取到不一致的数据,即“脏读”。读取未提交是并发最高,一致性最差的隔离级别

  2. 读取已提交:可避免脏读的发送,但是在互联网大数据量,高并发量的情况下,基本不会用到这两个级别

  3. 可重复读:mysql默认隔离级别,避免了脏读,不可重复读的情况

  4. 串行化:避免了脏读,幻读,不可重复读的情况,是四种隔离级别最高的级别,在Oracle中,只支持串行化和可重复读级别,默认是可重复读

并发事务带来的问题:

  1. 脏读:一个事务看到了另外一个事务未提及的更新数据,例如A给B转账,A还没转过去,B就已经收到了

  2. 丢失修改:两个事务同时更新一组数据,最后一个事务的更新会覆盖第一个事务的更新,从而导致了第一个事务更新的数据丢失,例如A查询余额是500元,B查询余额也是500元,B充值了200并且提交事务,然后A消费了100,但是他又撤销了,这时候作为最后提交事务的A,他数据更新了,但是B充值的200元丢失了

  3. 不可重复读:在同一事务中,两次读取同一数据,得到的内容不同,也就是有其它事务更新修改了数据,例如事务A查询到一条记录,事务B更新了A查询到的数据,并提交了事务,结果A再去查询这条数据时,发现数据不同了,这就叫不可重复读

  4. 幻读:第一个事务开始时读取到了一批数据,但此后又一个事务更新插入了一批数据,此时第一个事务又读取到这批数据,但发现数据多了一批

2. 索引

数据库索引是数据库管理系统中一个排序的数据结构,以加快查询速度,更新数据库中的数据。mysql的常用索引是B+数

使用索引的好处:

  1. 使用索引查询,可以提高性能

  2. 通过创建唯一性索引,可以保证数据表每一行的唯一性

  3. 在使用分组和排序时,可以减少查询时间

使用索引的缺点:

创建索引比较耗费资源,一是增加了数据库的存储空间,二是在插入和删除时,要花费较多的时间维护

为什么用 B+ 树做索引而不用哈希表做索引? 1、哈希表是把索引字段映射成对应的哈希码然后再存放在对应的位置,这样的话,如果我们要进行模糊查找的话,显然哈希表这种结构是不支持的,只能遍历这个表。而B+树则可以通过最左前缀原则快速找到对应的数据。 2、如果我们要进行范围查找,例如查找ID为100 ~ 400的人,哈希表同样不支持,只能遍历全表。

使用B+数索引的好处使用B+树存储数据可以让一个查询尽量少的读磁盘

索引保存数据的方式一般有两种:

  • 数据区保存id 对应行数据的所有数据具体内容。

  • 数据区保存的是真正保存数据的磁盘地址。

B+数的储存结构:

所有关键字存储在叶子节点,非叶子节点不存储真正的data

为所有叶子节点增加了一个链指针

3. 分页

实现分页的条件就必须知道哪一页的数据,从哪里开始到哪里结束,以及每页数据量。

分页实现原理,必要的五个变量:

  1. 数据总量 (TotalSize) 查数据库

  2. 每条页面数据 (PageSize)自定义

  3. 总页数 (TotalPage)自己算

  4. 当前页 (CurrentPage)自定义

  5. 当前页的数据集合 (list)

4. mysql与orcal区别
  1. 默认端口不同:mysql默认端口3306,默认用户root,oracle默认端口1521,默认用户system

  2. 空间大小不同:mysql安装后差不多一两百兆,Oracle差不多3个G

  3. 数据库的层次结构不同:mysql默认用户root,用户下可以创建多个数据库,每个数据库下还有多张表,不会创建多个用户,Oracle创建一个数据库,数据库下有多个用户,System,Scott等等,不同用户下有多张表

  4. 事务提交不同:mysql默认自动提交,可以修改为手动提交,Oracle默认不自动提交,需要手动提交,需要写commit或点击commit按钮

5. 数据库优化,sql优化,大表优化
数据库优化:
  1. 选择最合适的字段大小

  2. 在数据库设计时,不要让字段有NULL指,这样会使复合索引失效

sql优化
  1. 查询语句尽量不要使用select*

  2. 减少子查询,使用关联查询

  3. 减少使用IN或NOT IN,使用exists、not exists代替

  4. or的查询尽量用union或union all代替

  5. 应尽量避免在where字句中使用!=或<>操作符,这样会使索引失效

  6. 尽量避免在where字句中对null的判断,这样也会使索引失效

  7. 使用like查询时,尽量不要用百分号包起来,因为以百分号开头无法命中索引,如果实在要用的话,可以替换成Lucene

大表优化
  1. 限定数据范围:禁止不带任何限制数据范围条件的查询语句,比如:在用户查询订单历史的时候,可以限定范围在一个月以内

  2. 读/写分离:主库负责写,从库负责读

  3. 垂直分区:将写入操作比较频繁的表,如用户表和订单表,将这两个表分离出来,放在不同的服务器,虽然进行查询消耗性能,但是对于数据量大的情况下来说,减轻了不少负担

  4. 水平分区:垂直分区用来负责数据量大的情况,但如果有了更大的数据量的表,这时候垂直分区就撑不住了,所以就需要水平分区,在数据结构不变的情况下,把每一片数据分散到不同的表或库中,解决数据量过大的问题,但是也存在问题,这样会带来逻辑,部署,运维的各种复杂度,尽量不要对数据进行分片

索引优化:

在创建索引的时候,判断哪些字段需要索引

1、简单,不容易被改变,经常用

2、索引不是越多越好,根据数据量判断,数据量少就不需要创建,只有当数据量达到一定的程度时候创建

redis篇 (Remote Dictionary Server)

1. 数据类型及其应用场景
  1. string(字符串):常用命令:set 、get、decr(自减)、incr(自增)、mget (同时获取多个key)

    时效性设置,例如某电商商家开启热门推荐,但是它不会一直显示热门,可能三天后,七天后失效,所以可以使用string设 置数据的失效时间 (setx name 10s)

  2. list(列表):常用命令:lpush (设置key的多个值到头部)、rpush (从尾部插入值)、lpop(删除并返回第一个元素)、rpop(删除最后并返回元素)、lrange(遍历)

    微信朋友圈点赞,要求按照点赞顺序显示好友信息,当好友取消点赞时,移除好友信息 (lrem key count 1)

  3. hash(哈希):常用命令:hget、hset、hgetall(获取指定的fieldvalue)

    用来存储对象信息,例如储存用户信息,redis中的hash实际是内部储存的value为一个hashMap,把一个key作为用户id,value,就是用户的多个信息,使用键值对保存

  4. set(无序不可重复集合):常用命令:sadd、spop(删除第一个集合)、smembers(查看指定集合的所有值)、sunion(查并集)
  5. Sorted Set(有序不可重复集合):常用命令:zadd、zrange(返回指定key对于的集合)、zrem、zcard(返回指定集合包含的元素)

    进行大数据量的排序,sorted set可以通过用户额外提供一个优先级(score)的参数来为自动排序

2. 持久化机制
Redis官方提供了两种不同的持久化方法将数据储存到磁盘:
  1. RDB持久化机制(redisDateBase):

    特点:每隔一段时间保存

  2. AOF持久化机制:

    特点:一秒一存

在开发中用什么持久化机制:根据业务场景不一样:例如不重要的数据,不需要同步,可以用RDB,效率高

例如在前端需要实时更新的数据,就用AOF机制

Redis可以做发布订阅:redis server :发送者 (publish) 发送消息,订阅者 (subscribe ) 接收消息,都是服务器的客户端

3. 高可用
4. 集群

redis3.0版本之前只支持单例模式,在3.0版本及以后才支持集群

集群部署:

1、安装ruby脚本(yum install ruby)

2、然后把ruby相关的包安装到服务器(gem install redis-3.0.0.gem)

3、把这个ruby脚本工具复制到usr/local/redis-cluster目录下

4、最后连接集群节点,连接任意一个即可

redis包含三种集群策略

1、主从复制:主库(master)负责写,从库(slave)负责读

主从复制特点:

主库读写数据,一般会同步返回数据库

从库一般只负责读,并且接收主库同步过来的数据

一个主库有多个从库,但一个从库只能有一个主库 (因为一般只查,所以主库少,从库多)

2、哨兵:主要为了监控redis系统的运行情况和选举功能,它的作用:

1、监控主从数据库是否正常运行

2、当主库出现问题时,自动将从库转换为主库

3、多哨兵配置的时候,哨兵之间也会自动监控

4、多个哨兵可以同时监控一个redis

哨兵工作机制:哨兵启动时,只需要找到主库的端口即可进行监控,一个哨兵可以监控多个主库,只需要提供多个配置

哨兵启动后,会与要监控的master建立俩条连接:

一条连接用来订阅master的sentinel:hello频道与获取其他监控该master的哨兵节点信息 另一条连接定期向master发送INFO的命令获取master本身的信息 与master建立连接后,哨兵会执行三个操作,这三个操作的发送频率都可以在配置文件中配置:

定期向master和slave发送INFO命令(可以获取主库最新信息,进行相应操作,角色更改等等) 定期向master和slave的sentinel:hello频道发送自己的信息(可以与同样监控这些数据库的哨兵共享自己的信息,发送内容为哨兵的ip端口、运行id、配置版本、master名字等等) 定期向master、slave和其他哨兵发送PING命令(可以定时监控这些数据库和节点有没有停止服务)

5. 击穿,穿透,雪崩,热数据,冷数据
雪崩:热点数据同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉
解决办法:事前尽量保证redis集群的高可用性,选择合适的淘汰策略,事中,本地ehcache+hystrix限流,避免mysql崩掉,事后,利用redis持久化机制保存的数据尽快恢复缓存
穿透:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,导致数据库崩掉
解决办法:采用布隆过滤器,将所有可能存在的数据以哈希储存到一个足够大的bitmap中,不存在的数据bitmap会拦截下来
击穿:指缓存中没有但数据库中有的数据(一般是缓存时间到期,这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决办法:设置热点数据永不过期,接口限流,降级,加互斥锁

Linux篇

1. 常用的基本命令与作用
  1. 查看linux 系统:uname -a

  2. 查询端口状态:netstat -an |grep 12354

  3. 打印出所有进程间通信方式的信息:lpcs -a

  4. ipcrm -q msgld:移除使用msgid创建的消息队列

  5. (-a(all)):查看所有连接

  6. Netstat -at (all tcp)只列出tcp连接

  7. -p:查看进程信息

  8. grep:查看服务是否在运行

2. 软件安装部署

了解篇

1. springCloud
2. MQ (ACK:签收,NACK不签收)
为什么使用MQ

解耦、异步、削峰

1、解耦:一个系统,一个模块,调用多个系统或者模块,相互之间的调用很复杂,维护起来很麻烦,但是这个调用不需要同步调用接口,就可以用MQ来给它异步解耦,例如A系统发送一条数据,发送到MQ里面去,哪个系统需要就自己去MQ里消费,如果不需要了,就取消对MQ消息的消费即可,这样A系统就不需要去考虑给谁发送,不需要去维护

2、异步:A系统发送一个请求,需要在自己本地写库,还需要再BCD三个系统写库,自己本地写库要3ms,BCD三个系统分别写库要300ms,400ms,200ms,这样请求延时差不多要1s,降低了速度,如果使用MQ,那么A系统连续发送3条请求到MQ队列中,假如耗时5ms,那么一共就只需要耗时8ms,大大加快了速度

3、削峰:减少高峰时期对服务器的压力

MQ优缺点
  • 系统可用性降低:系统引入的外部依赖越多,越容易挂,万一MQ挂了,MQ一挂,整套系统就崩溃了

  • 系统复杂度提高:加MQ进来,要考虑如何保证消息没有重复消费,怎么处理消息丢失,怎么保证消息传递的顺序性

  • 一致性问题:A系统处理完了直接返回成功,别人就以为这个请求成功了,但如果BCD,其中BD两个系统写库成功,C失败了,怎么处理一致性问题

Kafka和RabbitMQ的区别

1、Kafka支撑高吞吐,RabbitMQ比它第一个数量级,对于延迟量来说RabbitMQ是最低的

2、RabbitMQ支持持久化消息,避免机器在不可抗力的因素下挂掉,消息不会消失

3、RabbitMQ比Kafka成熟,在可用性上、稳定性上、可靠性上,RabbitMQ胜于Kafka

4、Kafka的定位主要是在日志方面,因为Kafka设计的初衷就是处理日志,针对性比较器,所以业务方面还是建议选择RabbitMQ

如何保证高可用

RabbitMQ是比较具有代表性的,因为是基于主从做高可用的,RabbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式

一般实现高可用用的是镜像集群模式,跟普通集群模式不一样,在镜像集群模式下,创建的queue,无论元数据还是queue里面的消息都会存在于多个实例上,就是说,每个节点上面都有完整的queue镜像,都有它的全部数据,每次写消息到queue中,都会自动把消息同步到多个实例上queue上。

如何保证消息的可靠性传输,丢了怎么办

数据丢失问题可能会在生产者。MQ。消费者中

生产者丢失:生产者将数据发送到RabbitMQ的时候,可能数据在中途就丢失了,因为网络问题等等,为了避免这种问题发生,可以使用Rabbit'MQ提供的事务功能,在发送数据之前开启事务channel.txSelect,如果没有成功接收,就回滚,重新发送,但是这样比较消耗性能,另外还可以开启confirm模式,在生产者设置开启,每次写的时候都会分配一个唯一id,如果写入到了RabbitMQ中,就会响应一个ack消息,如果失败了,就会回调一个nack接口,告诉你失败了,重试,还可以根据id状态设置超时时间,事务机制和confirm机制最大的不同建就是事务机制是同步的,confirm机制是异步的,它发送一个消息之后可以发送下一个消息,而事务会被阻塞

MQ中丢失:就是RabbitMQ自己弄丢了,这时候就需要开启RabbitMQ持久化,将消息持久到磁盘,哪怕是挂了,也不会丢失数据,设置持久化有两个步骤,创建queue的时候设置为持久化,保证RabbitMQ持久化的queue的元数据,第二个发送消息的时候将消息的deliveryMode设置为2,将消息设置成持久化到磁盘里,必须要同时设置这两个持久化,这样RabbitMQ挂了之后,再次重启,也会从磁盘恢复queue,恢复queue的数据

消费端丢失:在消费的时候,刚消费到,还没处理,结果进程挂了,这时候需要使用RabbitMQ提供的ack机制,关闭RabbitMQ的自动ack,通过api来调用,在程序里ack一次,如果没处理完,就不会有ack,RabbitMQ就不会认为处理完了,这个时候RabbitMQ会把消费分配给别的consumer处理,消息就不会丢失

如何保证消息顺序性

拆分多个queue,每个queue一个consumer,然后这个consumer内部用内存队列做排队,分发给底层不同的worker来处理

如何解决消息队列的过期失效问题,满了怎么处理,出现积压怎么办?

消息积压处理:临时紧急扩容

  • 先修复consumer的问题,确保恢复消费速度,如何将现有的consumer停掉

  • 新建一个topic,partition是原来的10倍,临时建立原先10倍的queue数量

  • 然后写一个临时的consumer程序,部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀写入到临时创建的10倍queue中

  • 接着临时调用10倍的机器来部署consumer,每一批consumer消费一个临时的queue,以正常的10倍速度来消费

  • 等快速消费完后,恢复原来部署的结构,重新开启原先的consumer机器来消费

MQ消息失效:假设用的是RabbitMQ,它是可以设置过期时间的,也就是TTL,如果消息在queue中积压一定时间,就会被RabbitMQ清理掉如果有大数据量积压在MQ里,就可以采用一个方案,批量重导直接丢弃数据,等过了高峰期,在临时把那些丢掉的数据,查出来,然后重新灌入到MQ中

3. zookeeper与dubbo
4. Nginx

面试常见题

多线程
  1. 线程和进程的区别:
    1. 根据区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

    2. 包含关系:如果一个进程包含多个线程,执行过程是多条线程完成,线程是进程的一部分,所有线程也被称为轻权进程或者轻量级进程

    3. 内存分配:同一进程的线程共享资源,而进程之间的地址空间和资源是相互独立的

  2. Java是如何实现线程安全的?哪些数据结构是安全的?

    1、使用锁机制,用同步锁,lock锁给共享资源加锁

    2、使用Java提供的安全类,util下面的concurrent包下的类自身就是线程安全的,在保证安全的情况下还能保证性能

    常见的安全数据结构有:ConcurrentHashMap、Atomicinteger(队列)

  3. 什么是线程死锁?如何避免线程死锁?

    多个线程同时被阻塞,它们中的一个或者全部都在等待这个某个资源释放,由于线程被无限期的阻塞,因此程序不能被正常终止

    死锁必须具备四个条件:

    1.互斥条件:该资源任意时刻只有一个线程占用

    2.请求与保持条件:一个进程因请求资源被阻塞时,对已获得的资源保持不放

    3.不剥夺条件:线程已获得的资源在没使用完之前不能被其他 线程强行剥夺,只有在自己用完的时候才释放资源

    4.循环等待条件:多个进程之间形成一种头尾相接的循环等待 资源

    如何避免死锁:只要破坏4个条件其中1个就可以了,例如通过锁排序法,获取指定锁的顺序,比如只要A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁
    5.什么是线程安全:布莱恩·格茨说过线程安全并非是非真即假的概念,而是按照安全程度来来排序,分为了5类
    1. 不可变:指不可变的对象一定是线程安全的,无论是对象的实现还是方法的调用,都不需要采取任何的线程安全保障措施

    2. 绝对线程安全:这个程度完全满足了作者对线程安全的定义,这是个很严格的定义:一个类要达到不管运行时环境如何,调用者都不需要采取额外的措施

    3. 相对线程安全:就是我们通常意义尚讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,在调用时不需要做额外的保障措施,但是对于一些特定顺序的连续调用,就需要使用额外的同步手段来保证正确性,例如Vector、HashTable、Collections的synchronizedCollection() 方法包装集合等

    4. 线程兼容:指本身并不是线程安全的,但可以通过一些同步手段来保证对象在并发环境下可以安全的使用,例如ArrayList、HashMap等

    5. 线程对立:指无论调用者是否采取了同步措施,都无法在多线程并发环境下使用,由于java本身就具备多线程性,基本会出现很少的排斥多线程的代码

    sleep()方法和wait()方法的区别和相同点
    区别:

    1.sleep()方法是Thread类的静态方法,wait()方法是Object的方法,必须与synchronized一起使用

    2.sleep()方法没有释放锁,而wait方法释放了锁

    3.sleep通常被用于暂停,wait通常被用于线程间的通信

    相同点:两者都可以暂停线程的执行
多线程怎么使用,在什么场景使用

1.在系统出现了阻塞的情况下,就可以根据实际情况使用多线程提高运行效率

2.连续的操作,需要花费大量的时间操作才能完成

3.tomcat内部采用的就是多线程,例如上百个人同时访问一个web应用,tomcat接入后都是把后续的处理交给新的线程,线程来调用 doGet或者doPost,如果不使用多线程,上百个人同时访问的话,tomcat就得排队串行处理了,那样速度会变得很慢

#{}和${}的区别

1.#{}是预编译,${}是字符串替换

2.Mybatis在处理#{}时,会将sql中的#{}替换成?号,调用PreparedStatemet方法来赋值

3.Mybatis在处理${}时,就是把里面的值替换成变量

4.使用#{}可以防止sql注入,提高安全

泛型,即“参数化类型”就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)

集合
Collectoin框架中实现比较如何实现

1. 内部比较器:实体类实现Collection<T>接口,并实现compareTo(比较)方法,如果希望在实体类里放集合排序,就可以实现Collection接口

2.创建外部比较器:在外部比较器实现compare()方法,如果只需要比较两个实体类的大小,返回结果,就可以用compare

接口可以有返回值吗 1.8之后可以允许接口有一个默认的实现方法

HashMap和HashTable的区别
  1. 继承父类不同:HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时实现 了map、Cloneable(可复制)、Serializable(可序列化)这三个接口

  2. 对外提供接口不同:Hashtable比HashMap多提供了elments() 和contains() 两个方法。 elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的 value的枚举。 contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上, contansValue() 就只是调用了一下contains() 方法。

  3. 对null的支持不同:HashTable的key和value都不能为空,HashMap的key可以为空,但只有一个key能为空,因为要保证key的唯一性,但是多个key的值可以null

  4. 安全性不同:HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要自己 处理多线程的安全问题。 Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中,当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。 ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为 ConcurrentHashMap使用了分段锁,并对整个数据进行锁定。(默认16个桶,支持16个线程进行并发操作)

IO流
IO中的flush()和close()有什么区别

close()方法具备刷新功能,在关闭流之前就会先刷新缓冲区,将缓冲区的字节全部刷新到文件上,再关闭流。close()方法包含一 次flush()方法

flush()方法可以刷新,并且刷新之后可以继续写,而close()方法刷新之后就不能继续写了。

Files的常用方法有哪些
  1. Files.exitis:检测文件路径是否存在

  2. Files.caretaFile:创建文件

  3. Files.read():读取文件

  4. Files.write():写入文件

String、StirngBuilder、StirngBuffer的区别:

1.String声明的是不可变的对象,每次操作都会产生一个新的对象,StringBuffer和StringBuilder都是继承AbstractStringBuilder类

2.StringBuffer具备线程安全,因为它的方法加了同步锁,而StringBuidler是线程不安全的,因为它的方法没加锁

3.StringBuffer和StringBuilder底层都是可变的字符数组,所以在进行频繁的操作时,推荐使用这两个

StirngBuffer常用方法:tostring()、append、substring、indexOf

StringBuilder常用方法:insert、delete、append

String常用方法:length、substring、equals、split、tochararray

Array和ArrayList的区别:ArrayList本质就是一个数组,但是它内部封装了Object类数据

Array是一个固定数组,而ArrayList是可变长度的

Array可以包含基本类型和对象类型,ArraList只能包含对象类型

接口和抽象类的区别:接口不能包含静态代码块和静态方法,抽象类可以

一个类只能继承一个抽象类,但可以实现多个接口

抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量

final用法:被final修饰的类不可以被继承

被final修饰的变量不可以被改变,如果要引用,那么表示引用不可变,但指向的内容可变

被final修饰的方法不可以被重写

bean四大作用域:request、page、session、appliaction

int和integer的区别:int是基本数据类型,integer是包装器类型

int默认为0 integer默认null

int不需要实例化,integer需要实例化

迭代器:迭代器包含三个属性,hasnext()、next()、remove

JDK与JRE的区别:JDK是编译环境,JRE是运行环境,JDK是一个工具包,包含了JRE,JRE包含了JVM虚拟机和一些基础类库

如果你需要运行java程序,只需安装JRE就可以了。如果你需要编写java程序,需要安装JDK。

抽象类和抽象方法的区别

1、在类中没有方法体的叫抽象方法,含有抽象方法的类叫抽象类

2、抽象类的抽象方法必须被实现

3、用abstract修饰的类,叫抽象类,用abstract修饰的方法叫抽象方法,如果一个子类没有实现父类的抽象方法,那么子类也变成了抽象类

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值