带你认识单例模式之七种不同实现方式,最后一种堪称王炸,你绝对没见过!

单例模式之七种不同实现方式

一、饿汉式

顾名思义,这是一个饿汉子,上来就将食物塞进去(实例化),这种方式最简单粗暴

实现方式

java 复制代码
/**
 * 饿汉式单例模式
 */
public final class HungrySingleton {

    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
        System.out.println("HungrySingleton is created");
    }

    public static HungrySingleton getInstance() {
        return instance;
    }

}

测试代码

java 复制代码
public static void testHungrySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName());
                    HungrySingleton instance = HungrySingleton.getInstance();
                    System.out.println(instance);
                }
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
HungrySingleton is created

二、懒汉式

懒汉式,这个方法非常懒,就像我上学时一样,作业不写,偏偏等到要上学的前几天才开始动手疯狂补作业。

java 复制代码
/**
 * 懒汉式单例模式
 * 不要一开始就初始化实例,而是等到要用到的时候再去初始化实例
 */
public final class LazySingleton {

    private static LazySingleton instance = null;

    private LazySingleton() {
        System.out.println("LazySingleton constructor");
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

}

测试代码

java 复制代码
/**
* 懒汉式单例模式
*/
public static void testLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                LazySingleton instance = LazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor

总结

上面代码在多线程情况下出现了多次实例化的情况,假如线程1正在做if (instance == null)判断,此时CPU开始调度,线程2进入if (instance == null)并且创建了一个实例,然后放弃CPU使用权,当线程1获取到CPU使用权之后就会从停止的地方继续开始执行未完成的代码,此时就会再一次实例化,出现多次实例化的情况。因此这种情况在多线程情况下并不能保证单例

三、synchronized方式

为了解决懒汉式多线程情况下多次实例化的问题,简单粗暴的方式就是加锁,直接getInstance方法上锁,保证仅一个线程同时访问此方法。

java 复制代码
public class SynchronizedLazySingleton {

    private static SynchronizedLazySingleton instance = null;

    private SynchronizedLazySingleton() {
        System.out.println("Synchronized Lazy Singleton");
    }

    public static synchronized SynchronizedLazySingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedLazySingleton();
        }
        return instance;
    }

}

测试代码

java 复制代码
public static void testSynchronizedLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedLazySingleton instance = SynchronizedLazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
Synchronized Lazy Singleton

思考

上面加锁的粒度是不是太大了,多线程情况下同时仅有一个线程才能访问getInstance方法了,有没有办法再优化一下呢,下面就是双重检查的实现方式了。

四、双重检查(double check)

在线程进入时判断一次,为空则加锁,进入之后再判断一次,至于为什么要再判断一次,这是为了防止两次实例化。假设线程1进入了第一个判断,此时释放了CPU;线程2进来之后,同样可以执行进入第一个判断并且加锁,然后实例化,最后释放CPU;然后等到线程1再次拿到CPU之后,从上一次未执行的地方开始,对DoubleCheckLazySingleton.class加锁,由于没有第二个判断,此时线程1就可以完成第二次实例化了;因此,这就是为什么要加两次判断的原因。

java 复制代码
/**
 * 双重检查懒汉式单例模式
 */
public class DoubleCheckLazySingleton {

    private static DoubleCheckLazySingleton instance = null;

    private DoubleCheckLazySingleton() {
        System.out.println("DoubleCheckLazySingleton is created");
    }

    public static DoubleCheckLazySingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckLazySingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }

}

测试代码

java 复制代码
    public static void testDoubleCheckLazySingleton() {
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    DoubleCheckLazySingleton instance = DoubleCheckLazySingleton.getInstance();
                }
            }, "Thread" + i).start();
        }
    }

运行结果

java 复制代码
DoubleCheckLazySingleton is created

五、双重检查+可见性(double check + volatile)

为什么要加volatile,因为我代码没有写完,如果此时单例类中还有其他的字段,就容易出现空指针异常,因为这些字段对于其它线程是不可见的,需要加上volatile保证对于其它线程也可见。

java 复制代码
/**
 * 双重检查懒汉式单例模式+volatile
 */
public class VolatileDoubleCheckLazySingleton {

    private volatile static VolatileDoubleCheckLazySingleton instance = null;

    private VolatileDoubleCheckLazySingleton() {
        System.out.println("VolatileDoubleCheckLazySingleton construct");
    }

    public static VolatileDoubleCheckLazySingleton getInstance() {
        if (instance == null) {
            synchronized (VolatileDoubleCheckLazySingleton.class) {
                if (instance == null) {
                    instance = new VolatileDoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }

}

测试代码

java 复制代码
/**
* 双重检查单例模式 + volatile
*/
public static void testVolatileDoubleCheckLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                VolatileDoubleCheckLazySingleton instance = VolatileDoubleCheckLazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
VolatileDoubleCheckLazySingleton construct

六、holder内部类实例化

借助于内部类来实例化单例,秀不秀?

java 复制代码
public class HolderSingleton {
    private static HolderSingleton instance;

    private HolderSingleton() {
        System.out.println("HolderSingleton constructor");
    }

    private static class Holder {
        private static final HolderSingleton INSTANCE = new HolderSingleton();
    }

    public static HolderSingleton getInstance() {
        return Holder.INSTANCE;
    }

}

测试代码

java 复制代码
/**
* Holder
*/
public static void testHolderSingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                HolderSingleton instance = HolderSingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
HolderSingleton constructor

七、枚举类实现方式

王炸!枚举类天然的定义就是单例,这没的说。

java 复制代码
public enum EnumSingleton {

    INSTANCE,
    ;

    private EnumSingleton() {
        System.out.println("INSTANCE will be initialized immediately");
    }

    public static void method() {

    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }

}

测试代码

java 复制代码
/**
* EnumSingleton
*/
public static void testEnumSingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                EnumSingleton instance = EnumSingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
INSTANCE will be initialized immediately
相关推荐
qmx_0714 分钟前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
为风而战23 分钟前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat
编程零零七1 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
技术无疆2 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
2401_858286112 小时前
52.【C语言】 字符函数和字符串函数(strcat函数)
c语言·开发语言
铁松溜达py2 小时前
编译器/工具链环境:GCC vs LLVM/Clang,MSVCRT vs UCRT
开发语言·网络
everyStudy2 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
C-SDN花园GGbond4 小时前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
迷迭所归处5 小时前
C++ —— 关于vector
开发语言·c++·算法
架构文摘JGWZ5 小时前
Java 23 的12 个新特性!!
java·开发语言·学习