1.简介
单例模式是软件开发中常用的一种设计模式,它要确保某个类在系统中只有唯一的一个实例
2.应用场景
a.共享资源,保证数据的一致性,如配置文件
b.创建对象耗时或者消耗资源过多,但又经常使用到,如数据库连接池、线程池等c.工具类对象
3.实现方式
a.饿汉式
public class SingletonOne { private static final SingletonOne singletonOne = new SingletonOne(); private SingletonOne() { if (singletonOne != null) { // 防止反射获取多个对象的漏洞 throw new RuntimeException("instance has init"); } } public static SingletonOne getInstance() { return singletonOne; } // 防止反序列化获取多个对象的漏洞 private Object readResolve() throws ObjectStreamException { return singletonOne; }}复制代码
优点:
类加载时完成对象实例化,避免多线程下的同步问题,同时获取单例速度较快
缺点:
资源利用效率不高,单例可能一直用不到,但是已经初始化了,造成资源浪费
b.懒汉式
public class SingletonTwo { private static SingletonTwo singletonTwo = null; private SingletonTwo() { if (singletonTwo != null) { throw new RuntimeException("instance has init"); } } public static SingletonTwo getInstance(){ if (singletonTwo == null) { singletonTwo = new SingletonTwo(); } return singletonTwo; } // 防止反序列化获取多个对象的漏洞 private Object readResolve() throws ObjectStreamException { return singletonTwo; }}复制代码
优点:
懒加载,第一次调用时实例化对象,资源利用率高,后续调用速度快
缺点:
多线程场景下,会产生多个对象
c.懒汉式+锁
public class SingletonThree { private static SingletonThree singletonThree = null; private SingletonThree() { if (singletonThree != null) { throw new RuntimeException("instance has init"); } } public static SingletonThree getInstance() { synchronized (SingletonThree.class) { if (singletonThree == null) { singletonThree = new SingletonThree(); } } return singletonThree; } // 防止反序列化获取多个对象的漏洞 private Object readResolve() throws ObjectStreamException { return singletonThree; }}复制代码
优点:
通过加锁解决多线程场景下产生多个实例的问题
缺点:
加锁后,每次调用方法都需要获取锁,影响性能,其实只需要在第一次实例化对象时调用对象
d.双重检查锁(DCL)
public class SingletonFour { private static SingletonFour singletonFour = null; private SingletonFour() { if (singletonFour != null) { throw new RuntimeException("instance has init"); } } public static SingletonFour getInstance() { if (singletonFour == null) { synchronized (SingletonFour.class) { if (singletonFour == null) { singletonFour = new SingletonFour(); } } } return singletonFour; } // 防止反序列化获取多个对象的漏洞 private Object readResolve() throws ObjectStreamException { return singletonFour; }}复制代码
优点:
未实例化时才会去竞争锁来实例化,实例化后直接返回实例对象,提升性能同时解决多线程的同步问题
缺点:
由于jvm的指令重排问题,在多线程场景下会引用一个未初始化完成的对象,导致系统出现问题,具体原因如下:对象创建时,其实是分三步完成的,1)在堆上给对象分配内存,2)调用构造方法初始化此对象,3)将对象引用指向对应内存地址,由于jvm的指令重排机制,这三步可能不是顺序执行的,如果是1)3)2)顺序,3)完成2)还没完成时,另一个线程判断对象不为null会直接返回未初始化完全的对象,导致系统出现问题
e.双重检查锁(优化)
public class SingletonFour { private static volatile SingletonFour singletonFour = null; private SingletonFour() { if (singletonFour != null) { throw new RuntimeException("instance has init"); } } public static SingletonFour getInstance() { if (singletonFour == null) { synchronized (SingletonFour.class) { if (singletonFour == null) { singletonFour = new SingletonFour(); } } } return singletonFour; } // 防止反序列化获取多个对象的漏洞 private Object readResolve() throws ObjectStreamException { return singletonFour; }}复制代码
优点:
在双重检查锁的基础增加了volatile关键字,禁止指令重排,避免出现引用未初始化完成的对象
缺点:
禁用指令重排后,jvm的一些代码优化措施会无效,影响性能
f.内部静态类
public class SingletonFive { private SingletonFive() { // 防止反射获取多个对象的漏洞 if (Singleton.singleton != null) { throw new RuntimeException("instance has init"); } } private static class Singleton { private static final SingletonFive singleton = new SingletonFive(); } public static SingletonFive getInstance() { return Singleton.singleton; } // 防止反序列化获取多个对象的漏洞 private Object readResolve() throws ObjectStreamException { return Singleton.singleton; }}复制代码
推荐使用
懒加载,线程安全
内部类不会随着外部类初始化,只会在调用getInstance时才会初始化,而由于类的初始化<clinit>()会被加锁,所以也是线程安全的
g.枚举
public enum SingletonSix { singleton; }复制代码
推荐使用
只需调用SingletonSix. singleton,写法简单且线程安全,还能防止反序列化重新创建对象,只会存在一个实例
4.结语
单例模式实现方式常见有以上这几种,推荐使用内部静态类和枚举的方式实现单例。同时在使用过程中要注意避免反射和反序列化创建多个对象