单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点
优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
常见的五种单例模式实现方式
- 饿汉式: 线程安全,调用效率高,不能延时加载
- 懒汉式: 线程安全,调用效率不高,可以延时加载
- 双重检测锁: 由于JVM底层内部模型原因,偶尔会出问题,不建议使用
- 静态内部类: 线程安全,调用效率高,可以延时加载
- 枚举单例: 线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞
饿汉式
public class SingletonDemo {
private static /*final*/ SingletonDemo s = new SingletonDemo();
private SingletonDemo(){} // 私有化构造器
public static /*synchronized*/ SingletonDemo02 getInstance(){
return s;
}
}
static
变量会在类装载时初始化,此时也不会涉及多个线程访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此可以省略synchronized
关键字
注: 如果只是加载本类,而不是要调用getInstance()
,甚至永远没有调用,则会造成资源浪费
懒汉式
public class SingletonDemo01 {
private static SingletonDemo01 s;
private SingletonDemo01(){} // 私有化构造器
public static synchronized SingletonDemo01 getInstance(){
if (s == null) {
s = new SingletonDemo01();
}
return s;
}
}
lazy load:延迟加载,懒加载,真正用的时候才加载
注: 资源利用率高,但是每次调用getInstance()
方法都要同步,并发效率低
静态内部类:兼备了并发高效调用和延迟加载的优势
public class SingletonDemo04 {
private static class SingletonClassInstance {
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance() {
return SingletonClassInstance.instance;
}
private SingletonDemo04() {}
}
外部类没有static属性,则不会像饿汉式那样立即加载对象。只有真正调用getInstance()
才会加载静态内部类。加载类是线程安全的,instancec
是static final
类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全。
以上单例模式存在的问题
通过反射和反序列化都能破解上面几种实现方式
解决方案
public class SingletonDemo01 implements Serializable {
private static SingletonDemo01 s;
private SingletonDemo01() throws Exception{
if(s!=null){
throw new Exception("只能创建一个对象");
// 通过手动抛出异常,避免通过反射创建多个单例对象!
}
}
// 私有化构造器
public static synchronized SingletonDemo01 getInstance() throws Exception{
if(s==null){
s = new SingletonDemo01();
}
return s;
}
// 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
private Object readResolve() throws ObjectStreamException {
return s;
}
}
枚举实现单例
public enum SingletonDemo05 {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例。
*/
INSTANCE;
/**
* 单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
}
注: 实现简单,枚举本身就是单例模式,由JVM从根本上提供保障,避免通过反射和序列化的漏洞
场景选择
占用资源少,不需要延时加载
- 枚举式好于饿汉式
占用资源多,需要延时加载
- 静态内部类好于懒汉式