目录
1. 单例模式的含义
2. 单例模式的四种常见方式
2.1 饿汉式经典加载方式
2.2 懒汉式加载方式
2.3 静态内部类方式加载
2.4 枚举类加载方式
1. 单例模式的含义
单例,简单来说就是单个实例,是一种常见的软件的设计模式。
一个类只实例化自己的一个实例,并且不允许再创建多余的实例,在对外提供访问时,访问的都是同一个实例对象。
2. 单例模式的四种常见方式
2.1 饿汉式经典加载方式
很好理解,饿的见到什么都吃,在单例模式中指不管用到用不到的资源,都统统加载到内存中。
优点:实现简单,实用性强,且不存在线程安全问题,是我们最常使用的一种实现方式;
缺点:不管用到与否,类加载时就完成实例化统统加载到内存,用不用都要加载,感觉有点浪费和多余,占用内存空间;
代码实现如下,这种方法实现单例模式的核心原理是JVM会且仅会将一个类加载一次到内存中去,加载一次 Mgr01 类,那么 INSTANCE 对象就被创建出来了
public class Mgr01 {
# 创建一个静态私有化 INSTANCE 对象
private static final Mgr01 INSTANCE = new Mgr01();
# 私有化构造器,不允许其他人再创建新的类对象
private Mgr01(){}
# 编写get方法,调用get方法即可返回已经创建好的 INSTANCE 对象
public static Mgr01 getInstance(){
return INSTANCE;
}
# 此处main方法为测试方法,与设计模式无关
public static void main(String[] args) {
Mgr01 m1 = new Mgr01().getInstance();
Mgr01 m2 = new Mgr01().getInstance();
System.out.println(m1 == m2);
}
}
我们来运行一下 main 方法,就会发现 m1 对象和 m2 对象是相等的,如下图所示
2.2 懒汉式加载方式
对比饿汉式加载,懒汉式最大的特点就是懒,我不加载,我在需要用到的时候才去进行加载。
public class Mgr02 {
// 创建一个静态 Mgr02 对象 INSTANCE,但不进行实例化,
// 并且不许使用 volatile 修饰禁止指令重排,否则可能会出错,设计知识点较深,暂不解说
public static volatile Mgr02 INSTANCE;
// 私有化无参构造,禁止其他人访问创建新实例
private Mgr02(){}
// 在 get 方法中进行对象的实例化,并采用双重校验实现线程安全
public static Mgr02 getInstance(){
// 第一次检查,如果 INSTANCE 不为空,直接返回 INSTANCE 对象
if (INSTANCE == null){
// 双重检查再次判断避免出现线程安全问题
synchronized (Mgr02.class){
if (INSTANCE == null){
INSTANCE = new Mgr02();
}
}
}
return INSTANCE;
}
// 此处为测试 main方法测试线程安全
public static void main(String[] args) {
// 循环一百次,创建一百个线程
for (int i = 0; i < 100; i++) {
// 匿名内部类的方法创建线程对象
new Thread(new Runnable() {
@Override
public void run() {
// 打印获取到的实例对象的哈希码,如果一样则说明为同一个对象
System.out.println(Mgr02.getInstance().hashCode());
}
}).start;
}
}
}
运行 main 方法,我们可以在控制台发现打印的对象的哈希码是一样的,说明这100个线程对象获取到的 INSTANCE 对象本质上都是同一个;
2.3 静态内部类方式加载
静态内部类是通过JVM的方式保证线程安全,JVM会保证每个类之加载一次,但类中的静态内部类是不会被加载的,只有当我们调用 get 方法时,get 方法会返回给我们一个静态内部类的INSTANCE,而INSTANCE在静态内部类中指代的就是外部类的实例对象,非常神奇,非常花哨,并且可以做到线程安全,任何人来访问get方法获取Mgr03对象的实例,获取到的都是同一个INSTANCE。
实现代码如下所示
public class Mgr03 {
// 私有化构造方法
private Mgr03(){}
// 定义静态内部类,静态内部类不会随着类的加载而加载
private static class Mgr03Helper{
private final static Mgr03 INSTANCE = new Mgr03();
}
// 定义静态 get 方法
public static Mgr03 getInstance(){
return Mgr03Helper.INSTANCE;
}
// 此处为 测试 main方法,测试是否有线程安全问题
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Mgr03.getInstance().hashCode());
}
}).start();
}
}
}
运行此方法,如下图所示,所有对向的哈希码都是一样的,
2.4 枚举类加载方式
枚举类实现单例模式的方法更为简单,我们只需要定义一个枚举类,然后在枚举类中定义一个INSTANCE对象,枚举类会在变量的前面默认添加 "public static final",所以不需要我们再额外添加
// 定义一个枚举类 Mgr01
public enum Mgr04 {
// 定义 INSTANCE 对象
INSTANCE;
// 此处为 main 测试方法,测试是否线程安全
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Mgr04.INSTANCE.hashCode());
}
}).start();
}
}
}
运行 main 方法,可以在控制台发现所有的对象的哈希码都是一样的,是线程安全的