简介
单例模式:一个类有且仅有一个实例,该类负责创建自己的对象,同时确保只有一个对象被创建。
特点:类构造器私有、持有自己实例、对外提供获取实例的静态方法。
单例模式的实现方式
饿汉式
类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。
案例:
java
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
饿汉式比较耗费资源,因为它创建单例的时间过早,它是在类被加载的时候创建单例的
懒汉式加双重检查加锁
饿汉式的优化,只有在获取类的实例时才会创建实例。
案例:
java
public class SingleTon {
// 使用volatile修饰,保证变量的可见性
private static volatile SingleTon instance;
public static SingleTon getInstance() {
// 先检查实例是否存在,如果不存在才进入下面的同步块
if (instance == null) {
// 同步块,线程安全的创建实例
synchronized (SingleTon.class) {
// 再次检查实例是否存在,如果不存在才真的创建实例
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}
饿汉式中的注意事项:
1、为什么要用volatile修饰成员变量:为了保证指定变量的有序性和可见性。new一个对象的代码 SingleTon instance = new SingleTon();
可以分解为3行伪代码:
text
memory=allocate();// 分配内存 相当于c的malloc
ctorInstanc(memory) //初始化对象
instance=memory //设置instance指向刚分配的地址
上面的代码在编译器运行时,可能出现重排序,从 1-2-3 变为 1-3-2,在多线程环境下就会出现问题,用户拿到的实际上是没有初始化的对象,使用 volatile 关键字会禁止这种重排序。
2、为什么要双重锁:如果只有一个锁,很有可能两个线程都通过了 if(instance == null)
的判断,所以在进入同步代码块之后还需要再判断一次
用静态内部类来实现单例模式
案例:
java
public class SingleTon {
private SingleTon2() { }
// 用一个私有的静态内部类来存储外部类的实例,类只会被加载一次,保证单例。
// 内部类只有在被调用时才会被加载,保证了延迟加载
private static class SingleTonHolder {
private static SingleTon2 instance = new SingleTon2();
}
public static SingleTon2 getInstance() {
return SingleTonHolder.instance;
}
}
破坏单例模式
破坏单例模式:序列化和反射可以破坏单例模式。
- 解决序列化破坏单例的问题:在类中添加readResolve方法,返回类中的实例,可以解决通过序列化破坏单例模式的问题
- 解决反射破坏单例的问题:在构造方法中抛异常,可以解决通过反射破坏单例模式的问题
单例模式的使用案例
饿汉式单例模式的使用:jdk中的Runtime类,每个java程序中都只有一个Runtime实例,它代表java程序的运行环境
java
public class Runtime {
// 类被加载时,就会实例化一个对象并交给自己的引用
private static Runtime currentRuntime = new Runtime();
// 返回单例对象的方法
public static Runtime getRuntime() {
return currentRuntime;
}
// 私有化的构造方法
/** Don't let anyone else instantiate this class */
private Runtime() {}
}