java单例模式(六种)


概述

  1. 单例模式是23种设计模式中最常用的一种,属于设计模式中的创建型模式
  2. 单例模式主要作用是让类在应用生命周期中只存在一个实例,避免类的重复创建,降低创建实例的资源消耗,提高性能
  3. 单例模式主要应用场景如数据库连接池、线程池、应用配置、spring bean,对了,还有你哦😜

饿汉式
  顾名思义,饿汉式就跟你饿了一样,肯定想的是立马吃饭。而在代码里所表达的是立即创建实例。
  优点:写法简单,易于理解,线程安全
  缺点:立即加载,占用资源(当这个类越大,缺点越明显)

/**
 * 饿汉式
 * @author yz
 * @version 1.0
 * @date 2020/5/5 14:52
 */
public class HungrySingleton {

    /**
     * 好处:写法简单,易于理解,线程安全
     * 缺点:立即加载,占用资源
     */

    private static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}

懒汉式
  懒汉式和饿汉式最大的区别是只有进行调用的时候才会进行创建,也就是懒加载,不会浪费系统资源,但是也会导致线程不安全,假如同时有两个线程同时调用了getInstance方法,当第一个线程正在new对象的时候,singletonLazy 还是等于null,这就导致了第二个线程可能会也进行new对象,导致出现两个对象
  优点:延迟加载,节省资源
  缺点:线程不安全

/**
 * 懒汉式
 * @author yz
 * @version 1.0
 * @date 2020/5/5 14:52
 */
public class LazySingleton {

    /**
     * 好处:延迟加载(懒加载)
     * 缺点:线程不安全
     */
    private static LazySingleton lazySingleton;

    private LazySingleton() {
    }


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

}

同步方法(懒汉式)
  鉴于普通的懒汉式方法会导致线程不安全,那么就在getInstance上加上关键字synchronized,这样就保证了只有一个线程可以进入,保证了实例的单一,但是也会导致每次调用都会使用同步,而绝大多数情况下是不会产生并发的,所以导致了效率底下

/**
 * 同步方法单例(懒汉式)
 * @author yz
 * @version 1.0
 * @date 2020/5/5 14:52
 */
public class SynchronizedMethodSingleton {

    /**
     * 好处:延迟加载(懒加载),线程安全
     * 缺点:调用效率太低
     */

    private static SynchronizedMethodSingleton synchronizedMethodSingleton;

    private SynchronizedMethodSingleton() {

    }

    public static synchronized SynchronizedMethodSingleton getInstance() {
        if (synchronizedMethodSingleton == null) {
            synchronizedMethodSingleton = new SynchronizedMethodSingleton();
        }
        return synchronizedMethodSingleton;
    }
}

双重锁(懒汉式)
  双重锁模式是将synchronized加到了代码里,synchronized虽然加到了代码里,但是也需要在synchronized里再加一次if判断,如果两个线程同时调用getInstance方法,经过第一次if,实例可能为null,就会导致两个线程同时进入,第一个线程创建时,第二个线程等待,第一个线程创建实例完成后,第二个线程会再次创建,导致单例模式失败,所以在synchronized中加一个if,这样就避免了这种情况的发生。

volatile是用来防止重排序的

/**
 * 双重锁(懒汉式)
 * @author yz
 * @version 1.0
 * @date 2020/5/5 16:16
 */
public class DualLockSingleton {

    private static volatile DualLockSingleton dualLockSingleton = null;

    private DualLockSingleton() {
    }
    public static DualLockSingleton getInstance() {
        if (dualLockSingleton == null) {
            synchronized (DualLockSingleton.class) {
                if (dualLockSingleton == null) {
                    dualLockSingleton = new DualLockSingleton();
                }
            }
        }
        return dualLockSingleton;
    }
}

静态内部类单例
   静态内部类同时保证了延迟加载和线程安全,因为外部类和内部类没有直接关系,外部类加载时,并不会实例化静态类,只有调用时才会实例化内部类,同时因为类是静态的,所以保证了在内存中只有一个,所以不用担心线程安全问题

/**
 * 静态内部类单例
 * @author yz
 * @version 1.0
 * @date 2020/5/5 15:02
 */
public class StaticClassSingleton {

    private static class InternalStaticClassSingleton {
        private static StaticClassSingleton staticClassSingleton = new StaticClassSingleton();
    }

    private StaticClassSingleton() {}
    
    public static StaticClassSingleton getInstance() {
        return InternalStaticClassSingleton.staticClassSingleton;
    }
}

枚举单例
   能防止反序列化重新创建新的对象,而且创建枚举默认是线程安全的,而且枚举中只有一个INSTANCE常量,jvm只会创建一个实例,枚举本质是就是实现了Enum接口的一个类(在枚举里没有定义抽象方法的时候),然后它将所有的值都在静态代码块里进行了初始化。所以枚举的单例是饿汉式,至于枚举如何防止序列华破坏,可以去看看readObject这个方法,你就会发现枚举单独处理了。至于防止反射攻击则更简单,newInstance这个方法里,直接判断如果类型是枚举,就会抛出异常

/**
 * 枚举实现单例
 * @author yz
 * @version 1.0
 * @date 2020/5/5 15:02
 */
public enum EnumSingleton {
    INSTANCE;

    public String name;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer age;

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

单例模式的优点
1.确保所有对象都访问唯一实例
2.因为在类中创建,所以类可以灵活更改实例化过程
3.避免类的重复创建,降低创建实例的资源消耗,提高性能

单例模式的缺点
1.可扩展行比较差
2.如果对象长时间未调用,会被进行垃圾回收