设计模式学习笔记2-单例模式

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();在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。

猜你喜欢

转载自blog.csdn.net/ZHLittleRed/article/details/82744258