四、多线程 【25道】
- 聊一聊并行和并发的区别?
并发是指同一时间一起发生的.
例如: 一个处理器同时处理多个任务叫并发处理, 同一时间好多个请求一起访问你的网站叫做并发请
求等
并行是指多个任务在同一时间一起运行.
例如: 多个处理器或者是多核的处理器同时处理多个不同的任务, 这叫并行执行. - 线程和进程的区别?
进程是执行中的一个程序, 而一个进程中执行的一个任务即为一个线程
一个线程只属于一个进程, 但一个进程能包含多个线程
线程无地址空间, 它包括在进程的地址空间中
线程的开销比进程小 - 说一说什么是原子性、可见性、有序性?
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(Atomic、CAS算法、
synchronized、Lock)
可见性:一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile)
有序性:如果两个线程不能从 happens-before原则 观察出来,那么就不能观察他们的有序性,虚拟机
可以随意的对他们进行重排序,导致其观察结果杂乱无序(happens-before原则) - Java中实现多线程的方式
通过扩展Thread类来创建多线程:
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。
通过实现Runnable接口来创建多线程
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线
程执行体
通过线程池实现
定义线程池实例, 使用的时候从线程池中获取线程使用.
通过Callable和Future创建线程
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该
Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象的target创建并启动新线程。
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值 - Java中提供的线程池种类,及其特点!
五种线程池
Single Thread Executor : 这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这
个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执
行
代码: Executors.newSingleThreadExecutor()
Cached Thread Pool : 创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收
空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
代码:Executors.newCachedThreadPool()
Fixed Thread Pool : 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待, 代
码: Executors.newFixedThreadPool(4) 在构造函数中的参数4是线程池的大小,你可以随意设
置,也可以和cpu的核数量保持一致,获取cpu的核数量
代码 : int cpuNums = Runtime.getRuntime().availableProcessors();
Scheduled Thread Pool : 创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类
似于Timer。
代码:Executors.newScheduledThreadPool() - 线程池的工作原理以及重要参数含义
原理:
线程池会初始化一定数量线程, 每次使用线程从线程池中拿一个使用, 省去了创建线程的开销和时间,
使用完毕, 放回线程池中, 不销毁, 省去了销毁的开销和时间.
重要参数:
corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
unit:存活时间的单位
workQueue:存放任务的队列
threadFactory:创建线程的工厂
handler:拒绝策略 - 线程池的阻塞队列有哪些?
ArrayBlockingQueue : 数组有界阻塞队列FIFO, 按照阻塞的先后顺序访问队列,默认情况下不保证线程
公平的访问队列, 如果要保证公平性,会降低一定的吞吐量
LinkedBlockingQueue : 链表有界阻塞队列,默认最大长度为Integer.MAX_VALUE。此队列按照先进先
出的原则对元素进行排序SynchronousQueue : 不存储元素的阻塞队列
DelayedWorkQueue : 延迟获取元素队列,指定时间后获取,为无界阻塞队列。 - 线程池的拒绝策略有哪些?
拒绝策略核心接口:
java.util.concurrent.RejectedExecutionHandler
实现:
CallerRunsPolicy : java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy
当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程
执行,也就是谁提交任务,谁就负责执行任务。
AbortPolicy : java.util.concurrent.ThreadPoolExecutor.AbortPolicy
拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的
RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放
弃提交等策略。
DiscardPolicy : java.util.concurrent.ThreadPoolExecutor.DiscardPolicy
当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我
们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
DiscardOldestPolicy : java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy
如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的
任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长
的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。 - synchronized和reentranlock锁的区别?
Synchronized属于重量级锁, JDK早期版本使用了线程的状态变化来实现, JDK1.8中使用了自适应自旋锁
实现. 总体来说安全, 但是效率慢
Reentranlock是JUC包下的锁, 底层使用CAS + Volatile关键字实现. 效率高. - synchronized底层如何实现?什么是锁升级、降级!
jdk 中做了优化,提供了三种不同的 Monitor实现,分别是:
偏斜锁 (Biased Locking)
轻量级锁
重量级锁
所谓锁的升级,降级,实际上是 JVM 对 synchronized优化的一种策略,JVM 会检测不同的竞争状态,
然后自动切换到合适的锁实现,这种切换就是锁的升级,降级。
当没有出现锁的竞争时,默认使用的是偏斜锁。JVM 会利用 CAS 实现.
如果有另一个线程试图锁定某个以被加持偏斜锁的对象时,JVM 就需要撤销偏斜锁,并切换到轻量级锁
实现。如果获取成功,就使用轻量级锁,否者,进一步升级到重量级锁 - reentranlock的底层实现原理
reentranlock 是基于AQS(AbstractQueueSynchronizer) 采用FIFO的队列表示排队等待的线程.
AQS底层采用CAS(内存比较交换技术) + Volatile关键字实现. - volatile关键字的特点
保证线程之间的可见性,当一个线程对共享的变量进行了修改,其他的线程能够通过此关键字发现
这个修改
禁止指令重排序,编译器在编译的过程中会对程序进行优化,在保证结果不变的前提下,调整指令
执行的顺序,提高执行效率,如果加了volatile关键字,则会禁止指令重排序。
不能保证原子性 - Java为什么会指令重排
java中源代码文件会被编译成.class的字节码文件, 字节码指定在执行之前, jvm底层有内置的对字节码的
优化策略, 也就是指令重排机制, 会调整指令执行顺序. 目的是加快执行速度. - 悲观锁和乐观锁的区别
悲观锁 : 无论读还是更改对象都要加锁, 所以慢, 但是安全.
乐观锁 : 读不加锁, 更改对象加锁, 所以快, 但是没有悲观锁安全 - 什么是CAS, 以及它的ABA问题, 如何解决ABA问题
CAS的含义 :
CAS是compare and swap的缩写,即我们所说的比较交换。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值
和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第
一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能
机会执行。
CAS的ABA问题 :
CAS容易造成ABA问题。一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其
实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。在
java5中,已经提供了AtomicStampedReference来解决问题。
CAS造成CPU利用率增加。之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到
状态,cpu资源会一直被占用。 - Atomic变量如何保证的原子性
它底层是使用CAS + volatile关键字来保证原子性操作,从而达到线程安全的目的. - ThreadLocal是什么,有什么作用
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映
射,各个线程之间的变量互不干扰,在高并发场景下可以实现无状态的调用,特别适用于各个线程依赖
不通 的变量值完成操作的场景。
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的
ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全的问题了。 - ThreadLocal的内存泄漏问题
ThreadLocalMap 中的 key 是一个 ThreadLocal 对象,且是一个弱引用,而 value 却是一个强引
用。
存在一种情况,可能导致内存泄漏。如果在某一时刻,将 ThreadLocal 实例设置为 null ,即
ThreadLocal 没有强引用了,如果发生 GC 时,由于 ThreadLocal 实例只存在弱引用,所以被回收
了,但是 value 仍然存在一个当前线程连接过来的强引用,其不会被回收,只有等到线程结束死亡或
者手动清空 value 或者等到另一个 ThreadLocal 对象进行 get 或 set 操作时刚好触发
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 。。。。
}
ThreadLocal的内存泄漏问题
expungeStaleEntry 函数并且刚好能够检查到本 ThreadLocal 对象 key 为空(概率太小),这样才
不会发生内存泄漏。否则, value 始终有引用指向它,它也不会被 GC 回收,那么就会导致内存泄漏。
虽然发生内存泄漏的概率比较小,但是为了保险起见,也建议在使用完 ThreadLocal 对象后调用一下
remove 方法清理一下值。
作者:guozhchun - jdk1.8对锁进行了哪些优化
LongAdder 类似automicLong, 但是提供了“热点分离”。过程如下:如果并发不激烈,则与
automicLong 一样,cas赋值。如果出现并发操作,则使用数组,数组的各元素之和为真实value值,让
操作分散在数组各个元素上,把并发操作压力分散,一遇到并发就扩容数组,最后达到高效率。一般cas
如果遇到高并发,可能一直赋值失败导致不断循环,热点分离可以解决这个问题
stampedLock 改进读写锁,读不阻塞写。
completableFuture 对Future进行增强,支持函数式编程的流式调用 - 死锁发生的情况和如何避免死锁
死锁就是多个线程同时等待其他线程释放锁,导致被无限期阻塞的现象。
发生死锁的四个必要条件:
互斥条件
不可抢占条件
占有且申请条件
循环等待条件
避免死锁:
尽量避免一个线程同时获取多个锁
尽量避免一个线程在锁内部占用多个资源,尽量保证每个锁只占用一个资源
顺序加锁
加锁时限
死锁检验 - 介绍锁的升级过程
锁状态一种有四种 : 从级别由低到高依次是:无锁、偏向锁,轻量级锁,重量级锁,
锁状态只能升级,不能降级
升级过程 :
无锁状态 - > 当一个线程访问同步代码块时升级成偏向锁 - > 偏向锁 -> 有锁竞争时升级成轻量级锁 - >
轻量级锁 - > 自旋N次失败, 锁膨胀, 升级成重量级锁 - > 重量级锁 - 介绍锁的降级过程
锁降级
锁降级发生在读写锁中,写锁降级读锁的过程
读写锁 : ReentrantReadWriteLock
读写锁,既可以获取读锁,也可以获取写锁
写锁是独占锁,所谓独占即为独自占有,别的线程既不能获取到该锁的写锁,也不能获取到对
应的读锁。
读锁是共享锁,所谓共享即是所有线程都可以共同持有该读锁
锁降级过程 :
锁降级指的是写锁降级为读锁的过程,他的过程是持有写锁,获取读锁,然后释放写锁 - 怎么解决多线程的大量访问时的数据同步
可以加锁解决, 至于锁可以使用synchronized重量级锁, 也可以使用Lock轻量级锁
还可以使用线程安全的对象作为多线程共用的数据操作对象, 比如ConcurrentHashMap, 或者Atomic原
子操作类等
速度快慢则是, 线程安全操作对象ConcurrentHashMap或者原子操作类比轻量级锁快, 轻量级锁比重量
级锁要快. - 线程的 run()和 start()有什么区别
每个线程都是通过某个特定Thread对象所对应的run()方法来完成其操作的,方法run()称为线程体。也
就是run方法中是线程具体要执行的任务或者业务.
Start方法是启动线程, 调用线程类的Start方法线程开始运行. 这时无需等待run方法体代码执行完毕,可
以直接继续执行下面的代码, 真正实现了多线程运行 - JDK1.6对Synchronized底层做了哪些优化
Synchronized底层早期JDK版本是采用线程的状态转换实现的, 主要使用了线程的阻塞和唤醒来实现.
JDK1.6中使用了自适应自旋锁实现, 还加入的锁消除, 锁粗化等优化策略
自适应自旋锁 : JDK 1.6引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数不
再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
锁消除 : 为了保证数据的完整性,我们在进行操作时需要对这部分操作进行同步控制,但是在有些情况
下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除
锁粗化 : 就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁
五、IO【5道】
- Java提供了哪些IO方式?
传统BIO同步阻塞IO, 这里面又分为字节流和字符流
JDK1.4引入NIO, 同步非阻塞IO, 速度比传统BIO快, 并且更节省资源
JDK1.7引入NIO2也叫做AIO, 异步非阻塞IO, 基于事件和回调机制实现, 速度更快 - NIO, BIO, AIO区别?
NIO:在JDK1.4以前,Java的IO模型一直是BIO,从JDK1.4开始,JDK引入的新的IO模型NIO,它是同步
非阻塞的。而服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器Selecter上,多路复
用器轮询到连接有IO请求时才启动一个线程处理
BIO:同步阻塞,服务器的实现模式是一个连接一个线程,这样的模式很明显的一个缺陷是:优于客户
端连接数与服务器线程数成正比关系,可能造成不必要的线程开销,严重的还会导致服务器内存溢出,
当然,这种情况可以通过线程池改善,但并不能从本质上消除这个弊端
AIO:JDK1.7发布了NIO2.0,这就是真正意义上的异步非阻塞,服务器的实现模式为多个有效请求一个
线程,客户端的IO请求都是由OS完成再通知服务器应用去启动线程处理(回调) - 有哪些缓冲流?如何实现缓冲功能!
缓冲流也叫高效流,是对四个基本的FileXxx流的增强,按照数据类型分类:
字节缓冲流 :BufferedInputStream,BufferedOutputStream
字符缓冲流:BufferedReader,BufferedWriter
基本原理:
是在创建流对象的时候,会创建一个内置默认大小的缓冲区数组,减少系统IO次数,从而提高读写
效率
字节缓冲流
public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
public BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。 - 实现文件拷贝的几种方式!
通过字节流实现文件拷贝
通过字符流实现文件拷贝
通过字节缓冲流实现文件拷贝
通过字符缓冲流实现文件拷贝
通过JAVA NIO非直接缓冲区拷贝文件
通过JAVA NIO直接缓冲区拷贝文件
通过JAVA NIO通道传输拷贝文件 - 什么Java序列化,如何实现序列化!
序列化:就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的
对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作
时所引发的问题。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements
Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造
一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object
obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
六、网络编程 【9道】
- http协议和RPC协议区别
RPC是远程过程调用, 是JDK底层定义的规范, 它的实现既可以使用TCP也可以使用http, 底层可以使用二
进制传输, 效率高.
Http是协议, http协议底层位于传输层是tcp协议. 通用性好可以传输json数据, 效率稍微慢一些 - OSI网络模型七层都有哪些
OSI参考模型分为7层,分别是物理层,数据链路层,网络层,传输层,会话层,表示层和应用层。 - 传输层上的TCP和UDP区别
TCP与UDP的特点:
UDP:无连接、不可靠、传输速度快. 适合传输语音, 视频等
TCP:面向连接、可靠、传输速度没有UDP快, 例如: http, smtp等协议底层就是tcp - http协议1.0和1.1版本区别
主要有4个方面:缓存处理,带宽优化和网络连接使用,Host请求头的区别,长连接
● 缓存处理:HTTP1.1则引入了更多的缓存控制策略
● 长连接:HTTP1.1默认使用长连接,可有效减少TCP的三次握手开销,在一个TCP连接上可以传送多个
HTTP请求和响应,减少了建立和关闭连接的消耗和延迟
● Host请求头的区别:HTTP1.0是没有host域的,HTTP1.1才支持这个参数
● 带宽优化和网络连接使用:在1.1后,现在只会发送head信息,果服务器认为客户端有权限请求服务
器,则返回100,当接收到100后才会将剩下的信息发送到服务器 - http是短连接还是长连接, 如何实现的
HTTP协议与TCP/IP协议的关系
HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP
协议,在网络层使用IP协议。IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层
之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出顺序一致。
TCP有可靠,面向连接的特点。
如何理解HTTP协议是无状态的
HTTP协议是无状态的,指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状
态。也就是说,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。
HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使
用的是UDP协议(无连接)。
什么是长连接、短连接?
在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立
一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包
含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web
资源,就会建立一个HTTP会话。
但从 HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头有
加入这行代码:Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP
连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。
Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设
定这个时间。实现长连接要客户端和服务端都支持长连接。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
TCP连接
当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连
接,当读写操作完成后,双方不再需要这个连接 时它们可以释放这个连接,连接的建立是需
要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗
三次握手建立连接 , 四次挥手关闭连接
短连接的操作步骤是:
建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接
长连接的操作步骤是:
建立连接——数据传输…(保持连接)…数据传输——关闭连接 - 简述tcp三次握手四次挥手过程
先向HTTP服务器发起TCP的确认请求(三次握手)
客户端 --> SYN --> 服务器
服务器 --> SYN+ACK —>客户端
客户端 --> ACK --> 服务器
客户端要和服务器断开TCP连接(四次挥手)
客户端 --> FIN +ACK —> 服务器
服务器 --> FIN —> 客户端
服务器 --> ACK --> 客户端
客户端 --> ACK —> 服务器 - http有多少类响应码?分别什么含义
响应码由三位十进制数字组成,它们出现在由HTTP服务器发送的响应的第一行。
响应码分五种类型,由它们的第一位数字表示:
1xx:信息,请求收到,继续处理
2xx:成功,行为被成功地接受、理解和采纳
3xx:重定向,为了完成请求,必须进一步执行的动作
4xx:客户端错误,请求包含语法错误或者请求无法实现
5xx:服务器错误,服务器不能实现一种明显无效的请求 - tomcat的实现原理!tomcat如何进行优化?
tomcat是一个基于JAVA的WEB容器,其实现了JAVA EE中的 Servlet 与 jsp 规范,与Nginx Apache 服务
器不同在于一般用于动态请求处理。在架构设计上采用面向组件的方式设计。即整体功能是通过组件的
方式拼装完成。另外每个组件都可以被替换以保证灵活性。
实现原理:
Tomcat是运行在JVM中的一个进程。它定义为“中间件”,顾名思义是一个在Java项目与JVM之间的中间容
器。
Web项目的本质,是一大堆的资源文件和方法。Web项目没有入口方法(即main方法),这意味着
Web项目中的方法不会自动运行起来。Web项目部署进Tomcat的webapp中的目的是很明确的,那就是
希望Tomcat去调用写好的方法去为客户端返回需要的资源和数据。
Tomcat可以运行起来,并调用写好的方法。那么,Tomcat有一个main方法。对于Tomcat而言,它并
不知道用户会有什么样的方法,这些都只是在项目被部署进webapp下后才确定的。由此,可知Tomcat
用到了Java的反射来实现类的动态加载、实例化、获取方法、调用方法。但是部署到Tomcat的中的Web
项目必须是按照规定好的接口来进行编写,以便进行调用。
优化:
修改内存的相关配置
修改TOMCAT_HOME/bin/catalina.sh,在其中加入,也可以放在 CLASSPATH=下面, 加一些
对内存调优的参数
优化连接器Connector
Connector是连接器,负责接收客户的请求,以及向客户端回送响应的消息。所以 Connector
的优化是重要部分。默认情况下 Tomcat只支持200线程访问,超过这个数量的连接将被等待
甚至超时放弃,所以我们需要提高这方面的处理能力。
在TOMCAT_HOME/conf/server.xml添加最大线程数量和最小空闲线程数,请求的数量
配置线程池
Executor代表了一个线程池,可以在Tomcat组件之间共享。使用线程池的好处在于减少了创
建销毁线程的相关消耗,而且可以提高线程的使用效率。 - get 和 post 请求有哪些区别?
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST没有。
参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中
七、MySQL以及SQL面试题【20道】
- 说一说什么是数据库事务!
数据库事务就是在一套业务操作的多条sql语句执行中要么全成功, 要么全失败. 保证了数据的一致性.
事务的四个属性:原子性,一致性,隔离性,持久性。
原子性:在事务中进行的修改,要么全部执行,要么全不执行。如果在事务完成之前系统出现故
障,SQLServer会撤销在事务中的修改。
一致性:为了事务在查询和修改时数据不发生冲突。
隔离性:隔离性是一种用于控制数据访问的机制,能够确保事务只能访问处于期望的一致性级别下
的数据。SQLServer使用锁对各个事务之间正在修改和查询的数据进行隔离。
持久性:在将数据修改写入到磁盘之前,总是先把这些修改写入到事务日志中。这样子,即使数据
还没有写入到磁盘中,也可以认为事务是持久化的。这是如果系统重新启动,SQL Server也会检查
数据库日志,进行恢复处理。
隔离级别:读未提交、读已提交、可重复读、串行化 - 事务并发产生的问题和隔离级别!
事务并发产生的问题:
脏读:一个事务读取另一个事务的未提交数据! 真错误!read UNCOMMITTED;
不可重复读:一个事务读取;另一个事务的提交的修改数据!read committed;
虚读()幻读:一个事务读取了另一个数的提交的插入数据!repeatable read
隔离级别:读未提交、读已提交、可重复读、串行化
隔离级别选择:
数据库隔离级别越高!数据越安全,性能越低!
数据库隔离级别越低!数据越不安全,性能越高
建议:设置为第二个隔离级别 read committed; - Spring中事务的传播特性有哪些?
七种传播特性:
propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事
务中,这是最常见的选择。
propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
propagation_nested:如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启
动一个新的事务,并在它自己的事务内运行。
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED - MySQL的内部有哪几部分组成?
连接器
MYSQL拥有非常多的客户端比如:navicat,jdbc,SQLyog等客户端,这些客户端都需要向
MYSQL通信都必须要建立连接,这个建立连接的工作就是由连接器完成的
查询缓存(5.8版本开始取消了这个)
连接建立之后,你就可以执行select语句了 这个时候首先会去查询下缓存,缓存的存储形式类似于
key-value,key保存的是sql语句value 保存的是结果集,如果查询不在缓存中就会执行sql语句执
行完成后把执行结果放入缓存中,如果是能在缓存中查询到那么直接返回
词法分析器
解析sql语句中的词法, 语法
优化器
对解析后生成的执行计划进行自动优化, 提升查询效率
执行器
执行sql语句分析后生成的执行计划, 执行后返回结果 - MySQL如何实现乐观锁和悲观锁
①乐观锁实现:版本号控制及时间戳控制。
版本号控制:表中加一个 version 字段;当读取数据时,连同这个 version 字段一起读出;数据每
更新一次就将此值加一;当提交更新时,判断数据库表中对应记录的当前版本号是否与之前取出来
的版本号一致,如果一致则可以直接更新,如果不一致则表示是过期数据需要重试或者做其它操
作。
时间戳控制:其原理和版本号控制差不多,也是在表中添加一个 timestamp 的时间戳字段,然后
提交更新时判断数据库中对应记录的当前时间戳是否与之前取出来的时间戳一致,一致就更新,不
一致就重试。
②悲观锁实现:必须关闭mysql数据库的自动提交属性,开启事务,再进行数据库操作,最后提交事
务。 - MySQL常用的存储引擎及区别
InnoDB默认的存储引擎, 不需要任何配置, 默认使用的就是它, 支持事务, 支持主外键连接, 速度一般
MyISAM不支持事务, 速度快, 以读写插入为主的应用程序,比如博客系统、新闻门户网站。
Memory存储的数据都放在内存中, 服务器断电, 数据丢失, 速度最快, 现基本被redis取代. - 说一说对MySQL索引的理解
什么是数据库索引?
数据库索引,是数据库管理系统中一个排序的数据结构,索引实现通常使用B树及变种的B+树。在
数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指
向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构就是索引。
索引的作用?
协助快速查询、更新数据库表中数据。
副作用:
增加了数据库的存储空间插入和修改数据时要花费较多的时间(因为索引也会随之变动) - MySQL中什么字段适合添加索引
表的主键、外键必须有索引;
经常与其他表进行连接的表,在连接字段上应该建立索引;
经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
索引应该建在选择性高的字段上;
索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替: - MySQL中常见的索引类型
普通索引:是最基本的索引,它没有任何限制
唯一索引:与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索
引,则列值的组合必须唯一
主键索引:是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时
创建主键索引
组合索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被
使用。使用组合索引时遵循最左前缀集合 - MySQL中索引失效的场景!
WHERE字句的查询条件里有不等于号(WHERE column!=…),MYSQL将无法使用索引,类似地,如果
WHERE字句的查询条件里使用了函数(如:WHERE DAY(column)=…),MYSQL将无法使用索引
在JOIN操作中(需要从多个数据表提取数据时),MYSQL只有在主键和外键的数据类型相同时才能使用
索引,否则即使建立了索引也不会使用
如果WHERE子句的查询条件里使用了比较操作符LIKE和REGEXP,MYSQL只有在搜索模板的第一个字符
不是通配符的情况下才能使用索引。比如说,如果查询条件是LIKE ‘abc%’,MYSQL将使用索引;如果条
件是LIKE ‘%abc’,MYSQL将不使用索引。 - 说一说Hash和B+树结构索引区别?
hash索引 : 等值查询效率高,不能排序,不能进行范围查询;Hash索引仅仅能满足"=",“IN"和”<=>"查
询,不能使用范围查询
B+树索引 : 数据有序,支持范围查询, 查询效率没有hash索引高. - B+比B树索引的区别?
B树,每个节点都储存key和dta,所有节点组成这棵树,并且叶子节点指针为null,叶子节点不包含任何
关键字的信息。
B+树,所有叶子节点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子节点本身
依关键字的大小自小尔达的顺序链接,所有的非终端节点可以看成是索引部分,节点中仅含有其子树根
节点中最大的(或最小的)关键字。 - 聚集(集中)索引和非聚集(稀疏)索引的区别
【聚集索引】:也称 Clustered Index。是指关系表记录的物理顺序与索引的逻辑顺序相同。由于一张表
只能按照一种物理顺序存放,一张表最多也只能存在一个聚集索引。与非聚集索引相比,聚集索引有着
更快的检索速度。
MySQL 里只有 INNODB 表支持聚集索引,INNODB 表数据本身就是聚集索引,也就是常说 IOT,索引
组织表。非叶子节点按照主键顺序存放,叶子节点存放主键以及对应的行记录。所以对 INNODB 表进行
全表顺序扫描会非常快。
【非聚集索引】:也叫 Secondary Index。指的是非叶子节点按照索引的键值顺序存放,叶子节点存放
索引键值以及对应的主键键值。MySQL 里除了 INNODB 表主键外,其他的都是二级索引。MYISAM,
memory 等引擎的表索引都是非聚集索引。简单点说,就是索引与行数据分开存储。一张表可以有多个
二级索引。 - 什么是索引倒排!
倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具
有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称
为倒排索引(inverted index)。带有倒排索引的文件我们称为倒排索引文件,简称倒排文件(inverted
file)。 - 如何快速的复制一张表!
将表结构和数据导出成sql脚本文件, 可以根据要求修改脚本中的表名字防止重名, 然后再导入到mysql中
在库中创建新表后, 使用insert into 表名(字段名) select 字段名 from 表名 - SQL语句优化的方案?
第一, 开启慢查询日志,让系统运行一段时间, 打开慢查询日志找到具体需要优化的sql语句
第二, 使用explain执行计划分析sql语句中问题出现的哪里
第三, 根据sql语句编写规范, 进行优化, 例如: 不要使用like模糊查询, 因为索引会失效; 尽量避免使用
select*, 因为会返回无用字段; 条件中不要使用or关键字, 因为会全表扫描等. - 组合索引的最左原则?
MySQL 的联合索引会首先根据联合索引中最左边的、也就是第一个字段进行排序,在第一个字段排序的
基础上,再对联合索引中后面的第二个字段进行排序,依此类推。联合索引当遇到范围查询(>、<、
between、like)就会停止匹配,建立索引时匹配度高的字段在前,匹配度低的字段在后
举例 : a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立
(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮
你优化成索引可以识别的形式 - 什么是行锁, 表锁, 页锁?
行锁 : 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低
行锁就是针对数据表中行记录的锁。这很好理解,比如事务A更新了一行,而这时候事务B也要更新
同一行,则必须等事务A的操作完成后才能进行更新。
表锁 : 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突概率高
表锁的语法是 lock tables … read/write。可以用unlock tables主动释放锁,也可以在客户端断开
的时候自动释放。需要注意,lock tables语法除了会限制别的线程的读写外,也限定了本线程接下
来的操作对象。
页级锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲
突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度
一般 - 数据库的三范式是什么
第一范式:1NF 是对属性的原子性约束,强调的引擎是列的原子性,即数据库表的每一列都是不可分割
的原子数据项。
第二范式:2NF 是对记录的惟一性约束,要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能
存在仅依赖主关键字一部分的属性。
第三范式:3NF 是对字段冗余性的约束,任何非主属性不依赖于其它非主属性。即任何字段不能由其他
字段派生出来, 它要求字段没有冗余。 - 什么情况下设置了索引但无法使用
以“%”开头的 LIKE 语句,模糊匹配
OR 语句前后没有同时使用索引
数据类型出现隐式转化(如 varchar 不加单引号的话可能会自动转换为 int 型)
八、常用框架【19道】
- MyBatis$和#的区别
#{ }可以防止Sql 注入,它会将所有传入的参数作为一个字符串来处理。
Mybatis在处理#{}时,会将SQL语句中的#{}替换为?号,调用PrepaerdStatement的set方法来赋值。
$ {} 则将传入的参数拼接到Sql上去执行,一般用于表名和字段名参数,$ 所对应的参数应该由服务器端
提供,前端可以用参数进行选择,避免 Sql 注入的风险
Mybatis在处理 时,就是把 {}时,就是把 时,就是把{}换成变量的值。 - MyBatis中一级缓存和二级缓存的区别
一级缓存 :
一级缓存是基于sqlsession默认开启的,在操作数据库时需要构造SqlSession对象,在对象中有一
个HashMap用于存储缓存数据。不同的SqlSession之间的缓存数据区域是互相不影响的。
一级缓存作用是sqlsession范围的,在同一个sqlsession中执行两次相同的sql时,第一次得到的数
据会缓存放在内存中,第二次不再去数据库获取,而是直接在缓存中获取,提高效率。
如果执行了增删改并提交到数据库,mybatis是会把sqlsession中的一级缓存清空的,这样是为了
数据的准确性,避免脏读现象。
二级缓存 :
二级缓存是基于mapper的namespace作用域,但多个sqlsession操作同一个namespace下的sql
时,并且传入的参数也相同,执行相同的sql语句,第一次执行完毕后会将数据缓存,这就是二级
缓存。
二级缓存同样是使用HashMap进行数据存储。相比一级缓存SqlSession,二级缓存的范围更大,
多个Sqlsession可以共用二级缓存,二级缓存它是可以跨越多个sqlsession的。 - MyBatis和数据库交互的原理
详细流程如下:
加载mybatis全局配置文件(数据源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文
件生成Configuration,和一个个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射
配置),其对应着<select | update | delete | insert>标签项。
SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession。
SqlSession对象完成和数据库的交互, 通过Executor执行器将MappedStatement对象进行解析,最后通
过JDBC执行sql。
借助MappedStatement中的结果映射关系,将返回结果转化成HashMap、JavaBean等存储结构并返
回。 - Spring的IOC是什么
SpringIOC是控制反转, 负责创建对象,管理对象,装配对象,配置对象,并且管理对象的整个生命周期,
也就是以前创建对象需要new, 那么在使用了spring的IOC后, 所有这样的工作都交给spring进行管理. - Spring的常用注解
@Autowired 用于自动注入bean
@Resource 根据名字注入bean
@Controller 用于声明控制层类
@Repository 用于声明Dao持久层类
@Service 用于声明Service业务层类
@Component 用于声明一个普通JavaBean
@Configuration 用于声明初始化类相当于spring核心配置文件的标签
@Bean 声明一个配置, 相当于spring核心配置文件的标签 - Spring的IOC中创建对象的整体流程
通过ClassPathXmlApplicationContext对象加载spring核心配置文件创建ApplicationContext对象
通过applicationContext对象的getBean(beanName)方法创建所需类对象
转换beanName,因为传入的参数可能是别名,也可能是FactoryBean,所以需要解析
如果是beanName,不需要处理
如果是 “&beanName”,则去掉&
如果是别名,则转换为beanName - Spring的Bean的三级缓存
一级缓存singletonObjects是完整的bean,它可以被外界任意使用,并且不会有歧义。
二级缓存earlySingletonObjects是不完整的bean,没有完成初始化,它与singletonObjects的分离主要
是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持
有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。
三级缓存singletonFactories,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且
只能处于第三层。
在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一
级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,
最终都会在一级缓存中。 - Spring如何处理循环依赖
构造器参数循环依赖 :
通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyIn
CreationException异常表示循环依赖。
如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC
类,则又去创建TestC,最终在创建TestC时发现又需要TestA,从而形成一个环,没办法创建。
Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建
过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里
时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当
前创建Bean池”中清除掉。
setter方式单例,默认方式 :
Spring先是用构造实例化Bean对象 ,此时Spring会将这个实例化结束的对象放到一个Map中,并
且Spring提供了获取这个未设置属性的实例化对象引用的方法。当Spring实例化了StudentA、
StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map
中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题。
prototype作用域bean的循环依赖 :
这种循环依赖同样无法解决,因为spring不会缓存prototype作用域的bean,而spring中循环依赖
的解决方式正是通过缓存来实现的。
Spring解决这个问题主要靠巧妙的三层缓存,Spring首先从singleTonObjects(一级缓存)中尝试
获取,如果获取不到并且对象在创建中,则尝试从earlySingleTonObjects(二级缓存)中获取,
如果还是获取不到并且允许从singleTonFactories通过getObject获取,则通过
singleTonFactory.getObject()(三级缓存)获取。
如果获取到了则移除相对应的singleTonFactory,将singleTonObject放入到
earlySingleTonObjects,其实就是将三级缓存提升到二级缓存,这个就是缓存升级。Spring在进行
对象创建的时候,会依次从一级、二级、三级缓存中寻找对象,如果找到直接返回。
拓展:
一级缓存:singletonObjects,存放完全实例化属性赋值完成的单例对象的cache,直接可以使
用。
二级缓存:earlySingletonObjects,存放提前曝光的单例对象的cache,尚未进行属性封装的
Bean。
三级缓存:singletonFactories,存放单例对象工厂的cache。 - @Autowired和@Resource的区别
@Autowired是自动注入, 一个接口只有一个实现类的时候使用
@Resource是根据bean的名字注入, 一个接口有多个实现类的时候使用, @Resource(实现类bean名字)
根据实现类的名字注入指定实现类. - SpringMVC的执行流程
- Spring和SpringMVC的父子工厂关系
Spring容器是父容器,包含的对象有dao,service等
Springmvc是子容器,包括controller
子容器可以访问父容器的对象
父容器不能访问子容器的对象 - 阐述SpringBoot启动类中注解
@SpringBootAplication:这个注解是启动springboot,从源码我们会发现这个注解被
@Configuration、@EnableAutoConfiguration、 @ComponentScan三个注解修饰。换言之,
springboot提供了统一的注解来替代这三个注解。
@EnableEurekaClient:这个注解是必须的,表示注册到某个Eureka服务(注册中心)中,相当于给这个
服务加了一个通行证,通行证的具体的内容需要在application.yml中配置。一般配置里面会有:
eureka、client、serviceurl、defaultZone:http://,代表注册到上面的注册中心,这个注册中心里面
的服务包括所有的接口都可以通过协商对方暴露的接口之后直接按照规则进行调用
@MapperScan:这个注解是用来扫描到dao的范围的。如果使用的是mybatis的话,需要通过配置文件
来之指定Mapper和主要的配置文件的位置。
@EnableRedisHttpSession:这个注解是用来获取session中缓存的内容, 还需要配合配置文件来完
成。 - SpringBoot如何实现的自动装配
Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载
META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需
加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖 - Spring事务的传播行为?
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务
方法应该如何进行。是为自己开启一个新事务运行,还是由原来的事务执行, 这是由传播行为决定的 - Spring中AOP底层实现原理
AOP底层实现使用了动态代理
在spring中有两种实现方式, 一种是JDK动态代理, 一种是CJlib动态代理
JDK动态代理针对实现了接口的类使用
CJlib动态代理针对只有类的时候使用 - Spring中ApplicationContext对象和BeanFactory对象区别?
这两个对象都可以创建JavaBean对象
ApplicationContext对象 : 这个对象底层是通过BeanFactory对象实现的, 但是ApplicationContext创建
JavaBean的时机是调用了这个对象的getBean()方法, 就会立即创建JavaBean
BeanFactory对象 : 它创建JavaBean的时机是调用getBean方法不进行创建JavaBean而是等到用到了
JavaBean的时候才去创建. - Spring中的注入方式有哪些
常用的注入方式主要有三种:
构造方法注入 : 构造方法注入会有不支持的情况发生,因为在调用构造方法中必须传入正确的构造
参数,否则报错。
setter注入 : 注入支持大部分的依赖注入
基于注解的注入 : 可以在类中使用@Autowired或是@Resource注解进行注入.
Spring中Bean的生命周期 - Spring 支持几种 bean 的作用域
支持如下5种作用域:
singleton(默认的):单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实
例
prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一
个新的Bean实例
request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求
将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在
Web应用中使用Spring时,该作用域才有效
globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型
情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才
有效 - Spring中Bean的生命周期