单例涉及模式分为三种:饿汉式、饱汉式和双检锁
饿汉式:线程安全,初始化类的时候创建对象
public class Singleton1 {
private static Singleton1 singleton1 = new Singleton1();
private Singleton1(){};
public static Singleton1 getInstance(){
return singleton1;
}
}
饱汉式:线程不安全,延迟初始化
public class Singleton2 {
private static Singleton2 singleton = null;
private Singleton2(){};
public static Singleton2 getInstance(){
if (singleton == null) {
return new Singleton2();
}
return singleton;
}
}
双检锁:线程安全,延迟初始化
public class Singleton3 {
private volatile static Singleton3 singleton3 = null;
private Singleton3(){}
public static Singleton3 getInstance(){
if (singleton3 == null) {
synchronized (Singleton3.class){
if (singleton3 == null) {
return new Singleton3();
}
}
}
return singleton3;
}
}
这里重点介绍一下双检锁:
在 Java 单例模式的双重检查锁(Double-Checked Locking)中,为了确保只有在第一次创建实例时才进行同步和实例化操作,需要在进入同步块之前和之后进行两次检查。这是因为双重检查锁需要同时满足以下两个条件:
-
第一次检查(非同步检查):在进入同步块之前,首先检查实例是否已经被创建。如果实例已经被创建,就无需进入同步块,直接返回已有的实例。这样可以避免不必要的同步开销。
-
第二次检查(同步检查):只有在第一次检查的基础上,才进入同步块进行实例化操作。进入同步块后,再次检查实例是否已经被创建。这是因为可能有多个线程同时通过了第一次检查,进入同步块之前,需要再次检查实例是否已经创建,避免重复创建实例。
这种双重检查的设计模式可以提供比直接使用同步方法更高的性能。它在第一次检查时避免了不必要的同步操作,只有在实例未创建时才进行同步和实例化操作。然后,在进入同步块之后再次检查,以确保只有一个线程创建实例。
需要注意的是,为了使双重检查锁模式在多线程环境下正确工作,要求实例属性必须声明为 volatile
,以确保可见性和内存屏障的正确使用。此外,在使用双重检查锁模式时,还需要确保实例的构造函数是私有的,以防止通过其他途径创建实例。