常见的设计模式(二)—单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
实现方式
1.懒汉式:线程不安全,懒加载
public class Singleton { private static Singleton instance=null; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
2.线程安全懒汉式:效率很低
public class Singleton{ private static Singleton instance; private Singleton(){} public static synchronized Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } }
3.饿汉式
基于classloader机制,线程安全,没有锁,效率相对提高。但在类加载就初始化,浪费内存。
public class Singleton{ private static Singleton instance=new Singleton(); private Singleton(){}; public static Singleton getInstance(){ return instance; } }
关于其名字由来:懒汉是在你真正用到的时候才去建这个单例对象。饿汉是不管你用不用的上,一开始就创建一个单例对象。
4.双重校验锁(DCL即double checked locking)
安全且在多线程情况下能保持高性能。
public class Singleton{ private volatile static Singleton singleton; private Singleton(){} public static Singleton getSingleton(){ if(singleton==null){ synchronized(Singleton.class){ if(singleton==null){ singleton=new Singleton(); } } } return singleton; } }
1.为啥要判断两次空?
在同步块前判空是为了提高性能,如果没有判空,每个进入getInstance()都会得到一个静态内部锁,先判空能大大减少同步块的执行次数。第二次同步块里的判空是保证多线程的单例。
2.为啥要加volatile关键字
至于volatile关键字是避免指令重排,这就要归咎于JAVA的内存模型允许所谓的“无序写入”。具体情况可以看看这篇博文
5.静态内部类
public class Singleton{ private static class SingletonHolder{ private static final Singleton INSTANCE=new Singleton(); } private Singleton(){} public static final Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
这种方式能达到双重锁一样效果,对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
只有在要明确实现 lazy loading 效果时,才会使用这种方式。
6.枚举
public enum Singleton { INSTANCE; public void whateverMethod() { } }
它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
浙公网安备 33010602011771号