单例模式是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。Java 里面实现的单例是一个虚拟机的范围,因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的 ClassLoad 装载实现单例类的时候就会创建一个类的实例。在 Java 语言中,这样的行为能带来两大好处:
-
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
-
由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
1.饿汉式
/**
* 饿汉式
* @author BaShen
*/
public class Singleton {
private User user;
private Singleton() {
user = new User();
}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
public User getUser() {
return user;
}
}
2.懒汉式
/** * 缺陷的懒汉式 * @author BaShen * */ public class SingletonOne { private SingletonOne() { } private static SingletonOne instance = null; public static SingletonOne getInstance() { if (instance == null) { synchronized (SingletonOne.class) { if (instance == null) { instance = new SingletonOne(); } } } return instance; } }
/**
* 下面我们开始说编译原理。所谓编译,就是把源代码“翻译”成目标代码——大多数是指机器代码——的过程。
* 针对Java,它的目标代码不是本地机器代码,而是虚拟机代码。
* 编译原理里面有一个很重要的内容是编译器优化。
* 所谓编译器优化是指,在不改变原来语义的情况下,通过调整语句顺序,来让程序运行的更快。
* 这个过程成为reorder(重排)。
* 要知道,JVM只是一个标准,并不是实现。JVM中并没有规定有关编译器优化的内容,也就是说,JVM实现可以自由的进行编译器优化。
* 下面来想一下,创建一个变量需要哪些步骤呢?
* 一个是申请一块内存,调用构造方法进行初始化操作,另一个是分配一个指针指向这块内存。这两个操作谁在前谁在后呢?
* JVM规范并没有规定。那么就存在这么一种情况,JVM是先开辟出一块内存,然后把指针指向这块内存,最后调用构造方法进行初始化。
* 下面我们来考虑这么一种情况:线程A开始创建SingletonClass的实例,此时线程B调用了getInstance()方法,首先判断instance是否为null。
* 按照我们上面所说的内存模型,A已经把instance指向了那块内存,只是还没有调用构造方法,因此B检测到instance不为null,
* 于是直接把instance返回了——问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,
* 但你并不能住进去,因为里面还没有收拾。此时,如果B在A将instance构造完成之前就是用了这个实例,程序就会出现错误了!
* 于是,我们想到了下面的代码:
*/
public class SingletonOne { private SingletonOne() { } private static SingletonOne instance = null; public static SingletonOne getInstance() { if (instance == null) { SingletonOne sc= null; synchronized (SingletonOne.class) { sc= instance; if(slo == null){ synchronized (SingletonOne.class) { if (sc == null) { sc= new SingletonOne(); } } instance = sc; } } } return instance; } }
/**
*我们在第一个同步块里面创建一个临时变量,然后使用这个临时变量进行对象的创建,并且在最后把instance指针临时变量的内存空间。
*写出这种代码基于以下思想,即synchronized会起到一个代码屏蔽的作用,同步块里面的代码和外部的代码没有联系。
*因此,在外部的同步块里面对临时变量sc进行操作并不影响instance,所以外部类在instance=sc;之前检测instance的时候,结果instance依然是null。
*不过,这种想法完全是错误的!
*同步块的释放保证在此之前——也就是同步块里面——的操作必须完成,但是并不保证同步块之后的操作不能因编译器优化而调换到同步块结束之前进行。
*因此,编译器完全可以把instance=sc;这句移到内部同步块里面执行。这样,程序又是错误的了!
*/
/** * 说了这么多,难道单例没有办法在Java中实现吗?其实不然! * 在JDK 5之后,Java使用了新的内存模型。volatile关键字有了明确的语义——在JDK1.5之前, * volatile是个关键字,但是并没有明确的规定其用途——被volatile修饰的写变量不能和之前的读写代码调整, * 读变量不能和之后的读写代码调整!因此,只要我们简单的把instance加上volatile关键字就可以了。 */
完整的懒汉式
/** * 完整的懒汉式 * @author BaShen */ public class SingletonOne { private SingletonOne() { } private volatile static SingletonOne instance = null; public static SingletonOne getInstance() { if (instance == null) { synchronized (SingletonOne.class) { if(instance == null){ instance = new SingletonOne(); } } } return instance; } }
完整的懒汉式
/** * 完整的懒汉式 * @author BaShen */ public class SingletonFive { private SingletonFive() { init(); } private void init() { user = new User(); } private User user; private static class LazyHolder { private static final SingletonFive INSTANCE = new SingletonFive(); } public static SingletonFive getInstance(){ return LazyHolder.INSTANCE; } public User getUser(){ return user; } }
3.枚举单例
/** * 枚举 推荐的单例 * @author BaShen */ public enum SingletonEnum { INSTANCE; private SingletonEnum() { user = new User(); } private User user = null; public User getUser() { return user; } }
4.登记式单例模式
/** * * @author BaShen */ public class SingletonRegistryManager { private static Map<String, Object> singletonMap = new HashMap<>(); /** * 获取实例 * @param className * @return */ public static Object getInstance(String className) { if (isRegistred(className)) { return singletonMap.get(className); } return null; } /** * 获取实例 * @param clazz * @return */ public static Object getInstance(Class clazz) { return getInstance(clazz.getName()); } /** * 注册 * @param className */ public synchronized static void registry(String className) { if (!singletonMap.containsKey(className)) { try { singletonMap.put(className, Class.forName(className).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /** * 注册 * @param clazz */ public synchronized static void registry(Class clazz) { registry(clazz.getName()); } /** * 反注册 * @param className */ public synchronized static void unRegistry(String className) { if (singletonMap.containsKey(className)) { singletonMap.remove(className); } } /** * 反注册 * @param clazz */ public synchronized static void unRegistry(Class clazz) { unRegistry(clazz.getName()); } /** * 判断是否注册 * @param className * @return */ public synchronized static boolean isRegistred(String className) { return singletonMap.containsKey(className); } public synchronized static boolean isRegistred(Class clazz) { return isRegistred(clazz.getName()); } }