设计模式之单例模式

143 阅读5分钟

饿汉式单例模式

饿汉式单例模式分为两种:1、静态变量的方式 2、静态代码块的方式

1、静态变量的方式的实现

/**
 * @author songle
 * @create 2021-05-22 20:32
 * @descreption 单例模式---饿汉式---静态变量
 */
public class HungerySingleton {
    public static void main(String[] args) {

        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1.equals(singleton2));
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }

}

class Singleton {

    // 1、构造器私有
    private Singleton(){}

    // 2、在本类的内部创建对象实例
    private final static Singleton instance = new Singleton();

    // 3、提供一个共有的静态方法,返回对象的实例
    public static Singleton getInstance() {
        return instance;
    }
}

**

2、静态代码块的方式的实现

/**
 * @author songle
 * @create 2021-05-22 20:32
 * @descreption 单例模式---饿汉式---静态代码块
 */
public class HungerySingleton2 {
    public static void main(String[] args) {

        Singleton2 singleton1 = Singleton2.getInstance();
        Singleton2 singleton2 = Singleton2.getInstance();
        System.out.println(singleton1.equals(singleton2));
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }

}

class Singleton2 {

    // 1、构造器私有
    private Singleton2(){}

    // 2、在本类的内部创建对象实例
    private static Singleton2 instance = null;

    // 3、静态代码块中实例化
    static {
        instance = new Singleton2();
    }

    // 3、提供一个共有的静态方法,返回对象的实例
    public static Singleton2 getInstance() {
        return instance;
    }
}

**

饿汉式单例模式的总结

优点:这种方式比较简单,就是在类装载的时候就完成了实例化,避免了线程的同步问题。
缺点:在类装载的时候就完成了实例化,无法达到懒加载的效果,如果这个实例一直没有被使用,就会造成内存浪费。
这种方式基于classLoader机制避免了线程同步问题,不过instance在类装载的时候就完成了实例化,大多数都是直接调用getInstance方法,但是导致类装载的原因会有很多,因此不能确定有其他方式导致类装载,难以达到懒加载的效果。
结论:这种单例模式可用,但是可能会造成内存浪费。

懒汉式单例模式

懒汉式单例模式分为三种:1、线程不安全懒汉式 2、线程安全懒汉式

1、线程不安全懒汉式的实现

/**
 * @author songle
 * @create 2021-05-22 21:01
 * @descreption 单例模式---懒汉式---线程不安全的实现方式
 */
public class UnSafeLazySingleton {

    // 1、构造器私有
    private UnSafeLazySingleton(){}

    // 2、在本类的内部创建对象实例
    private static UnSafeLazySingleton instance;


    // 3、提供一个共有的静态方法,返回对象的实例,当需要用到该实例的时候才去创建
    public static UnSafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new UnSafeLazySingleton();
        }
        return instance;
    }


    public static void main(String[] args) {
        UnSafeLazySingleton singleton1 = UnSafeLazySingleton.getInstance();
        UnSafeLazySingleton singleton2 = UnSafeLazySingleton.getInstance();
        System.out.println(singleton1.equals(singleton2));
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }


}

总结

优点:起到了懒加载的效果,但是只能在单线程下使用。
如果在多线程下,一个线程进入了if (instance == null) 但是还没有生成实例的时候,另一个线程也执行到了这里,这时便会生成多个实例,所以在多线程的情况下不能使用这种方式。
结论:在实际开发中不要使用这种方式。

2、线程安全懒汉式的实现

/**
 * @author songle
 * @create 2021-05-22 21:01
 * @descreption 单例模式---懒汉式---线程安全的实现方式
 */
public class SafeLazySingleton {

    // 1、构造器私有
    private SafeLazySingleton(){}

    // 2、在本类的内部创建对象实例
    private static SafeLazySingleton instance;


    // 3、提供一个共有的静态方法,返回对象的实例,当需要用到该实例的时候才去创建
    public static synchronized SafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new SafeLazySingleton();
        }
        return instance;
    }


    public static void main(String[] args) {
        SafeLazySingleton singleton1 = SafeLazySingleton.getInstance();
        SafeLazySingleton singleton2 = SafeLazySingleton.getInstance();
        System.out.println(singleton1.equals(singleton2));
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }


}

总结

解决了线程不安全的问题。
每个线程想要获得类的实例的时候,在执行getInstance方法的时候都要进行同步。而实际上这个方法只需要执行一次实例化代码,后面的线程想要获得该类的实例直接return就可以。所以这种方法的效率很低。
结论:在实际开发中不推荐使用这种方式。

双重检查单例模式

/**
 * @author songle
 * @create 2021-05-22 21:01
 * @descreption 单例模式---双重检查
 */
public class DoubleCheckSingleton {

    // 1、构造器私有
    private DoubleCheckSingleton(){}

    // 2、在本类的内部创建对象实例
    private static volatile DoubleCheckSingleton instance;


    // 3、双重检查,解决线程安全问题,并且解决了懒加载的问题,同时保证了效率问题
    public static synchronized DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }


    public static void main(String[] args) {
        DoubleCheckSingleton singleton1 = DoubleCheckSingleton.getInstance();
        DoubleCheckSingleton singleton2 = DoubleCheckSingleton.getInstance();
        System.out.println(singleton1.equals(singleton2));
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }


}

总结

解决了线程不安全的问题,同时解决了懒加载的问题。
实例化代码只会执行一次,下个线程进来就会就会直接return实例对象回去,提高了效率。
结论:在实际开发中推荐使用这种方式。

静态内部类单例模式

/**
 * @author songle
 * @create 2021-05-22 21:01
 * @descreption 单例模式---静态内部类
 */
public class InnercClassSingleton {

    // 1、构造器私有
    private InnercClassSingleton(){}

    // 2、写一个静态内部类,该类汇总有一个静态的属性
    private static class SingletonInstance {
        private static final InnercClassSingleton instance = new InnercClassSingleton();
    }

    // 3、直接返回类实例
    public static InnercClassSingleton getInstance() {
        return SingletonInstance.instance;
    }


    public static void main(String[] args) {
        InnercClassSingleton singleton1 = InnercClassSingleton.getInstance();
        InnercClassSingleton singleton2 = InnercClassSingleton.getInstance();
        System.out.println(singleton1.equals(singleton2));
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }


}

总结

这种方式采用了类装载的机制,保证了初始化的时候只有一个线程。
静态内部类在类装载的时候不会立刻实例化,而是在需要实例化的时候,调用getInstance方法,然后在内部类中完成实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化的时候,别的线程是无法进入的。
优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
结论:推荐使用。

枚举单例模式

/**
 * @author songle
 * @create 2021-05-22 21:01
 * @descreption 单例模式---枚举
 */
public class EnumSingleton {


    public static void main(String[] args) {
        SingletonEnum singleton1 = SingletonEnum.INSTANCE;
        SingletonEnum singleton2 = SingletonEnum.INSTANCE;
        System.out.println(singleton1.equals(singleton2));
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());

        singleton1.sayOk();

    }

}

enum SingletonEnum {
    INSTANCE;
    public void sayOk() {
        System.out.println("ok~");
    }
}

总结

不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。
结论:推荐使用。

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

jdk的Runtime类就是使用了饿汉式的单例模式