单例模式

逆流者 2020年08月05日 38次浏览

1 简介

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类
只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

比如: Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session 对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory就够,这是就会使用到单例模式。

2 单例设计模式的方式

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举

2.1 饿汉式(静态常量)

/**
 * 1、饿汉式(静态常量) 测试 class
 *
 * @author wsh
 * @date 2019-07-12 23:18
 */
public class SingletonTest1 {

    public static void main(String[] args) {
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //ture
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

/**
 * 1) 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
 * 2) 缺点:在类装载的时候就完成实例化,没有达到LazyLoading的效果。如果从始至终从未使用过这个实例,则
 * 会造成内存的浪费
 * 3) 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,
 * 在单例模式中大 多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,
 * 因此不能确定有其他的方式(或者其他的静 态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果
 * 4) 结论:这种单例模式可用,可能造成内存浪费
 */
class Singleton {

    /**
     * 1、私有化构造器,防止外部 new 对象
     */
    private Singleton() {

    }

    /**
     * 2、对象内部创建对象实例
     */
    private final static Singleton INSTANCE = new Singleton();

    /**
     * 3、提供一个公有的静态方法,获取实例对象
     */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

结果:

true
instance1.hashCode=1580066828
instance2.hashCode=1580066828

2.2 饿汉式(静态代码块)

/**
 * 2、饿汉式(静态变量) 测试 class
 *
 * @author wsh
 * @date 2019-07-12 23:31
 */
public class SingletonTest2 {

    public static void main(String[] args) {
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //ture
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

/**
 * 和 test1 一样
 * 1) 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,
 * 就执 行静态代码块中的代码,初始化类的实例。优缺点和test1是一样的。
 * 2) 结论:这种单例模式可用,但是可能造成内存浪费
 */
class Singleton {

    /**
     * 1、私有化构造器,防止外部 new 对象
     */
    private Singleton() {

    }

    /**
     * 2、对象内部声明对象实例
     */
    private static Singleton instance;

    /**
     * 3、在静态代码块中,创建单例对象
     */
    static {
        instance = new Singleton();
    }

    /**
     * 4、提供一个公有的静态方法,获取实例对象
     */
    public static Singleton getInstance() {
        return instance;
    }
}

2.3 懒汉式(线程不安全)

/**
 * 3、懒汉式(线程不安全)class
 *
 * @author wsh
 * @date 2019-07-16 22:10
 */
public class SingletonTest3 {
    public static void main(String[] args) {
        System.out.println("懒汉式(线程不安全) test.....");
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //ture
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton {

    private static Singleton instance;

    private Singleton() {}

    /**
     * 提供一个公有的静态方法,使用时,才去创建实例
     * 懒汉式
     * @return
     */
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

利弊分析:

2.4 懒汉式(线程安全,同步方法)

public class SingletonTest4 {
    public static void main(String[] args) {
        System.out.println("懒汉式2(线程安全, 同步方法) test.....");
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //ture
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton {

    private static Singleton instance;

    private Singleton() {}

    /**
     * 提供一个公有的静态方法,使用时,才去创建实例
     * 加入同步处理的代码,解决线程安全问题
     * 懒汉式
     * @return
     */
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

2.5 懒汉式(线程安全,同步代码块)

/**
 * 5、懒汉式(线程安全,同步方法)class
 *
 * @author wsh
 * @date 2019-07-16 22:20
 */
public class SingletonTest5 {

    public static void main(String[] args) {
        System.out.println("懒汉式3(线程安全,同步方法) test.....");
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //ture
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton {

    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

2.6 双重检查

/**
 * 6、双重检查 class
 *
 *     ******** 推荐使用 *******
 *
 * @author wsh
 * @date 2019-07-16 22:25
 */
public class SingletonTest6 {
    public static void main(String[] args) {
        System.out.println("懒汉式4(双重检查、线程安全、延迟加载) test.....");
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //ture
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton {

    private static volatile Singleton instance;

    private Singleton() {}

    /**
     * 双层检查代码,线程安全问题,懒加载
     * 推荐使用这种方式,即保证了线程安全,又顾及了效率
     * @return
     */
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2.7 静态内部类

/**
 * 7、静态内部类 class
 * 1)类装载机制保证初始化实例时只有一个线程
 * 2)在Singleton 类被装载时不会立即实例化,需要实例化时,调用gitInstance 方法,才会装载 SingletonInstance 类,完成实例化
 * 3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行
 *    初始化时,别的线程是无法进入的。
 *
 *    ******** 推荐使用 *******
 *
 * @author wsh
 * @date 2019-07-16 22:55
 */
public class SingletonTest7 {

    public static void main(String[] args) {
        System.out.println("静态内部类 test.....");
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //ture
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}


class Singleton {

    private static volatile Singleton instance;

    private Singleton() {}

    /**
     * 静态内部类,有一个静态属性Singleton
     */
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    /**
     * 提供一个静态的公有方法,直接返回静态内部类的静态属性
     * @return
     */
    public static synchronized Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

2.8 枚举

/**
 * 8、枚举class
 *
 *    ******** 推荐使用 *******
 *
 * @author wsh
 * @date 2019-07-16 23:08
 */
public class SingletonTest8 {
    public static void main(String[] args) {
        System.out.println("枚举 test.....");
        //测试
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        //ture
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
        instance1.sayOK();
    }
}

enum  Singleton {
    INSTANCE;

    public void sayOK() {
        System.out.println("ok~");
    }
}

3 单例模式在源码中的应用

3.1 JDK中 Runtime 类(饿汉式)

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {
    }
    /*...........*/
}

4 总结

  • 单例模式保证了系统内存中只存在一个对象, 可以节省系统资源,对于频繁创建销毁的对象,可以使用单例模式来提高系统性能;
  • 使用建议:
    一般情况下,建议使用饿汉方式。
    只有在要明确实现 lazy loading 效果时,使用静态内部类方式。
    如果涉及到反序列化创建对象时,可以尝试使用枚举方式。
    如果有其他特殊的需求,可以考虑使用双检锁方式。

设计模式分为 三种类型,共 23 种

  • 创建型模式:
    单例模式(Singleton Pattern)
    工厂模式(Factory Pattern)
    抽象工厂模式(Abstract Factory Pattern)
    建造者模式(Builder Pattern)
    原型模式(Prototype Pattern)
  • 结构型模式:
    适配器模式(Adapter Pattern)
    桥接模式(Bridge Pattern)
    过滤器模式(Filter、Criteria Pattern)
    组合模式(Composite Pattern)
    装饰器模式(Decorator Pattern)
    外观模式(Facade Pattern)
    享元模式(Flyweight Pattern)
    代理模式(Proxy Pattern)
  • 行为型模式:
    责任链模式(Chain of Responsibility Pattern)
    命令模式(Command Pattern)
    解释器模式(Interpreter Pattern)
    迭代器模式(Iterator Pattern)
    中介者模式(Mediator Pattern)
    备忘录模式(Memento Pattern)
    观察者模式(Observer Pattern)
    状态模式(State Pattern)
    空对象模式(Null Object Pattern)
    策略模式(Strategy Pattern)
    模板模式(Template Pattern)
    访问者模式(Visitor Pattern)