保证一个类只有一个实例,并且提供一个访问该示例的全局访问点。
常见场景:
- windows的Task Manager(任务管理器)就是很经典的单例模式
- windows的Recycle Bin(回收站)
- 项目中,读取配置的文件类,一般也只有一个,没有不要每次使用配置文件数据,每次new一个对象去读取
- 网站的计数器,一般也采用单例模式,否则难以同步
- 应用程序日志应用,一般都采用单例模式,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容也不好追加。
优点:
- 单例模式只生成一个实例。减少了系统性能的开销吗,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久存在内存的方式来解决;
- 单例模式可以在系统设置全局访问的访问点,优化工系哦啊资源访问。例如可以设计一个单例来负责所有的数据表的迎合处理。
常见的五种单例模式实现方式
- 饿汉式(线程安全,调用效率高,不能延时加载)
public class SingletonDemo1 {
//类初始化时立即加载这个对象(没有延时加载的优势),加载类时,是线程安全的.
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1() {}//私有构造器
//方法没有同步synchronized,调用效率高
public static SingletonDemo1 getInstance() {
return instance;
}
}
- 懒汉式(线程安全,调用效率不高,可以延时加载)
public class SingletonDemo1 {
//类初始化时,不加载这个对象(延时加载,真正用时候再创建)
private static SingletonDemo1 instance;
private SingletonDemo1() {
if (instance != null) {
throw new RuntimeException();
}
} //私有构造器
//资源利用率高了,但是每次调用方法度要同步,并发效率较低
public static synchronized SingletonDemo1 getInstance() {
return instance == null ? new SingletonDemo1() : instance;
}
}
DCL
- 双重检测锁式:
将同步内容下放到if内部,提高了执行的效率不必每次获取对象。
《Java并发编程实战》如果声明为volatile类型就能启用DCL,volatile变量读取操作对性能通常只是略高于非volatile变量读取操作的性能。然而,DCL的这种使用方法已经被广泛的废弃了,因而它不是一种高效的优化措施。”
public class SingletonDemo1 {
//类初始化时,不加载这个对象(延时加载,真正用时候再创建)
private static volatile SingletonDemo1 instance;
private SingletonDemo1() {} //私有构造器
//资源利用率高了,但是每次调用方法度要同步,并发效率较低
public static SingletonDemo1 getInstance() {
if(instance == null){
synchronized(SingletonDemo1.class ){
if(instance == null)
instance = new SingletonDemo1();
}
}
return instance;
}
}
- 静态内部类式(线程安全,调用效率较高,可以延时加载)
public class SingletonDemo1 {
private static class SingletonClassInstance {
private static SingletonDemo1 instance = new SingletonDemo1();
}
//私有构造器
private SingletonDemo1() {}
public static synchronized SingletonDemo1 getInstance() {
return SingletonClassInstance.instance;
}
}
外部类没有static属性,则不会像饿汉式立即加载。
只有真正调用getInstance()才会加载静态内部类。加载是线程安全的,instance是static final类型,保证了内存中只有这样一个实例,而且只能赋值一次。从而保证了线程的安全。
兼备了并发高效调用和延迟加载的优势
- 枚举单例(线程安全,调用效率高,避免根据反射和反序列化的漏洞,不能延时加载)
public enum Singleton {
INSTANCE;//这个枚举元素本身就是单例模式
public void singletonOperation() {
//todo
}
}
如何选用?
创建单例对象 占用 资源少,不需要延时加载:枚举 > 饿汉
创建单例对象 占用 资源大,需要延时加载:静态内部类式 > 懒汉式
问题:
反射可以破解单例(不含枚举)实现方式:可以在构造方法中手动抛出异常控制
反序列化可以破解单例(不含枚举)实现方式:通过定义readResolve()防止获得不同对象,定义返回指定对象。