单例模式---对于整个系统只须要一个实体就能完成工做的状况下,咱们系统只须要一个实体而且保证只有一个实例,避免形成资源浪费java
1.懒汉ios
懒汉模式是在须要用到该实例的时候才进行实例化 安全
优势:节约资源,在须要用到该实例的时候才初始化多线程
缺点:线程非安全,并发访问状况下,有可能屡次实例化,而且每次实例化都覆盖上一次的实例并发
public class Singleton { private static Singleton SINGLETON; private Singleton(){} public static Singleton getInstance(){ if(Singleton.SINGLETON == null); Singleton.SINGLETON = new Singleton(); return SINGLETON; } }
2.饿汉ide
饿汉单例模式在类加载的时候就实例化性能
优势:安全,不存在并发建立多实例问题优化
缺点:容易形成资源浪费,一直占用着资源且没法回收ui
public class Singleton { private static final Singleton SINGLETON = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return SINGLETON; } }
3.懒汉模式(方法加锁)this
这种模式在获取实例的时候添加synchronize同步锁能避免多并发状况下形成建立多实例问题
优势:具备懒汉模式的节约资源优势,且方法加锁状况下避免了多并发建立屡次实例的状况
肯定:方法锁消耗性能比较大,必须是第一访问完整个方法才到第二次访问进入
public class Singleton { private static Singleton SINGLETON; private Singleton(){} public synchronized static Singleton getInstance(){ if(Singleton.SINGLETON == null); Singleton.SINGLETON = new Singleton(); return SINGLETON; } }
4.双重锁校验(推荐)
双重锁校验是优化了方发锁的方式而来,优化啊了多并发状况下性能低下的结果
优势:保证了线程安全状况下,节约资源且访问性能高
public class Singleton { private static Singleton SINGLETON; private Singleton(){} public static Singleton getInstance(){ if(Singleton.SINGLETON == null){ synchronized (Singleton.class) { if(Singleton.SINGLETON == null) { Singleton.SINGLETON = new Singleton(); } } } return SINGLETON; } }
进入方法体以后首先判断了实例是否存在,若是存在,则直接返回实例,不然加锁执行多一次判断,若是为null再实例化。由于第一次判断和加锁之间,对象可能已经实例化,因此加锁以后再判断一次,避免屡次建立。可是这种方式还有点缺陷,synchronized关键字能够保证多线程状况下同步问题,若是是多核计算机(如今绝大部分都是多核计算机)状况下,还会有一个指令重排的问题因此咱们须要用volatile 来修饰SINGLETON,最后改形成下面代码
public class Singleton { private volatile static Singleton SINGLETON; private Singleton(){} public static Singleton getInstance(){ if(Singleton.SINGLETON == null){ synchronized (Singleton.class) { if(Singleton.SINGLETON == null) { Singleton.SINGLETON = new Singleton(); } } } return SINGLETON; } }
5.静态内部类
静态内部类是在调用的时候才会进行加载,是懒汉模式另一种实现方式
public class Singleton { private Singleton(){} public static Singleton getInstance(){ return Instance.singleton; } private static class Instance{ private static final Singleton singleton = new Singleton(); } }
6.枚举
枚举为最优的单例模式实现方案,由于能够防反射暴力建立对象,也能够避免序列化问题,下面先放了一个简单的例子,
public enum SingletonEnum { SINGLETON; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public static SingletonEnum getInstance(){ return SINGLETON; } }
看一下使用方式
public static void main(String[] args) { SingletonEnum.SINGLETON.setName("name1"); System.out.println(SingletonEnum.SINGLETON.getName()); }
输出结果,因而可知 SingletonEnum.SINGLETON 时调用的都是同一个实例
下面咱们看看枚举类型防放射暴力建立实例
咱们用以前静态内部类的那个代码来比较
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 反射获取构造器 Constructor<Singleton> singletonConstructor = Singleton.class.getDeclaredConstructor(); // 经过构造器建立对象 Singleton singleton1 = singletonConstructor.newInstance(); // 经过咱们单例获取实例的接口获取实例 Singleton singleton2 = Singleton.getInstance(); // 下面结果为false,证实是2个不同的实例,甚至都不用调用构造器的 setAccessible() 就能成功新建一个实例 System.out.println(singleton1 == singleton2); }
接下来再看看枚举
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 反射获取构造器 Constructor<SingletonEnum> singletonEnumConstructor = SingletonEnum.class.getDeclaredConstructor(); // 经过构造器建立对象 SingletonEnum singletonEnum1 = singletonEnumConstructor.newInstance(); // 获取单例 SingletonEnum singletonEnum2 = SingletonEnum.SINGLETON; // 下面结果为false,证实是2个不同的实例,甚至都不用调用构造器的 setAccessible() 就能成功新建一个实例 System.out.println(singletonEnum1 == singletonEnum2); }
这时候报是报了个java.lang.NoSuchMethodException,缘由是由于枚举类型没有无参构造
下面咱们进入debug模式能够看到只有一个带一个String参数和一个int参数的构造方法
因此改形成
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
// 反射获取构造器
Constructor<SingletonEnum> singletonEnumConstructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
// 经过构造器建立对象
SingletonEnum singletonEnum1 = singletonEnumConstructor.newInstance("",1);
// 获取单例
SingletonEnum singletonEnum2 = SingletonEnum.SINGLETON;
// 下面结果为false,证实是2个不同的实例,甚至都不用调用构造器的 setAccessible() 就能成功新建一个实例
System.out.println(singletonEnum1 == singletonEnum2);
}
可是改过来以后报了个 java.lang.IllegalAccessException 非法访问异常
缘由是若是实例化的对象是个枚举类型,就会抛出这个异常,这说明枚举类型天生就是单例的
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
序列化与反序列化,若是咱们实体须要储存到程序之外的存储媒介,当再次获取时候,这个实例并不是咱们最开始的实例
序列化的时候实体类必须实现 Serializable
public class Singleton implements Serializable{}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { // 经过获取实例接口获取实例 Singleton singleton1 = Singleton.getInstance(); // 建立输出流而且输出到文件 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\singleton\\singleton.txt")); oos.writeObject(singleton1); // 建立输入流而且反序列化实例 ObjectInputStream ios = new ObjectInputStream(new FileInputStream("D:\\singleton\\singleton.txt")); Singleton singleton2 = (Singleton) ios.readObject(); oos.close(); ios.close(); System.out.println(singleton1 == singleton2); }
序列化先后的对象结果对比,不是同一个实例
再看看枚举类型
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { SingletonEnum singletonEnum1 = SingletonEnum.SINGLETON; // 建立输出流而且输出到文件 ObjectOutputStream oosE = new ObjectOutputStream(new FileOutputStream("D:\\singleton\\singletonE.txt")); oosE.writeObject(singletonEnum1); // 建立输入流而且反序列化实例 ObjectInputStream iosE = new ObjectInputStream(new FileInputStream("D:\\singleton\\singletonE.txt")); SingletonEnum singletonEnum2 = (SingletonEnum) iosE.readObject(); oosE.close(); iosE.close(); System.out.println(singletonEnum1 == singletonEnum2); }
序列化先后的对象是一致的,没有被破坏
因此单例的最优方案是枚举,其余方法都会由于反射或者序列化破坏了整个系统只有一个实例的原则,固然根据业务要求选择一种比较合适目前开发团队的方案也很重要