单例模式---对于整个系统只需要一个实体就能完成工作的情况下,我们系统只需要一个实体并且保证只有一个实例,避免造成资源浪费
1.懒汉
懒汉模式是在需要用到该实例的时候才进行实例化
优点:节约资源,在需要用到该实例的时候才初始化
缺点:线程非安全,并发访问情况下,有可能多次实例化,并且每次实例化都覆盖上一次的实例
public class Singleton { private static Singleton SINGLETON; private Singleton(){} public static Singleton getInstance(){ if(Singleton.SINGLETON == null); Singleton.SINGLETON = new Singleton(); return SINGLETON; } }
2.饿汉
饿汉单例模式在类加载的时候就实例化
优点:安全,不存在并发创建多实例问题
缺点:容易造成资源浪费,一直占用着资源且无法回收
public class Singleton { private static final Singleton SINGLETON = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return SINGLETON; } }
3.懒汉模式(方法加锁)
这种模式在获取实例的时候添加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); }
序列化前后的对象是一致的,没有被破坏
所以单例的最优方案是枚举,其他方法都会因为反射或者序列化破坏了整个系统只有一个实例的原则,当然根据业务要求选择一种比较合适目前开发团队的方案也很重要
来源:oschina
链接:https://my.oschina.net/u/2925037/blog/3167775