单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
(一)懒汉式单例
1、适用于单线程环境
此方式在单线程的时候工作正常,但在多线程的情况下就有问题了。如果两个线程同时运行到判断instance是否为null的if语句,并且instance的确没有被创建时,那么两个线程都会创建
一个实例,此时类型Singleton1就不再满足单例模式的要求了。
public static Singleton1 getInstanceA() {
if (null == instance) {
instance = new Singleton1();
}
return instance;
}
2、双重检验锁
为了在多线程环境下,不影响程序的性能,不让线程每次调用getInstanceC()方法时都加锁,而只是在实例未被创建时再加锁,在加锁处理里面还需要判断一次实例是否已存在。
public static Singleton1 getInstanceC() {
// 先判断实例是否存在,若不存在再对类对象进行加锁处理
if (instance == null) {
synchronized (Singleton1.class) {
if (instance == null) {
instance = new Singleton1();
}
}
}
return instance;
}
3、静态内部类方式(推荐)
加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。 由于在调用 StaticSingleton.getInstance() 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的;由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。
总结:
优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。
public class StaticSingleton {
private StaticSingleton() {
}
public static StaticSingleton getInstance() {
return StaticSingletonHolder.instance;
}
private static class StaticSingletonHolder {
private static final StaticSingleton instance = new StaticSingleton();
}
public void methodA() {
}
public void methodB() {
}
public static void main(String[] args) {
StaticSingleton.getInstance().methodA();
StaticSingleton.getInstance().methodB();
}
}
(二)饿汉式单例
1、饿汉式
public class Singleton2 {
private static final Singleton2 instance = new Singleton2();
private Singleton2() {
}
public static Singleton2 getInstance() {
return instance;
}
}
2、枚举方式
创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量)。
public class Singleton {
public static void main(String[] args) {
Single single = Single.SINGLE;
single.print();
}
enum Single {
SINGLE;
private Single() {
}
public void print() {
System.out.println("hello world");
}
}
}
(三)总结
一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举方式来实现单例。