My logo
Published on

singleton 单例模式

单例的定义:

单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对 象(或者叫实例), 那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

  1. 饿汉式
  2. 懒汉式
  3. 双重检测
  4. 静态内部类
  5. 枚举

单例类在老进程中存在且只能存在一个对象,在新进程中也会存在且只能存在一个对 象。而且,这两个对象并不是同一个对象,这也就说,单例类中对象的唯一性的作用范围是 进程内的,在进程间是不唯一的。

常见的场景

  1. 比如说,你自定义了一个框架,自定义了一份xml格式的一个配置文件,你要读取这个配置文件,这个配置文件中的数据,读取到类中,这个类的实例,只要保存一份就可以。那么此时可以使用单例模式,将这个类做成他的实例只能有一个,在这个实例中保存了配置文件中的数据。
  2. 类似于我们之前给大家讲解的那个工厂模式,有些工厂是需要实例化对象的,要基于实例化对象,来实现继承、接口、实现等功能,工厂实例可以做成单例的,就可以了。
  3. 你自己去判断,如果是一个类的实例只需要保持一份,那就做成单例。
package wang.jinggo.basics.zhss.singleton;

/**
 * 饿汉模式
 * @author: wangyj
 * @create: 2021-10-20
 * @version: 1.0.0
 **/
public class SafeFullSingletonPatternDemo {

    public static class Singleton {

        private static Singleton instance;

        private Singleton() {

        }

        // 不是完美的
        // 因为不同的JVM的编译器的问题,可能导致说,这个情况下,还是线程不安全的
        // 具体的我不再这儿讲,因为涉及到复杂的JVM内部的原理

        public static Singleton getInstance() {
            // 如果线程1和线程2都执行到了这一步,然后此时线程1判断发现还是null
            // 线程2此时判断发现instance == null,也会进去
            if(instance == null) {
                // 线程1就会进来,此时线程1停止,切换到线程2
                // 线程2也会进来,此时切换到线程1

                // 线程1,发现这里需要加锁, 在这里加锁,获取到了这个锁
                // 线程2过来,线程2发现说,我也想要在这里加锁,发现说这个锁被人加了,线程2挂起等待别人释放锁
                // 此时切换回线程2,线程2发现锁被释放,然后在这里加锁
                synchronized(SafeFullSingletonPatternDemo.class) {
                    // 线程1就进来了,此时切换到线程2
                    // 切换回线程1,线程1此时在这里,再次判断,instance == null
                    // 线程2就进来了,double check,如果这里没有instance == null的判断,那么线程2就会再次创建
                    // 一个实例
                    // 但是这里是双重检查,线程2又判断了一下,instance == null?否,不是null
                    if(instance == null) {
                        // 线程1就会进来,创建一个实例
                        instance = new Singleton();
                    }
                }
            }
            // 这边出来以后,线程1就释放锁了
            // 线程2跳出来,直接获取一个instance返回了,这个instance就是之前线程1创建的实例
            return instance;
        }

    }

}

package wang.jinggo.basics.zhss.singleton;

/**
 * 线程不安全的饱汉模式
 *
 * @author: wangyj
 * @create: 2021-10-20
 * @version: 1.0.0
 **/
public class UnsafeFullSingletonPatternDemo {


    /**
     * 线程不安全
     *
     * @author zhonghuashishan
     */
    public static class Singleton {

        private static Singleton instance;

        private Singleton() {

        }

        public static Singleton getInstance() {
            /*
             * 假设有两个线程过来
             *
             * 线程的基础:线程是并发着执行的,cpu,先执行一会儿线程1,然后停止执行线程1;切换过去执行线程2
             * 执行线程2一会儿之后,再停止执行线程2;回来继续执行线程1
             *
             * 第一个线程,判断发现说instance == null,代码就进入到了下面去
             * 第二个线程,执行到这儿,发现,此时instance == null,那么就没什么问题了,继续往下走
             *
             */
            if (instance == null) {
                // 第一个线程跑到了这儿来,但是此时第一个线程,还没有执行下面的那行代码
                // 此时,第二个线程代码也执行到了这儿,cpu切换回线程1

                // 执行线程1的代码,线程1会创建一个实例出来
                // 但是切换到线程2去执行的时候,线程2,的代码已经执行到这儿来了,此时又会再一次执行下面的代码
                // 就是会再一次创建一个实例,之前线程1创建的那个实例,就会被垃圾回收,废弃掉了

                instance = new Singleton();
            }
            return instance;
        }

    }
}

package wang.jinggo.basics.zhss.singleton;

/**
 * 这个才是我们实际开发过程中,最最常用的单例模式,内部类的方式来实现
 *
 * @author: wangyj
 * @create: 2021-10-20
 * @version: 1.0.0
 **/
public class InnerClassFullSingletonPatternDemo {

    /**
     * 可以做饱汉模式
     *
     * 内部类,只要没有被使用,就不会初始化,Singleton的实例就不会创建
     *
     * 在第一次有人调用getInstance方法的时候,内部类会初始化,创建一个Singleton的实例
     *
     * 然后java能确保的一点是,类静态初始化的过程一定只会执行一次
     *
     */
    public static class Singleton {

        private Singleton() {

        }

        public static class InnerHolder {

            public static final Singleton instance = new Singleton();

        }

        public static Singleton getInstance() {
            return InnerHolder.instance;
        }

    }

}


package wang.jinggo.basics.zhss.singleton;

/**
 * @author: wangyj
 * @create: 2021-10-20
 * @version: 1.0.0
 **/
public class HungrySingletonPatternDemo {

    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        singleton.execute();

        new Singleton();
    }

    public static class Singleton {

        /**
         * 第一步:直接就是将这个类的实例在创建出来,赋予static final修饰的变量
         * <p>
         * static:就是一个类的静态变量
         * final:这个变量的引用第一次初始化赋予之后,就再也不能修改引用了
         */
        private static final Singleton instance = new Singleton();

        /**
         * 第二步:将构造函数搞成private私有的
         * <p>
         * 此时除了这个类自己本身,其他任何人都不能创建它的这个实例对象
         */
        private Singleton() {

        }

        /**
         * 第三步:给一个static静态方法,返回自己唯一的内部创建的一个实例
         *
         * @return
         */
        public static Singleton getInstance() {
            return instance;
        }

        public void execute() {
            System.out.println("单例类的方法");
        }

    }

}

最简单的一种类了,就是如果一个类就只需要一次,那么就使用这个类了。

但是使用单例模式有一个要求,不允许这个类的逻辑过于复杂,一般就是持有某份配置文件的配置,或者是别的一些数据。

因为如果别的很多类用了这个类,是没法打桩注入的,很麻烦。

所以只能是简单的情况下,用单例模式,就是持有一份数据,但是这份数据全局就只要一份,比如说一些配置数据,就用单例模式,或者是类似redis的客户端实例,或者是类似elasticsearch的客户端实例。