1. 什么是单例模式
最常用的设计模式,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例。
优点:不会频繁地创建和销毁对象,浪费系统资源。
使用场景:IO 、数据库连接、Redis 连接等。
2. 代码实现
2.1 饿汉式(静态常量和静态代码块)
饿汉式(静态常量):写法简单,类装载的时候就完成了实例化,避免了线程同步问题,没有达到 LazyLoading 的效果,如果没用这个对象,会造成资源浪费。
饿汉式(静态代码块):类装载的时候,执行静态代码块中的语句,初始化类的实例,没有达到 LazyLoading 的效果,如果没用这个对象,会造成资源浪费。
public class HungerSingleton1 {
private HungerSingleton1() {}
private static HungerSingleton1 instance = new HungerSingleton1();
public static HungerSingleton1 getInstance() {
return instance;
}
}
public class HungerSingleton2 {
private HungerSingleton2() {
}
private static HungerSingleton2 instance;
static {
instance = new HungerSingleton2();
}
public static HungerSingleton2 getInstance() {
return instance;
}
}
2.2 懒汉式
线程不安全:在多个线程使用同一个资源的时候,有可能存在一个资源被一个线程占有,但一系列操作(原子操作:不可再分割的操作)并未执行完成,执行过程中的资源被其他线程拿去用了。
线程不同步:举个例子来说,有变量int a=0.,同时被2个线程各加1,如果最后输出得到a的值是2,就是线程同步的。如果最后输出得到a的值只有1(少加)或者0(没加)的情况,就属于没同步。
//达到lazy loading的效果,但是只能在单线程下使用;多线程可能产生多个实例
public class LazySingleton1 {
private LazySingleton1(){};
private static LazySingleton1 instance;
public static LazySingleton1 getInstance() {
if (instance == null) {
instance = new LazySingleton1();
}
return instance;
}
}
//线程安全,同步方法,解决线程安全问题,效率低
public class LazySingleton2 {
private LazySingleton2(){};
private static LazySingleton2 instance;
public static synchronized LazySingleton2 getInstance() {
if (instance == null) {
instance = new LazySingleton2();
}
return instance;
}
}
//线程安全,同步代码块,锁住当前类,但不能起到线程同步的作用
public class LazySingleton3 {
private LazySingleton3(){};
private static LazySingleton3 instance;
public static LazySingleton3 getInstance() {
if (instance == null) {
synchronized (LazySingleton3.class) {
instance = new LazySingleton3();
}
}
return instance;
}
}
2.3 双重检查
双重检查:实例化代码只执行一次,后面再访问时,判断(instance==null),线程安全,延迟加载,效率较高
public class DoubleCheckSingleton {
private DoubleCheckSingleton(){}
private static DoubleCheckSingleton instance;
public static DoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
2.4 静态内部类
静态内部类:采用了类装载的机制来保证初始化实例只有一个线程,静态内部类在 InternalSingleton 类被装载时不会立即实例化,而是在需要实例化时,调用getInstance 方法,才会装载 SingletonInstance 类,从而完成 InternalSingleton 的实例化
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
public class InternalSingleton { private InternalSingleton(){} private static class SingletionInstance { private static final InternalSingleton INSTANCE = new InternalSingleton(); } public static InternalSingleton getInstance() { return SingletionInstance.INSTANCE; } }
2.5 枚举
枚举实现:不仅能避免多线程同步问题,而且还能防止反序列重新创建新的对象。
为什么枚举类可以避免多线程问题?
对枚举类进行反编译可以发现,当我们使用 enum 来定义一个枚举类型的时候,编译器会自动帮我们创建一个 final 类型的类继承 Enum类,所以枚举类型不能被继承,类中属性都使用了 static 修饰。因为 static 类型的属性会在类被加载之后被初始化,当一个 Java 类第一次被真正使用到的时候静态资源被初始化、Java 类的加载和初始化过程都是线程安全的。所以,创建一个 enum 类型是线程安全的。
public enum EnumSingletion {
INSTANGE;
public EnumSingletion getInstange() {
return INSTANGE;
}
}