一、概念
1.1 定义
它是一种创建类的对象的模式,能够确保系统中只产生该类的一个对象。
1.2 作用
- 可以省略那些被频繁使用的对象的创建时间,节省系统开销。
- 降低内存使用频率,减轻GC压力,缩短GC停顿时间。
二、种类
2.1 饿汉式单例
public class Singleton1 {
/**
* 必须有一个private修饰的构造器
*/
private Singleton1() {
System.out.println("Singleton instance is create!!");
}
//该成员变量必须用static修饰
private static Singleton1 instance = new Singleton1();
/**
* 创建实例的方法必须用static修饰
*/
public static Singleton1 getInstance() {
return instance;
}
}
优点:实现方式简单,可靠。
缺点:无法实现延迟加载,由于instance成员变量是static的,在jvm加载类时单例对象就会被创建,而不管该类是否能被用到。
2.2 懒汉式单例
public class Singleton2 {
private Singleton2() {
System.out.println("LazySingleton instance is create!!");
}
/**
* instance赋值null,确保系统加载时没有额外负载
*/
private static Singleton2 instance = null;
/**
* 注意:该方法必须是同步的,假如去掉同步关键字,在多线程环境下,加入线程正在新建单例,
* 完成赋值前,线程2进行instance是否为null判断,可能被认为是null,从而会导致多个实例被创建
* @return
*/
public static synchronized Singleton2 getInstance() {
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
优点:实现了延迟加载。
缺点:由于引入了synchronized 关键字,再多线程的环境下它的耗时要远远大于饿汉式单例。
2.3 静态内部类实现
public class Singleton3 {
private Singleton3() {
System.out.println("StaticSingleton instance is create!!");
}
private static class SingletonHolder{
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance() {
return SingletonHolder.instance;
}
}
优点:使用内部类来维护单例的实例,当该类被jvm加载时,内部类不会被实例化,当要获取单例实例时才会加载SingletonHolder,对instance进行初始化。同时,实例的建立实在类加载时完成,所以是多线程安全的。因此该方法即支持延迟加载又是线程安全的,算是很完美了。
缺点:有种极端情况,如果通过反射调用私有构造器依然会产生多个实例,一般不进行考虑。
2.4 能被串行化的单例
public class Singleton4 implements Serializable {
private Singleton4() {
System.out.println("Singleton instance is create!!");
}
private static Singleton4 instance = new Singleton4();
public static Singleton4 getInstance() {
return instance;
}
/**
* 如果去掉该方法,在反序列化后依然生成多个实例。
* 事实上,实现该方法后readObject()已经失效,返回值已经被readResolve()替代。
* @return
*/
private Object readResolve() {
System.out.println("method [readResolve()] is invoked!!");
return instance;
}
}
测试代码:
class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton4 s1 = null;
Singleton4 s = Singleton4.getInstance();
//将实例串行化到文件
FileOutputStream fos = new FileOutputStream("Singleton4.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s);
oos.flush();
oos.close();
//从文件读出原有的单例类
FileInputStream fis = new FileInputStream("Singleton4.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
//readObject()已经失效,已经被readResolve()。
s1 = (Singleton4) ois.readObject();
System.out.println(s==s1);
}
}
注意
序列化和反序列化会破坏单例,一般来说这种场景不多见。