什么是单例模式
单例模式是一种常用的软件设计模式。定义是单例的类只能有一个实例对象,并向外提供一个全局访问点。
优点
- 只有一个实例对象,节省内存空间
- 避免频繁的创建销毁对象,提高性能
- 避免对共享资源的多重占用
- 能够全局访问
实现思路
- 私有化构造方法,让外部不能 new 对象。
- 在类的内部创建好实例。【静态变量的目的是为了类加载的时候创建实例】
- 向外提供一个公有的静态方法,返回该唯一实例。
实现写法
1. 饿汉式
/**
* @description: 饿汉式
* @author: opoa
* @create: 2021-03-14 20:29
**/
/**
* 饿汉式
*/
public class Singleton1 {
/**
* 私有化构造方法
*/
private Singleton1() {}
/**
* 在类的内部创建好实例。【静态变量的目的是为了类加载的时候创建实例】
*/
private final static Singleton1 instance = new Singleton1();
/**
* 向外提供一个公有的静态方法,返回该唯一实例。
* @return 实例
*/
public static Singleton1 getInstance() {
return instance;
}
}
优点:写法简单,在类加载的时候就创建了实例,避免了线程同步问题,线程安全。
缺点:如果实例一直没有被使用,则会造成内存空间的浪费。
2. 懒汉式
非线程安全
public class Singleton2 {
private Singleton2() {}
private static Singleton2 instance;
public static Singleton2 getInstance() {
// 如果实例为空 则初始化一个赋给 instance
if (null == instance) {
instance = new Singleton2();
}
return instance;
}
}
懒汉式,顾名思义,采用懒加载的方式,当需要用到的时候才初始化实例。 这种写法在多线程运行时存在安全问题,可能有多个线程同时进入判断条件,生成多个实例。不推荐使用。
线程安全
public class Singleton3 {
private Singleton3() {}
private static Singleton3 instance;
public static synchronized Singleton3 getInstance() {
if (null == instance) {
instance = new Singleton3();
}
return instance;
}
}
优点:线程安全。
缺点:效率低。
3. 双重检查锁
public class Singleton4 {
private Singleton4() {}
// volatile 对于 instance 所有的写将先于读
private static volatile Singleton4 instance;
public static Singleton4 getInstance() {
// 第一次检查 只在第一次创建实例的时候同步,之后不再同步
if (null == instance) {
synchronized (Singleton4.class) {
// 第二次检查 确保只有一个线程能进入
if (null == instance) {
instance = new Singleton4();
}
}
}
return instance;
}
}
volatile 关键字:线程始终能读取到 volatile 修饰的变量的最新值。 这种写法线程安全,懒加载,效率高,推荐使用。
4. 枚举类
public enum Singleton5 {
/**
* 唯一实例
*/
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
}
测试代码:
public class TestSingleton5 {
@Test
public void test() {
Singleton5 instance = Singleton5.INSTANCE;
Singleton5 instance1 = Singleton5.INSTANCE;
// 比较内存地址
System.out.println(instance == instance1);
// 比较 hashCode
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
instance.doSomething();
}
}
运行结果:
true
1645995473
1645995473
doSomething
这种写法简单高效,充分利用了枚举类的特性。是绝对的单例,即使反序列化也不会生成多个实例。推荐使用。
Tips
如何区分懒汉式和饿汉式
懒汉式:顾名思义,它比较懒,只有需要用到的时候才会去生产,毕竟 Deadline 才是唯一生产力嘛。这里也可以理解为懒加载的形式。
饿汉式:不愿意让自己多饿一分钟,宁愿先把饭做好了,饿了的话就可以直接吃饭。对应提前初始化好实例对象。
推荐等级
枚举类 > 双重检查锁 > 饿汉式