作用
保证一个类只有一个实例,并且向外提供一个访问点。
应用场景
windows中的任务管理器就是一个很典型的单例模式。
读取配置文件的类一般只new一个对象,没必要在每次读取配置文件时重新new一个对象。
网站计数器也采用单例模式,否则很难同步
应用程序的日志文件通常只用一个对象来维护
数据库链接池的设计也采用单例模式。
文件系统也是一个单例模式,一个操作系统只能有一个文件系统。
在Spring中,每个Bean默认是单例的,这样Spring容器易于管理。
javaweb中的每个Servlet都是一个单例
优点
因为只产生一个实例,所以减少了系统开销。可以在系统设置全局访问点,优化共享资源访问。
分类
主要
-
饿汉式(线程安全,调用效率高,但不能延时加载)
- 懒汉式(线程安全,调用效率不高,可以延时加载)
其它
- 双重检测锁式 (由于JVM的原因,有时会出现问题)
- 静态内部类式(线程安全,调用效率高,可以延时加载)
- 枚举单例(线程安全,调用效率不高,不能延时加载)
举例
-
饿汉式
一上来就创建对象,立即加载,存在的问题就这个对象可能会一直用不到,白白浪费内存资源
package com.dy.xidian; public class SingleDemo1 { //类初始化是立即加载对象 //jvm只会将类加载一次,在加载类时就创建了一个实例 private static SingleDemo1 instance = new SingleDemo1(); //构造器私有化,不能被new对象 private SingleDemo1(){ } //对外提供一个访问接口,因为只有一个实例存在,所以线程安全 public static SingleDemo1 getInstance(){return instance;} }
- 懒汉式
用时才创建对象,延时加载。因为加入了同步机制,所以调用效率不高
package com.dy.xidian; public class SingleDemo2 { private static SingleDemo2 instance; private SingleDemo2() { } // 在调用的时候才会实例化一个对象 // 存在竞态条件,应进行同步 public static synchronized SingleDemo2 getInstance() { if (instance == null) instance = new SingleDemo2(); return instance; } }
- 双重检测锁实现
将同步块放到了方法的内部,这样不仅能延时加载,而且提高的调用效率。但是由于编译器优化以及JVM底层内部模型原因,偶而会出现问题,不建议使用package com.dy.xidian;public class SingleDemo3 private static SingleDemo3 instance;
private SingleDemo3() { } public static SingleDemo3 getInstance() { if (instance == null) {
synchronized(SingleDemo3.class) {
if (instance == null)
instance = new SingleDemo3();
}
}
return instance;
} }
- 静态内部类模式
JVM初始化类SingleDemo4时并不会去初始话其内部内,所以避免了向饿汉式那样立即加载对象。
只有真正调用getInstance(),才会加载内部类。加载类时是线程安全的,而final能保证内存中只有这样一个实例存在,兼备了高效调用与延迟加载的优势。
package com.dy.xidian; public class SingleDome4 { private SingleDome4(){} private static class SingleClassInstance{ private static final SingleDome4 instance = new SingleDome4(); } public static SingleDome4 getInstance(){ return SingleClassInstance.instance; } }
- 枚举方式
枚举类本来就是单例的,但是它不能延时加载
package com.dy.xidian; public enum SingleDome4 { INSTANCE; String a = new String(); // 处理方法 public static void main(String[] args) { System.out.println(SingleDome4.INSTANCE); SingleDome4.INSTANCE.a = "ff"; System.out.println(SingleDome4.INSTANCE.a); } }
存在问题
单例模式的关键点就是构造器私有化,但是通过反射机制将构造器变成可被外部调用,这样的话单例就会被破坏,对于这种情况解决方案如下。
package com.dy.xidian; public class SingleDome4 { private static SingleDome4 instance; //在构造器中抛出异常 private SingleDome4() { if(instance != null ) throw new RuntimeException(); } public synchronized SingleDome4 getInstance() { if (instance == null) instance = new SingleDome4(); return instance; } }
我们可以通过反序列化的方式产生多个的对象,如果一个单例对象被多次反序列化,那么该对象不再是单例的了,解决方案如下:
package com.dy.xidian; import java.io.ObjectStreamException; import java.io.Serializable; public class SingleDome4 implements Serializable{ private static SingleDome4 instance; private SingleDome4() { if(instance != null ) throw new RuntimeException(); } public synchronized SingleDome4 getInstance() { if (instance == null) instance = new SingleDome4(); return instance; } //基于回调的,在反序列化时该方法会直接被调用,而不是去创建一个新对象 private Object readResolve() throws ObjectStreamException{ return instance; } }