线程池的拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor
.AbortPolicy
:丢弃任务并抛出RejectedExecutionException异常,这也是默认策略ThreadPoolExecutor
.DiscardPolicy
:丢弃任务,但是不抛出异常ThreadPoolExecutor
.DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新提交被拒绝的任务ThreadPoolExecutor
.CallerRunsPolicy
:由调用线程(提交任务的线程)处理该任务
类锁、对象锁和方法锁
对象锁
加锁非静态方法,即是对象锁。synchronized
修饰非静态方法、同步代码块的 synchronized (this)
用法和 synchronized (非this对象)
的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁。
类锁
加锁静态方法,即类锁。可以换个方向理解,静态方法其实就是类方法,所以加锁静态方法,即类锁。类锁的范围是整个实体类,即全局锁。
方法锁
通过在方法声明中加入synchronized
关键字来声明synchronized方法
。synchronized方法
控制对类成员变量的访问,每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属 线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此 后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为synchronized
的成员函数中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。
String、StringBuffer、StringBuilder
- 运行速度:
StringBuilder
>StringBuffer
>String
- 长度
StringBuilder
和StringBuffer
是字符串变量,长度可变String
创建后不可更改,底层用于存放字符的数组为final
类型
- 线程安全
StringBuilder
是线程不安全的StringBuffer
是线程安全的
- 使用场景
String
适合少量字符串操作情况StringBuffer
适用于多线程在字符缓冲区进行大量操作情况StringBuilder
适用于在单线程在字符缓冲区进行大量操作的情况
为什么String不可变
String
类的字符是保存在value
数组中的,并且是被private final
修饰的。
private
修饰,表明外部的类是访问不到value
的,同时子类也访问不 到,当然String
类不可能有子类,因为类被final
修饰了final
修饰,表明value
的引用是不会被改变的,而value
只会在String
的构造函数中被初始化,而且并没有其他方法可以修改value
数组中的值,保证了value
的引用和值都不会发生变化
Hashmap的put操作流程
当我们put
的时候,首先计算key
的hash
值,这里调用了hash
方法,hash
方法实际是让key.hashCode()
与key.hashCode()>>>16
进行异或操作,高16bit
补0
,一个数和0
异或不变,所以hash
函数大概的作用就是:高16bit
不变,低16bit
和高16bit
做了一个异或,目的是减少碰撞。按照函数注释,因为bucket
数组大小是2
的幂,计算下标index = (table.length - 1) & hash
,如果不做hash
处理,相当于散列生效的只有几个低bit
位,为了减少散列的碰撞,设计者综合考虑了速度、作用、质量之后,使用高16bit
和低16bit
异或来简单处理减少碰撞,而且JDK8中用了复杂度O(log n)
的树结构来提升碰撞下的性能。
Arraylist和Linkedlist
ArrayList
的实现是基于数组,LinkedList
的实现是基于双向链表。
- 对于随机访问,
ArrayList
优于LinkedList
。 - 对于插入和删除操作,
LinkedList
优于ArrayList
。
LinkedList
比ArrayList
更占内存,因为LinkedList
的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
线程安全的List有哪些
SynchronizedList
和CopyOnWriteArrayList
。
SynchronizedList
用Collections.synchronizedList(list);
将list
包装成SynchronizedList
。需要注意的是SynchronizedList
的add
等操作加了锁,但是iterator()
方法没有加锁,如果使用迭代器遍历的时候需要在外面手动加锁。适用于当不需要使用iterator()
并且对性能要求不高的场景。
CopyOnWriteArrayList
在写的时候加锁(ReentrantLock
锁),读的时候不加锁,大大提升了读的速度。添加元素的时候,先加锁,再复制替换操作,再释放锁。适用于读多写少的场景。
Arraylist扩容
第一种情况
当ArrayList
的容量为0
时,此时添加元素的话,需要扩容,三种构造方法创建的ArrayList
在扩容时略有不同:
- 无参构造:创建
ArrayList
后容量为0
,添加第一个元素后,容量变为10
,此后若需要扩容,则正常扩容。 - 传容量构造:当参数为
0
时,创建ArrayList
后容量为0
,添加第一个元素后,容量为1
,此时ArrayList
是满的,下次添加元素时需正常扩容。 - 传列表构造:当列表为空时,创建
ArrayList
后容量为0
,添加第一个元素后,容量为1
,此时ArrayList
是满的,下次添加元素时需正常扩容。
第二种情况
当ArrayList
的容量大于0
,并且ArrayList
是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5
倍。
SpringBoot常用注解
@Component
: 会被spring容器识别,并转为bean。
@Repository
: 对Dao实现类进行注解。
@Service
: 对业务逻辑层进行注解。
@Controller
: 表明这个类是Spring MVC里的Controller,将其声明为Spring的一个Bean,Dispatch Servlet
会自动扫描注解了此注解的类,并将Web请求映射到注解了@RequestMapping
的方法上。
@RequestMapping
: 用来映射Web请求(访问路径和参数)、处理类和方法的。它可以注解在类和方法上。注解在方法上的@RequestMapping路径会继承注解在类上的路径。
@RequestBody
: 可以将整个返回结果以某种格式返回,如json或xml格式。
@PathVariable
: 用来接收路径参数,如/news/001
,可接收001作为参数,此注解放置在参数前。
@RequestParam
:用于获取传入参数的值。
@RestController
:是一个组合注解,组合了@Controller
和@ResponseBody
,意味着当只开发一个和页面交互数据的控制的时候,需要使用此注解。
Spring解决循环依赖
Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects
),二级缓存为早期曝光对象(earlySingletonObjects
),三级缓存为早期曝光对象工厂(singletonFactories
)。
当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A 代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)
来获取需要的依赖,此时的getBean(a)
会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject
方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?
如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator
这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。
Hystrix的隔离策略
Hystrix目前是有两种隔离策略,分别是线程池隔离和信号量隔离。
线程池隔离
他的隔离是通过线程池来做到的,隔离粒度是线程池。一个请求进来都经过一个线程池。
当前端发起请求过来到服务A或者B之后,服务A和服务B是通过线程池隔离的。服务A是否熔断,是否正常都和服务B无关。
他其实是一个异步编程,用线程池将后面的服务包裹了起来,至于服务内部tomcate的线程运行怎么样是无关的。他适合于绝大多数的场景,对于一些超时的场景都非常好用。但是既然是通过线程池来操作的,不可避免的就是线程之间的计算开销,以及线程上下文的切换,调度消耗。
信号量隔离
他的隔离是通过信号量来做到的。他其实是一个计数器。一个请求进来就会减少一个信号,一个请求完成就会增加一个信号。
信号量的调用是同步的,也就是说他会阻塞直到请求回来。所以他自身是不能实现超时的,因此这里的超时只能依靠协议的超时来做,否则是无法释放的(比如socket超时等等)。所以当此服务不对外部服务依赖同时自身没有大量的计算或者说经过这个服务的时间比较短,则非常适合信号量,比如说Spring cloud
的zuul
的gateway
网关。