单例模式
单例模式
作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点(方法/属性)-->比如静态的getInstance()方法。
常见的应用场景:
Windows的Task Manager(任务管理器)
windows的Recycle Bin(回收站),回收站一直维护着仅有的一个实例。
读取配置文件的类,一般也只有一个对象。
网站的计数器。
应用程序的日志应用,日志文件一直处于打开状态,因为只能有一个实例去操作 ,否则内容不好追加。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
操作系统的文件系统,也是大的单例模式实现,一个操作系统只能有一个文件系统。
Application 也是单例的典型应用(Servlet编程中会涉及到)
在servlet编程中,每个Servlet也是单例 。
在spring MVC框架/struts1框架中,控制器对象也是单例。
单例设计模式的优点:
1、只生成一个实例,减少了系统性能花销。
当一个对象的创建需要比较多的资源时,如读取配置、产生其他依赖对象,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
2、单例模式可以在系统设置全局的访问点,优化共享资源访问。例如可以设计一个单例类,负责所有数据表的映射处理。
一、五种单例模式实现方式
主要:
1、饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
2、懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
3、双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用。)
4、静态内部类式(线程安全,调用效率高。 但是,可以延时加载。)
5、枚举单例(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)
1.1、饿汉式实现(单例对象立即加载-->声明该类的时候)
public class SingletonDemo02 { private static /*final*/ SingletonDemo02 s = new SingletonDemo02(); //唯一实例 private SingletonDemo02(){}; //私有化构造器 public static /*synchronized*/ SingletonDemo02 getInstance(){ //synchronized 可以省略。因为JVM只会加载一次,没有并发。 return s; } } public class Client { public static void main(String[] args) { SingletonDemo02 s1 = SingletonDemo02.getInstance(); SingletonDemo02 s2 = SingletonDemo02.getInstance(); System.out.println(s1==s2); //true } }
饿汉式单例模式,static(static 方法的加载时机-->在类加载的初始化阶段)变量会在类装载时初始化。-->而懒加载是在调用方法时才初始化。
此时也不会涉及多线程对象访问该对象的问题。虚拟机保证只会装载一次该类,并不会发生并发访问问题。因此可以省略synchronized关键字。
存在的问题:只是加载该类,并没有调用getInstance()。就没有应用到此单例对象。造成了资源的浪费。
1.2、懒汉式实现(单例对象延迟加载)
public class SingletonDemo01 { private static SingletonDemo01 s; private SingletonDemo01(){} public static synchronized SingletonDemo01 getInstance(){ if(s==null){ s = new SingletonDemo01(); } return s; } }
lazyload:懒加载,延迟加载,真正用到的时候才加载。
存在的问题:资源利用率提高。但是每次调用gettInstance()方法都要同步,并发效率低。
1.3、双重检测锁实现
public class SingletonDemo03 { private static SingletonDemo03 instance = null; public static SingletonDemo03 getInstance(){ if(instance==null){ SingletonDemo03 s; synchronized (SingletonDemo03.class){ s = instance; if(s ==null){ synchronized (SingletonDemo03.class){ if(s==null){ s = new SingletonDemo03(); } } instance = s; } } } return instance; } }
1.4、静态内部类实现方式(懒加载的一种)
public class SingletonDemo04 { private SingletonDemo04(){ } private static class SingletonInstance{ private static final SingletonDemo04 instance = new SingletonDemo04(); } public static SingletonDemo04 getInstance(){ return SingletonInstance.instance; } }
外部类没有static属性,则不会像饿汉式那样立即加载对象。
只有真正调用getInstance()才会加载静态内部类。加载类时线程是安全的。instance是static final(不能在修改) 类型的,保证了内存中只有这样一个实例存在。而且只能被赋值一次,从而保证了线程安全。
兼备了并发高效调用和延迟加载的优势。
存在的问题:
1、反射可以破解上述几种(不包含枚举式)实现方式。(可以在构造函数中手动抛出异常控制。)
2、反序列化可以破解上面几种(不含枚举式)实现方式。
可以通过定义readResolve()方式获得不同的对象。
反序列化时,如果对象所在类定义了readResolve()(实际是一种回调),定义返回哪个对象。
在ObjectInputStream 类中 会检查序列化的类中是否有 readResolve 方法。
单例类:
public class SingletonDemo01 implements Serializable{ private static SingletonDemo01 s; private SingletonDemo01(){ //私有化构造器 if(s!=null){ throw new RuntimeException("只能创建一个对象"); //通过手动抛出异常,避免通过反射创建多个单例对象! } } public static synchronized SingletonDemo01 getInstance() throws Exception { if(null==s){ s= new SingletonDemo01(); } return s; } //反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象 private Object readResolve() throws ObjectStreamException { return s; } }
破解与解决:
public class SingleDemo01Hack { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { // Class cls = Class.forName("com.itheima.demo04.singletonhack.SingletonDemo01"); Class cls = SingletonDemo01.class; /* //1、通过暴力反射,并且将私有化的构造方法设置为public的,使用构造方法对象的newInstance(),就能破解单例模式 Constructor constructor = cls.getDeclaredConstructor(); constructor.setAccessible(true); Object o1 = constructor.newInstance(); Object o2 = constructor.newInstance(); System.out.println(o1==o2); //false*/ //2、通过暴力反射,并且将私有化的构造方法设置为public的,使用getInstance(),并在私有的构造方法中进行单例对象是否为空的判断 Method getInstance = cls.getMethod("getInstance"); Constructor constructor = cls.getDeclaredConstructor(); constructor.setAccessible(true); Object o1 = constructor.newInstance(); Object obj1 = getInstance.invoke(o1); Object obj2 = getInstance.invoke(o1); // Object obj3 = constructor.newInstance(); //此时要通过构造方法创建新的对象就会报错 System.out.println(obj1==obj2); //true //使用反序列化破解,和解决方法 writeSingleton(o1); readSingleton(o1); } private static void writeSingleton(Object o1){ ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("day10IO/singleton.txt")); oos.writeObject(o1); } catch (IOException e) { e.printStackTrace(); }finally { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } private static void readSingleton(Object o1){ ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream("day10IO/singleton.txt")); Object obj1 = ois.readObject(); //即使是构造方法私有化了,也能读取对象 ois = new ObjectInputStream(new FileInputStream("day10IO/singleton.txt")); Object obj2 = ois.readObject(); System.out.println(obj1==obj2); //true } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } }
1.5、使用枚举实现单例模式
public enum SingletonDemo05 { /*定义一个枚举元素,代表了SingletonDemo05的一个实例*/ INSTANCE; /*单例可以有自己的操作*/ public void singletonOperations(){ //功能处理 } } public class Demo05Test { public static void main(String[] args) { SingletonDemo05 sd1 = SingletonDemo05.INSTANCE; SingletonDemo05 sd2 = SingletonDemo05.INSTANCE; System.out.println(sd1==sd2); } }
优点:
实现简单。
枚举本身就是单例模式。由JVM从根本上提供保障。避免通过反射和反序列化的漏洞。
缺点:
无延时加载。
常见的五种单例模式在多线程环境下的效率测试
饿汉式 22ms
懒汉式 636ms
静态内部类 28ms
枚举类 32ms
双重检测锁式 65ms
如何选用?
单例对象,占用资源少,不需要延时加载:
枚举式好于饿汉模式
单例对象,占用资源大,需要延时加载:
静态内部类式好于懒汉模式

浙公网安备 33010602011771号