单例模式

单例模式

单例模式,是一种常用的软件设计模式。单例模式用于保证系统中,某一个类只有一个实例。即一个类只有一个对象实例。例如,Hibernate的SessionFactory类,该类是一个重量级的类,里面包含了数据库的所有信息,对于一个数据库应该只有一个SessionFactory实例。

单例模式的实现方式有很多,主要分为懒汉模式和饿汉模式。

一  单例模式实现

懒汉模式

public class LazySingleton {
    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

    public static LazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

单例模式就是把构造方法给私有化,使外部无法直接创建对象,而需要我们提供的公开方法来获取,在我们的获取对象方法中,先判断对象是否为空,为空则才会创建一个新对象返回,那么就保证了外部使用的始终是同一个对象。

缺点:线程不安全。

这种写法在极端情况下是非线程安全的,例如,当一个线程判断对象为空,那么开始执行new操作来创建对象,我们知道,在new对象的时候是非常消耗性能的,她并不是一个原子性的操作,那么在并发情况下,另一个线程同时进入方法,在判断对象是否为空的时候,因为上一个线程的new对象还没有完成,此时返回为true,也进入代码块进行对象创建,那么得到的就不是同一个对象了。如果要实现线程安全,我们可以为其加上同步锁来保证线程的同步。

    public synchronized static LazySingleton getLazySingleton(){
        if(lazySingleton==null){
            lazySingleton=new LazySingleton();
        }
        return lazySingleton;
    }
}

加上同步锁能够很好的解决线程安全的问题,可这里又有新的问题了,我们知道对锁的操作是十分消耗性能的,这里每次调用方法都要先获取锁,十分影响效率,因此,我们可以使用双重验证锁的方式来实现单例模式。 

public static LazySingleton getLazySingleton(){
        if(lazySingleton==null){
            synchronized (LazySingleton.class) {
                if(lazySingleton==null) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }

这里只有第一次创建对象的时候才会获取对象锁,这样即保证了线程安全,又不影响效率,目前这是最完美的懒汉模式实现方式。

饿汉模式

public class EagerSingleton{
    private static final EagerSingleton EAGER_SINGLETON = new EagerSingleton();

    private EagerSingleton() {

    }

    public static EagerSingleton getEagerSingleton() {
        return EAGER_SINGLETON;
    }
}

饿汉模式在类加载的时候就初始化了对象,其保证了线程安全性,因为其在类加载的时候就被初始化了,很有可能造成内存垃圾,假如这个实列一直没被调用,那么这个对象是不应该被创建的,对于这种情况我们可以使用懒汉模式。一般情况下,在工作中,饿汉模式用的比较多,其实现简单,只有在类加载的时候稍慢一点,返回对象特别快。

再来看一种内部类实现的懒汉模式,又名登记式
public class InnerClassSingleton{

    private InnerClassSingleton() {

    }

    public static InnerClassSingleton getInnerClassSingleton() {
        return InnerClassSingletonHolder.INNER_CLASS_SINGLETON;
    }

    private static class InnerClassSingletonHolder {
        private static final InnerClassSingleton INNER_CLASS_SINGLETON = new InnerClassSingleton();
    }
}

这种方式是利用JVM的类加载机制保证了线程安全来实现单例模式,在使用InnerClassSingleton其它方法的时候都不会创建该实例,只有调用getInnerClassSingleton()方法的时候,开始调用静态内部类,此时静态内部类才开始加载,初始化实例。

还有一种特殊的饿汉模式实现,通过枚举类来实现单例模式
public enum EnumSingleton {
    INSTANCE;
    public void execute(){
        System.out.println("执行方法");
    }
}

枚举类不用多说,和静态类类似,当类加载的时候进行初始化,值得注意的是,枚举类是在jdk1.5才出现的,所以用的传统的饿汉模式的人比较多。

二 在特殊需求下,单例模式的问题

有些时候我们需要把对象进行序列化与反序列化,下面我们就来测试反序列化后的对象与原对象是否是同一个对象。

public class EagerTest {
    public static void main(String[] args) throws Exception{
        EagerSingleton singleton=EagerSingleton.getEagerSingleton();
        FileOutputStream outputStream=new FileOutputStream("eager");
        ObjectOutputStream oup=new ObjectOutputStream(outputStream);
        oup.writeObject(singleton);
        oup.close();
        outputStream.close();
        FileInputStream inputStream=new FileInputStream("eager");
        ObjectInputStream ois=new ObjectInputStream(inputStream);
        EagerSingleton eagerSingleton=(EagerSingleton) ois.readObject();
        ois.close();
        inputStream.close();
        System.out.println(singleton==eagerSingleton);
    }

}

运行结果:

单例模式_第1张图片

首先不要忘记类要实现Serializable接口,这里以饿汉模式为例进行测试,可以看到反序列化后两个对象比较为false,说明这种方式创建的单例在反序列化后会出现问题,不能保证实例的唯一性了。解决这个问题只需要在类中加入下面的方法

public class EagerSingleton implements Serializable{
    private static final EagerSingleton EAGER_SINGLETON = new EagerSingleton();
    private static final long serialVersionUID = -6655710258921072343L;

    private EagerSingleton() {

    }

    public static EagerSingleton getEagerSingleton() {
        return EAGER_SINGLETON;
    }

    private Object readResolve() throws ObjectStreamException {
        return getEagerSingleton();
    }
}

这里新加入了readResolve()方法,该方法在反序列化的时候会被自动调用,我们再次运行测试代码,看运行结果

单例模式_第2张图片

再来对枚举类实现的单例模式进行反序列化测试

public class Test {
    public static void main(String[] args) throws Exception{
        EnumSingleton singleton=EnumSingleton.INSTANCE;
        FileOutputStream outputStream=new FileOutputStream("enum");
        ObjectOutputStream oup=new ObjectOutputStream(outputStream);
        oup.writeObject(singleton);
        oup.close();
        outputStream.close();
        FileInputStream inputStream=new FileInputStream("enum");
        ObjectInputStream ois=new ObjectInputStream(inputStream);
        EnumSingleton enumSingleton=(EnumSingleton) ois.readObject();
        ois.close();
        inputStream.close();
        System.out.println(singleton==enumSingleton);
    }
}

运行结果:

单例模式_第3张图片

总结:使用枚举类实现的单例模式在反序列化情况下不会发生问题,反序列化生成的对象与原对象一致,其它方式实现的单例模式在反序列化中会出现问题,反序列化生成的对象与原对象不一致,会使单例模式的实例产生多个的问题,解决方法,在类中加上readResove()方法。

你可能感兴趣的:(23种设计模式,单例模式,饿汉模式,懒汉模式,设计模式,singleton)