单例模式的优化

  • Post author:
  • Post category:其他



1、首先解释下什么是单例模式,单例模式即当前进程确保一个类全局只有一个实例。


2、什么时候用单例模式?即单例模式的使用场景有哪些?

  • 要求生成唯一序列号的环境
  • 在整个项目中需要一个共享访问点或共享数据
  • 创建一个对象需要消耗的资源过多
  • 需要定义大量的静态常量和静态方法(如工具类)的环境
  • 场景:在计算机系统中,

    线程池

    、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。


3、单例模式的优缺点?


优点:

  1. 单例模式在内存中只有一个实例,减少了内存开支
  2. 单例模式只生成一个实例,所以减少了系统的性能开销
  3. 单例模式可以避免对资源的多重占用
  4. 单例模式可以在系统设置全局的访问点


缺点

  1. 单例模式一般没有接口,扩展很困难
  2. 单例模式不利于测试
  3. 单例模式与单一职责原则有冲突。

4.    单例模式常见的写法:

一、饿汉式(线程比较安全)

/**
 * @author 李告
 * @Description:    饿汉式(线程安全)
 * @data 2022 年 08 月 25 日 11:22
 */
public class Singleton_1 {

//属性私有化,以此来创建一个对象(因为类在被加载的时候,static修饰的内容也跟着被加载,但是只会被加载一次)
    private static Singleton_1 instance = new Singleton_1();
//    构造器私有化
    private Singleton_1() {
    }
//设置外界可以访问的方法,以此返回给外界一个实例化对象,设置为static是让外界可以通过类名.方法名的方式创建实例
    public static Singleton_1 getInstance() {

        return instance;
    }
}

但是因为

instance

是个静态变量,所以它会在类加载的时候完成实例化,不存在线程安全的问题。

这种方式不是懒加载,不管我们的程序会不会用到,它都会在程序启动之初进行初始化。所以接下来用懒汉式实现懒加载效果。懒汉式即当我们需要用到的时候才会加载。

二、懒汉式(线程不安全)

/**
 * @author 李告
 * @Description:    懒汉式基础版(线程不安全)
 * @data 2022 年 08 月 25 日 17:56
 */
public class Singleton_2 {
    //    设置对象的初始化为null
    private static Singleton_2 instance=null;
    //构造器私有化
    private Singleton_2() {

    }
    //设置外界可以访问的方法,以此返回给外界一个实例化对象,设置为static是让外界可以通过类名.方法名的方式创建实
    public static Singleton_2 getInstance() {
//        判断实例是否为空,第一次调用此方法时为空则创建实例,第二次判断时,此时实例已存在,返回之前的实例
        if (instance == null) {
            instance = new Singleton_2();
        }
        return instance;
    }
}

但是如此般的懒汉式又会导致线程安全问题,可能存在这样的问题:

一个线程判断

instance==null

,开始初始化对象;还没来得及初始化对象时候,另一个线程访问,判断

instance==null

,也创建对象。最后的结果,就是实例化了两个Singleton对象。这不符合我们单例的要求啊?那如何解决呢?

三、懒加载(加锁)

/**
 * @author 李告
 * @Description: 懒汉式(加锁)
 * @data 2022 年 08 月 25 日 19:01
 */
public class Singleton_3 {

        private static Singleton_3 instance;

        private Singleton_3() {
        }
//添加同步锁,一个线程进来后,其他线程需要此类线程结束后方可进入此方法
        public synchronized static Singleton_3 getInstance() {
            if (instance == null) {
                instance = new Singleton_3();
            }
            return instance;
        }
}

但是这种把锁直接方法上的办法,所有的访问都需要获取锁,导致了资源的浪费。所以在此基础上可以加以改进。

四、懒汉式(Double Check 双重检查锁)

我们把

synchronized

加在了方法的内部,一般的访问是不加锁的,只有在

instance==null

的时候才加锁。

/**
 * @author 李告
 * @Description:    Double Check 单例模式  双重检查锁
 * @data 2022 年 08 月 25 日 19:15
 */

public class Singleton_4 {
    //volatile修饰,防止指令重排
    private static volatile Singleton_4 instance;

    private Singleton_4() {

    }

    public static Singleton_4 getInstance() {
        //第一重校验,检查实例是否存在
        if (instance == null) {
            //同步块
            synchronized (Singleton_4.class) {
                //第二重校验,检查实例是否存在,如果不存在才真正创建实例    
                if (instance == null) {
                    instance = new Singleton_4();
                }
            }
        }
        return instance;
    }
}

观察这种实现方式,大家有没有想过我们为什么要双重检查?如果不双重校验。如果两个线程一起调用getInstance方法,并且都通过了第一次的判断instance==null,那么第一个线程获取了锁,然后实例化了instance,然后释放了锁,然后第二个线程得到了线程,然后马上也实例化了instance。这就不是单例模式了。

五、静态内部类方式

不仅能实现懒加载、线程安全,而且JVM还保持了指令优化的能力。Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载静态内部类InnerSingleton类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,同时类加载的过程又是线程互斥的,JVM帮助我们保证了线程安全。

/**
 * @author 李告
 * @Description: 单例模式 (静态内部类方式)
 * @data 2022 年 08 月 25 日 19:31
 */
public class Singleton_5 {
//    构造器私有化
    private Singleton_5() {
    }
//静态内部类
    private static class InnerSingleton {
        private static final Singleton_5 instance = new Singleton_5();
    }

    public static Singleton_5 getInstance() {
        return InnerSingleton.instance;
    }
}


六、CAS方式

/**
 * @author 李告
 * @Description:   单例模式(CAS方式)
 * @data 2022 年 08 月 25 日 19:34
 */
public class Singleton_6 {
    /*
        sychronized是一种悲观锁,而CAS是乐观锁,相比较,更轻量级,但是这种写法也比较罕见,CAS存在忙等的问题,可能会造成CPU资源的浪费。
     */

    private static final AtomicReference<Singleton_6> INSTANCE = new AtomicReference<Singleton_6>();

    private Singleton_6() {
    }

    public static final Singleton_6 getInstance() {
//        等待
        while (true) {
            Singleton_6 instance = INSTANCE.get();
            if (null == instance) {
                INSTANCE.compareAndSet(null, new Singleton_6());
            }
            return INSTANCE.get();
        }
    }
}

七、枚举方式

/**
 * @author 李告
 * @Description:    单例模式( 枚举 )
 * @data 2022 年 08 月 25 日 19:37
 */
    public enum Singleton_7 {
        //定义一个枚举,代表了Singleton的一个实例
        INSTANCE;

        public void anyMethod() {
            System.out.println("枚举方式实现单例模式");
        }
        /*
        外部类调用方式
            @Test    void anyMethod() {         Singleton_7.INSTANCE.anyMethod();    }
         */
    }

由于枚举类的特点等,使这种写法解决了最主要的问题:线程安全、由串化、单实例。



版权声明:本文为qq_44707998原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。