1 单例模式
1.1 懒汉式(线程不安全)
public class Singleton {
private static Singleton instance;
private Sinleton() {
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式单例模式是在得到实例的时候进行对象的初始化,但这个形式的单例不是线程安全的。
1.2 懒汉式(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
这个形式的单例模式使用synchronized关键字对getInstance()进行同步操作,所以在得到实例的时候是线程安全的。
1.3 饿汉式(线程安全)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
饿汉式单例模式是在类加载的时候就实例化的,避免了多线程的同步,利用空间换时间,但这种方式类一加载就机型instance的初始化,没有达到懒加载的效果。
1.4 双重校验
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return instance;
}
}
双重检验是利用两次校验对instance进行判断instance是否被实例化,如果没有别实例化则进行同步操作,在同步操作里面再进行判断(防止第一次判断时有几个线程同时进入),如果没有实例化则进行实例化,这样的好处就是可以大大减少同步的用时,大大节省时间。
在这里我么你对类变量instance采用了volatile关键字进行修饰,那么这个关键字到底起到了什么作用呢?首先我们要了解volatile关键字的作用,volatile关键字主要是为了保证可见性以及防止指令重排序,在这里volatile主要的作用就是防止指令重排序,即当两个线程A、B同时调用getInstance()方法时,A先进行判断发现instance==nullj进入同步,把对象锁关闭这时B再进来是就在等待了,A在进行new操作的时候,类初始化一般情况是先进行声明(在单例类中已经申明了),其次是进行内存的分配,之后是对象的初始化,最后返回类的句柄,但这是一般情况,但有指令重排序的作用,可能会先返回类的句柄,但是类还没有进行初始化,返回句柄后,B进入同步块发现instance已经被实例化了(实际上还没有初始化,只是返回了句柄),这时B直接返回实例instance(未初始化的对象),这时就出为问题了,所以我们添加了volatile关键字来修饰instance保证指令不会重排序,这时B也就不会返回一个没有被初始化的句柄了。
1.5 静态内部类方式
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这里利用了类记载机制来保证初始化instance时只有一个线程,这种方式在Singleton类被装载也不一定会被初始化,因为:SingletonHolder类没有被制动使用,只有通过显示的调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance(这就保证了懒加载)。
1.6 枚举方式
enum Singleton{
INSTANCE;
public void otherMethods(){
System.out.println("Something");
}
}
优点:自由序列化,线程安全,保证单例;
使用方法:直接Singleton.INSTANCE;即得到一个实例。
enum是通过继承了Enum类实现的,enum结构不能够作为子类继承其他类,但是可以用来实现接口。
enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。这里展开说下这个private构造器,如果我们不去手写构造器,则会有一个默认的空参构造器,我们也可以通过给枚举变量参量来实现类的初始化。
对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。
在序列化中会通过反射调用无参构造函数创建一个新的对象。
外加:双重检测的变形(防止序列化破坏单例模式)
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
private Object readResolve() {
return instance;
}
}
这个在Singleton中添加了一个readResovle();在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。