Singleton 单例模式

Posted yuanbing1226

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Singleton 单例模式相关的知识,希望对你有一定的参考价值。

单例模式

单例模式限制了一个类的实例化,并确保java虚拟机中只存在一个类的实例。
单例类必须提供一个全局访问点来获取类的实例。
单例模式用于日志记录,驱动程序对象,缓存和线程池。
Singleton设计模式也用于其他设计模式,如Abstract Factory,Builder,Prototype,Facade等。

要点

私有的构造函数
公共静态方法,返回类的实例,这是外部世界获取单例类的实例的全局访问点。

一般单例模式

package com.ice.singleton;
/**
 * Created by yuanbing on 2018/3/20.
 */
public class EagerInitializedSingleton {
    private EagerInitializedSingleton() {
    }

    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

    public static EagerInitializedSingleton getInstance(){
        return instance;
    }
}

这种在初始化时,Singleton类的实例是在类加载时创建的,这是创建单例类的最简单的方法,但它有一个缺点即使客户端应用程序可能没有使用它也会创建实例。

静态块初始化

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;

    private StaticBlockSingleton() {
    }

    static {
        try {
            instance = new StaticBlockSingleton();
        } catch (Exception e){
            throw new RuntimeException("there is an exception", e);
        }
    }

    public static StaticBlockSingleton getInstance(){
        return instance;
    }
}

延迟初始化

public class LazyInitializedSingleton {
    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton(){}

    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

上面的实现对单线程环境工作良好,但是当涉及到多线程系统时,如果多个线程同时处于if循环内部,则会导致问题。它会破坏单例模式,两个线程都会得到单例类的不同实例。在下一节中,我们将看到创建线程安全单例类的不同方法。

线程安全的单例模式

public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

//    public static synchronized ThreadSafeSingleton getInstance(){
//        if(instance == null){
//            instance = new ThreadSafeSingleton();
//        }
//        return instance;
//    }
    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }

}

这种方式实现了线程安全的单例模式,但是在高并发场合使用时性能很糟糕。所以不推荐在高并发环境中使用。
Bill Pugh Singleton实现
-----
在Java 5之前,Java内存模型存在很多问题,以上方法在某些情况下失败,因为太多的线程试图同时获得Singleton类的实例。所以Bill Pugh提出了一种使用内部静态帮助类来创建Singleton类的另一种方法。Bill Pugh Singleton的实现是这样的;

public class BillPughSingleton {
    private BillPughSingleton(){};

    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}

注意包含单例类的实例的私有内部静态类。加载单例类时,SingletonHelper类不会加载到内存中,只有当有人调用getInstance方法时,才会加载此类,并创建Singleton类实例。这是Singleton类最广泛使用的方法,因为它不需要同步。

使用反射破坏单例模式

import java.lang.reflect.Constructor;
public class ReflectionSingletonTest {
    public static void main(String[] args) {
        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                //Below code will destroy the singleton pattern
                constructor.setAccessible(true);
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
    }
}

Enum Singleton

public enum EnumSingleton {
    INSTANCE;
    public static void doSomething(){
        //do something
    }
}

为了克服Reflection的这种情况,Joshua Bloch建议使用Enum来实现Singleton设计模式,因为Java确保任何枚举值在Java程序中仅实例化一次。由于Java Enum值是全局访问的,单例也是全局可访问的。缺点是枚举类型有点不灵活; 例如,它不允许延迟初始化

序列化和单例

有时在分布式系统中,我们需要在Singleton类中实现Serializable接口,以便可以将它的状态存储在文件系统中,并在稍后的时间点检索它。

public class SerializedSingleton implements Serializable {
    private static final long serialVersionUID = -7604766932017737115L;

    private SerializedSingleton(){}

    private static class SingletonHelper{
        private static final SerializedSingleton instance = new SerializedSingleton();
    }

    public static SerializedSingleton getInstance(){
        return SingletonHelper.instance;
    }
}

public class SingletonSerializedTest {

    @Test
    public void test() throws IOException, ClassNotFoundException {
        SerializedSingleton instanceOne = SerializedSingleton.getInstance();
        ObjectOutput out = new ObjectOutputStream(
                new FileOutputStream("filename.ser"));

        out.writeObject(instanceOne);
        out.close();

        ObjectInput in = new ObjectInputStream(
                new FileInputStream("filename.ser"));

        SerializedSingleton instanceTwo = (SerializedSingleton)in.readObject();
        in.close();

        System.out.println("instanceOne hashCode = " + instanceOne.hashCode());
        System.out.println("instanceTwo hashCode = " + instanceTwo.hashCode());
    }
}

instanceOne hashCode = 895328852
instanceTwo hashCode = 1922154895

运行上述测试代码会发现,犯序列化的两个对象不是同一个对象,为了解决这个问题我们需要做的就是提供readResolve()方法的实现。

protected Object readResolve() {
    return getInstance();
}

参考资料

https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
《java高并发程序设计》

以上是关于Singleton 单例模式的主要内容,如果未能解决你的问题,请参考以下文章

浅谈JAVA设计模式之——单例模式(Singleton)

C++之单例(singleton)模式

Java面向对象--单例(Singleton)设计模式和main方法

Java之单例模式(Singleton)

设计模式:单例模式(Singleton)

单例模式(Singleton)