单例模式的定义:单例对象的类必须保证只有⼀个实例存在。
单例模式的优点:系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。同时可避免对共享资源的多重占用。
缺点:不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。 可读性差,想实例化一个单例类的时候,需要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
使用场合: 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。 有状态的工具类对象。 频繁访问数据库或文件的对象。
单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例,一种是懒汉式单例。
饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用。
而懒汉式在调用取得实例方法的时候才会实例化对象。代码如下:
//单例-饿汉式
public class Singleton {
private static final Singleton1 instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
//单例-懒汉式
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return Holder.SINGLETON;
}
private static class Holder {
private static final Singleton SINGLETON = new Singleton();
}
}
每个人的单例模式的写法可能会不太一样。以饿汉式为例,有的会写在静态常量中完成实例化,有的会在写在静态代码块中完成实例化。
但总的来说万变不离其宗,大家总结一下会发现单例模式包含这3个要素:
私有的构造方法
指向自己实例的私有静态引用
以自己实例为返回值的静态的公有的方法。
题外话(其实是为了预防有人忘了所以提一嘴):
静态变量和静态代码块是类加载的时候执行。
静态方法调用的时候才会初始化执行。
因此可以看到上面演示用的代码,饿汉式是在静态变量中完成实例化。
而懒汉式是在静态方法中完成实例化。
上文中的懒加载代码只是为了演示和饿汉式的区别,但只能在单线程下使用。如果在多线程下会产生多个实例。所以在多线程环境下不可使用这种方式。
一般会使用双重检查锁来完成懒汉式的实例化:
//单例-懒汉式
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (null == instance) { //1
synchronized (Singleton.class) { //2
if (null == instance) { //3
instance = new Singleton(); //4
}
}
}
return instance; //5
}
}
Volatile在这里的作用是禁止指令重排。为什么要在这个例子里加这个关键字呢?
这是因为在满足特定条件下,处理器和编译器会对指令进行重排序,
a分配内存空间
b初始化对象
c将对象只想刚分配的内存空间
有些编译器为了性能的原因,可能会将第⼆步和第三步进⾏重排序,顺序就成了:
a分配内存空间
c将对象指向刚分配的内存空间
b初始化对象
在刚才的饿汉式单例例子中(我标注了5行),当线程A在执行第4行代码时,B线程进来执行到第1行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第5行并返回一个未初始化的对象。
因此为了避免这种情况会加volatile关键字来禁止重排。
单例模式是能够被反射和反序列化破坏所破坏的。我们一般会采取下列方案来解决:
对于反射,我们可以在单例的私有构造器中添加判断单例是否已经构造的代码,如果单例之前已经构造,则抛出异常。
对于反序列化,我们可以定义readResolve方法,直接返回方法指定的对象,而不会单独再创建对象。
代码如下(以懒汉式为例):
//单例-懒汉式
public class Singleton implements Serializable{
private volatile static Singleton instance;
//在构造器中做判断,如果单例被破坏则抛异常
private Singleton() {
if (null != instance) {
throw new RuntimeException();
}
}
public static Singleton getInstance() {
if (null == instance) { //1
synchronized (Singleton.class) { //2
if (null == instance) { //3
instance = new Singleton(); //4
}
}
}
return instance; //5
}
//反序列化时,如果定义了readResolve方法,则直接返回此方法指定的对象,而不会再创建对象
private Object readResolve() throws ObjectStreamException{
return instance;
}
}