前言
在别人的面经,看到手写单例模式,勾起了遥远的记录。来复习总结一下。
单例模式涉及概念
- 目的:众所周知,我们通过new都能创建一个实例,但在一些程序运行中,只允许存在一个对象实例,那么我们就需要从代码层面保证只有一个对象实例
- 运用场景:
-
- 一些类并不是轻量级的,一个项目中存在一个就行,譬如一些数据源连接对象
- 实现方式
-
- 目的:有很多方法都能实现这种方式,但本质都一样,都是从代码层面保证只产生一个对象
- 我们需要再明白饿汉式和懒汉式
-
- 饿汉式:在一开始,类装载的时候,实例就创建出来。缺点就是,不知道是不是需要用到就创建
- 懒汉式:需要用到实例的时候再创建出来
-
-
- 线程安全问题:创建的方式需要注意,不然会导致创建出来不止一个,则不是单例模式。
-
实现代码
饿汉式
- 通过静态常量的方式创建对象
public class Car1 {
// 1.将构造器私有化,外部不能new
private Car1(){
}
// 2.本类内部创建对象实例
private final static Car1 car = new Car1();
// 3.对外提供一个共有的静态方法,返回实例对象
public static Car1 getCar(){
return car;
}
}
- 通过静态代码块的方式创建对象
public class Car2 {
private Car2(){
}
private final static Car2 car;
static {
car = new Car2();
}
public static Car2 getCar(){
return car;
}
}
- 区别:这两种方法都是私有化构造方法,但是在类装载的时候就已经创建好实例,仅通过暴露一个方法,供外部获取。
懒汉式
- 线程不安全(跟Redis缓存击穿有点类似)
public class Car3 {
//1.私有化构造方法
private Car3(){}
private static Car3 car;
//2. getCar,如果没有就创建,如果有就返回
public static Car3 getCar(){
if(car == null ){
//缺点:如果是单线程,这个代码并没有问题,但多线程情况下,在代码执行到这里,多个线程都判断null,就都会去创建实例,这就破坏了单例模式
car = new Car3();
}
return car;
}
}
- 线程安全(加锁)
public class Car4 {
//1.私有化构造方法
private Car4(){}
private static Car4 car;
//2. getCar,如果没有就创建,如果有就返回.通过加锁保证只有一个线程去创建
public synchronized static Car4 getCar(){
if(car == null ){
car = new Car4();
}
return car;
}
}
- 双重检查
-
- 目的:我们并不想在方法上直接解锁的方式去解决问题,即便问题解决,但不是最优解
- 为什么是双重检查?在执行一个if是null的时候,进行同步代码块的时候,有可能是多个线程在等待,可能前面的线程已经创建了,后续等待的线程就不需要去创建
public class Car5 {
private Car5(){}
private static Car5 car;
public Car5 getCar(){
if(car == null){
synchronized (Car5.class){
if(car == null){
car = new Car5();
}
}
}
return car;
}
}
- 静态内部类
-
- 利用JVM提供的静态内部类的方法是实现,JVM底层会帮助我们,加载Car的时候,car实例并不会被创建,而调用getCar的时候,需要CarInstance,将CarInstance这个类加载并创建Car出来。利用类装载的机制保证
public class Car6 {
private static Car6 instance;
private Car6(){}
private static class CarInstance{
private static final Car6 INSTANCE = new Car6();
}
public static Car6 getCar(){
return CarInstance.INSTANCE;
}
}
总结
曾经看见过12种实现单例模式,我觉得我是无缘了,脑子记不得那么多种。(上面也只有几种常见的。只要满足在程序运行过程中,什么情况都只有一个实例就行。实现方式可以很多种)
但无论是饿汉还是懒汉本质都是通过只创建一个实例来完成。而饿汉式应用场景也很多。因为很多对象本身就轻量,所以饿汉式用得地方很多。如果我们使用懒汉式则考虑线程安全问题。
至于可以通过反射和序列化可以破坏单例模式,可以通过枚举和不实现序列化接口进行实现。反射会判断是不是枚举,如果是枚举会抛出异常,所以枚举是不会被破坏单例的。因此也可以使用枚举实现单例模式。