1、模式说明java
有时候,咱们但愿在应用程序中,仅生成某个类的一个实例,这时候须要用到单例模式。缓存
2、模式类图安全
3、模式中的角色多线程
4、代码示例并发
单例模式比较简单,要实现单例模式只需保证三点:ide
package com.designpattern.cn.singletonpattern; public class Singleton { private static Singleton singleton = new Singleton(); //也可使用静态域,在类加载时建立实例 //static {singleton = new Singleton();} private Singleton(){ System.out.println("Instance created!"); } public static Singleton getInstance(){ return singleton; } }
测试类运行结果以下:函数
上面的代码中,Singleton类的方法和成员属性都是静态的,缘由是咱们不能直接建立Singleton类的实例,可是想经过类的方法调用获取实例,所以方法必须是静态的,同时,静态方法只能操做静态成员,因此对象也是静态的。测试
另外注意到,Singleton类的构造函数被设置为私有的,这样能够避免经过调用new方法来直接建立对象。ui
5、扩展——几种不一样模式的单例模式实现及其线程安全性分析spa
上面的代码示例中实现的单例模式被称为“饿汉模式”,由于类在加载的过程当中,单例就已经初始化完成,确保在获取Instance的时候,实例是已经存在的了。所以饿汉模式是线程安全的单例模式实现,能够直接用于多线程环境。而且因为是在类的加载阶段就生成了实例,所以第一次调用获取对象方法时效率高。缺点是:不管程序最终是否会用到这个类,这个类都会被建立,会占用必定的内存。
与饿汉模式相对的,若是咱们在加载类的时候不建立实例,等到第一次调用getInstance方法时才生成实例对象,这种作法就是“懒汉模式”。
懒汉模式相比于饿汉模式,因为是在第一次调用getInstance方法时才经过构造函数建立对象,若是对象的建立代码比较复杂,会影响第一次获取对象的效率。同时,懒汉模式若是不作特殊处理,很明显是线程不安全的,若是要使用懒汉模式又想用在多线程环境中,有几种方式实现线程安全:
一、第一个能想到的方法就是加锁。
package com.designpattern.cn.singletonpattern; public class LazySingletonThreadLock { private static LazySingletonThreadLock lazySingletonThreadLock = null; private LazySingletonThreadLock(){}; public static synchronized LazySingletonThreadLock getInstance(){ if(lazySingletonThreadLock == null){ lazySingletonThreadLock = new LazySingletonThreadLock(); } return lazySingletonThreadLock; } }
这种写法的最大缺点就是效率低!每一个线程在想得到类的实例调用getInstance()方法都要进行同步,实际上这个方法只执行一次实例化代码就够了,后面每次获取实例直接return就好了,不用每次都让方法同步。
二、第二个方法:双重校验锁
package com.designpattern.cn.singletonpattern; public class LazySingletonDoubleCheck { //双重检查方式 private static volatile LazySingletonDoubleCheck lazySingletonDoubleCheck = null; private LazySingletonDoubleCheck(){} public static LazySingletonDoubleCheck getInstance(){ if(lazySingletonDoubleCheck == null){ synchronized (LazySingletonDoubleCheck.class){ if(lazySingletonDoubleCheck == null){ lazySingletonDoubleCheck = new LazySingletonDoubleCheck(); } } } return lazySingletonDoubleCheck; } }
这种写法经过两次判断lazySingletonDoubleCheck成员是否为空,从而决定是否建立实例,一旦实例建立后,后续调用获取对象的方法时,直接返回对象,优势:线程安全、延迟加载、高效。
这里必需要插播一下volatile这个java关键字:volatile关键字用以声明变量的值可能随时会别的线程修改,使用volatile修饰的变量会强制将修改的值当即写入主存,主存中值的更新会使缓存中的值失效。volatile具有可见性和有序性,不具有原子性。
三、第三种写法:静态内部类
package com.designpattern.cn.singletonpattern; public class LazySingletonStaticInnerClass { private LazySingletonStaticInnerClass(){} //静态内部类 private static class singletonInstance{ private static final LazySingletonStaticInnerClass INSTANCE = new LazySingletonStaticInnerClass(); } public static LazySingletonStaticInnerClass getInstance(){ return singletonInstance.INSTANCE; } }
这种写法比较推荐,这种方式看上去像是饿汉模式,实际上有区别,因为静态内部类的特性,在外部类加载时并不会立刻实例化,只有在调用getInstance方法时,才会实例化类。这里,JVM帮助咱们保证了线程安全性,当JVM进行类的初始化时,其余线程是没法进入的。保证了线程安全、延迟加载,效率高。
四、第四种写法:利用枚举类(实现单例模式的最佳作法)
建立一个对象的方式有多种:new,克隆,序列化,反射。前三种状况,上面的饿汉模式和线程安全的懒汉模式均可以保证生成实例的惟一性,可是对于最后一种——反射,没法保证明例的惟一性:经过反射能够获取到类的构造方法(即便声明构造方法是private的也没用,反射能够打破一切封装,可是枚举例外)
package com.designpattern.cn.singletonpattern; public enum SingletonEnum { INSTANCE; }
可使用SingletonEnum.INSTANCE方式获取枚举的实例。因为枚举的构造方式和单例模式很像(构造方法私有化),并且不用考虑序列化等问题。所以使用枚举来构建单例模式是目前最好的作法,而且不被反射打破。(能够参考阅读《Effective Java》)
6、相关的模式