单例模式是一种常用的设计模式,其定义是单例对象的类只能允许一个实例存在。下面来看看几种常见的单例模式的写法,以及如何保证线程安全的实现。
1、饿汉式(线程安全)
这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。但是在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance ;
}
}
2、懒汉式(线程安全)
这种方式效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。也就是我们之前提到的同步的粒度太粗,synchronized 同步代码应该是越细越好。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
3、懒汉式(线程安全)的细粒度优化(双重锁机制)
对上一种模式进行优化,这里判断了两次是否为 null 是因为在并发环境中当线程一执行了第一个判断的时候是为null,可此刻另外一个线程正好执行完初始化操作,在释放锁以后该线程并不知道已经初始化,如果此刻进入代码块不进行再次判断会再初始化一次,这就违背了单例模式的初衷了。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
4、静态内部类(懒加载,线程安全)
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有懒加载的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。避免了线程不安全,延迟加载,效率高。
public class Singleton {
private Singleton() {}
//内部类在外部类调用的时候才会被初始化
// 内部类一定要在方法调用之前初始化
private static class SingletonInstance {
private static final Singleton instance = new Singleton();
}
// static 使单例空间共享
// final使得方法不能被重写重载
public static final Singleton getInstance() {
return SingletonInstance.instance;
}
}
这里可以在私有的构造方法中进行一个双重锁的判断,定义一个 flag来判断该构造是否被重复调用,来防止反射的侵入。
除此之外还可以使用枚举类的方式来实现单例模式。由于实际工作中并未发现有人这么做,这里就不演示了。