单例模式
一、定义
确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例
二、优点
- 减少内存开支。特别是一个对象需要频繁的创建、销毁时,并且创建或销毁时性能有无法优化。
- 减少了系统的性能开销。当一个对象的产生需要较多的资源时,如读取配置,产生其他依赖对象等。
- 避免对资源的多重占用,例如写文件动作,如果只有一个实例在内存中,可以避免对同一个资源文件的同时写操作
- 可以在系统设置全局的访问点,优化和共享资源访问
三、缺点
- 单例模式一般没有接口,扩展困难
- 测试困难,在并行开发环境中,如果单例模式没有完成,是不能进行测试的
- 单例模式与单一职责原则有冲突。
四、使用场景
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据
- 创建一个对象需要消耗的资源过多
- 需要定义大量的静态常量和静态方法的环境,如工具类
五、注意事项
- 在高并发的情况下,要注意单例模式的线程同步问题
- 考虑对象的复用
- 坏单例模式
六、单例类型
1. 饿汉模式
饿汉模式其实就是一种预加载机制,在使用前就完成对象的实例化
-
示例
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() { } public static Singleton getInstance() { return INSTANCE; } }
-
优点
-
实现简单,避免了线程同步
-
如果单例对象的实例化比较耗时,调用时可以做到快速使用,无需再在实例化上花费太多时间
-
-
缺点
在类加载时就完成了实例化,如果一直没有用到这个实例,就会造成内存浪费
2. 懒汉模式(不同步)
懒汉模式其实就是懒加载的意思,在使用时再进行实例化,不用则不创建对象
-
示例
public class Singleton2 { private static Singleton2 singleton; private Singleton2() { } public static Singleton2 getInstance(){ if (singleton== null){ singleton=new Singleton2(); } return singleton; } }
-
优点
实现了懒加载,在不使用时节省内存
-
缺点
线程不安全,在高并发下不能保证全局单例
3.同步方法
通过同步方法加锁,使其线程安全
-
示例
public class Singleton3 { private static Singleton3 singleton; private Singleton3() { } public synchronized static Singleton3 getInstance(){ if (singleton== null){ singleton=new Singleton3(); } return singleton; } }
-
优点
通过简单的添加了synchronized关键字就显示了线程安全
-
缺点
同步方法效率低,每次访问getInstance()都需要同步,而实际上只要第一次调用需要执行实例化时同步就可以了
4.同步代码块
将同步方法换成同步代码块,以增加调用getInstance方法的效率
-
示例
public class Singleton4 { private static Singleton4 singleton; private Singleton4() { } public static Singleton4 getInstance() { if (singleton == null) { synchronized (Singleton4.class) { singleton = new Singleton4(); } } return singleton; } }
-
优点
同步方法换成同步代码块,以增加调用getInstance方法的效率
-
缺点
并不能保证实例的全局唯一性,当一个线程进入了if (singleton == null)判断语句内,还未进行实例化,此时另一个线程获得了执行机会
将会导致第二个线程进入if语句块,最后导致多次实例化
5.双检锁
通过两次判空的方式优化第四个方法
-
示例
public class Singleton5 { private static Singleton5 singleton; private Singleton5() { } public static Singleton5 getInstance() { if (singleton == null) { synchronized (Singleton5.class) { if (singleton == null) { singleton = new Singleton5(); } } } return singleton; } }
-
优点
提升了getInstance的效率
-
缺点
不能真正保证线程安全,实例化一个对象分为三步,1.分配内存空间、2.初始化对象、3.将对象指向刚分派的内存空间,但有的编译器为了性能问题,会将第2步跟第3步进行重排序,可能会发生一个线程走了1,3步,此时第二个线程获得执行权,走到if语句块发现singleto不为null,然后将实例取出并进行操作,而实际上实例并没有初始化,就会出现问题。
6.带volatile关键字的双检锁
通过双检索的方式优化第四个方法
-
示例
public class Singleton6 { private static volatile Singleton6 singleton; private Singleton6() { } public static Singleton6 getInstance() { if (singleton == null) { synchronized (Singleton6.class) { if (singleton == null) { singleton = new Singleton5(); } } } return singleton; } }
-
优点
volatile确保了指令执行顺序,不会重排序
既可以保证线程安全,有提升了getInstance的效率
-
缺点
volatile关键字在Java 5 以前不能确保代码的执行顺序,只有Java 5及之后的jvm才能确保结果正常
7.静态内部类
通过静态内部类的方式实现懒加载,与线程安全
-
示例
public class Singleton7 { private Singleton7() { } public static Singleton6 getInstance() { return Holder.INSTANCE; } private static class Holder { private static Singleton7 INSTANCE = new Singleton7(); } }
-
优点
巧用jvm的类加载机制,即实现了懒加载,还保证了线程的安全性,并且保证了实例的唯一性,强烈推荐使用
8.枚举
-
示例
public enum Singleton8 { INSTACE; private Singleton8() { } }
-
优点
实现简单、不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
-
缺点
枚举是在JDK 1.5后加入的,1.5前的版本无法使用