• 设计模式——单例模式详解


    设计模式类型

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

    • 创建型模式: 单例模式,抽象工厂模式,原型模式,建造者模式,工厂模式
    • 结构性模式: 适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
    • 行为型模式: 模板方法模式,命令模式,访问者模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,责任链模式

    单例模式

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

    单例模式方式

    饿汉式

    静态常量方式
    package 单例模式.饿汉式;
    
    /**
     * @author Han
     * @data 2023/10/27
     * @apiNode
     */
    public class Test1 {
        public static void main(String[] args) {
            Obj obj1 = Obj.getObj();
            Obj obj2 = Obj.getObj();
            // 因为是单例模式所以这两个对象是同一个,所以返回true
            System.out.println(obj1 == obj2);
    
        }
    }
    
    class Obj {
        // 创建一个私有的静态对象
        private final static Obj obj = new Obj();
    
        // 将构造方法私有化
        private Obj() {
    
        }
    
        // 提供静态公共方法将这个对象返回
        public static Obj getObj() {
            return obj;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    静态代码块形式
    package 单例模式.饿汉式;
    
    /**
     * 静态代码块方式
     *
     * @author Han
     * @data 2023/10/27
     * @apiNode
     */
    public class Test2 {
        public static void main(String[] args) {
            Obj2 obj21 = Obj2.getObj();
            Obj2 obj22 = Obj2.getObj();
            // 因为是单例模式所以这两个对象是同一个,所以返回true
            System.out.println(obj21 == obj22);
    
        }
    }
    
    class Obj2 {
        // 声明一个私有的静态对象
        private static Obj2 obj2;
    
        static {
            // 在静态代码块中创建对象
            obj2 = new Obj2();
        }
        // 将构造方法私有化
        private Obj2() {
    
        }
        // 提供静态公共方法将这个对象返回
        public static Obj2 getObj() {
            return obj2;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    优缺点说明:

    • 优点:写法简单,在类装载是完成实例化,避免了线程同步问题
    • 却爱:在类转载的时候就完成实例化,没有达到lazy loading的效果,如果从始至终都没有用过这个实例,则会造成内存的浪费
    • 这种法方式居于classloder机制避免了多线程的同步问题,不过 obj是在类装载是就实例化了,在单例模式中大多都是调用getObj方法
    • 结论:这种单例模式可用,可能会造成内存浪费

    懒汉式

    线程不安全(不推荐)
    package 单例模式.懒汉式;
    
    /**
     * 这种方式是线程不安全的
     * 原因在于在多线程状态下,if判断条件,
     * 可以能会出现第一个对象还未创建,第二个线程就去判断
     * 而发生创建多个对象的情况
     * @author Han
     * @data 2023/10/27
     * @apiNode
     */
    public class Test1 {
        public static void main(String[] args) {
            Obj obj1 = Obj.getObj();
            Obj obj2 = Obj.getObj();
            System.out.println(obj1 == obj2);
        }
    }
    class Obj {
        // 声明一个静态对象
        private static Obj obj;
        // 私有化构造函数
        private Obj() {}
        // 提供获取单例对象的方法
        public static Obj getObj(){
            // 如果还没有创建对象再去创建,不会发生内存的浪费
            if (obj == null) {
                obj = new Obj();
            }
            return obj;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    优缺点说明

    • 起到了lazy loading的效果,但是只能在单线程下使用
    • 如果在多线程下使用,一个线程进入了if(obi == null)判断语句块,还未来的及王往下执行,另一个线程也通过这个判断语句,这是会发生创建多个实例的错误,所以在多线程环境下不能使用
    • 结论:在实际开发中,不要使用这种方式
    懒汉式优化(不推荐)

    优化,加同步方法,解决线程不安全问题 存在效率问题

    package 单例模式.懒汉式;
    
    /**
     * 这种方式虽然解决了线程安全问题
     * 但是效率很低
     * @author Han
     * @data 2023/10/27
     * @apiNode
     */
    public class Test2 {
        public static void main(String[] args) {
            Obj2 obj1 = Obj2.getObj();
            Obj2 obj2 = Obj2.getObj();
            System.out.println(obj1 == obj2);
        }
    }
    
    class Obj2 {
        // 声明一个静态对象
        private static Obj2 obj;
        // 私有化构造函数
        private Obj2() {}
        // 提供获取单例对象的方法
        // 加入了同步处理的代码,解决线程安全问题
        public static synchronized Obj2 getObj(){
            // 如果还没有创建对象再去创建,不会发生内存的浪费
            if (obj == null) {
                obj = new Obj2();
            }
            return obj;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    优缺点说明

    • 解决了线程安全问题
    • 效率太低,每个线程在获得类的实例的时候,执行getObj方法都要进行同步,但是这个方法只需要执行一次实例化代码就够了,后面想要获取该实例直接return就行了,方法进行同步效率太低
    • 结论:在实际开发中,不推荐使用这中方式

    双重检查(推荐方式)

    package 单例模式.双重检查;
    
    import com.sun.org.apache.xpath.internal.operations.Variable;
    
    /**
     * 双重检查
     * 解决线程安全问题,并且支持懒加载
     *
     * @author Han
     * @data 2023/10/27
     * @apiNode
     */
    public class Test1 {
        public static void main(String[] args) {
            Obj obj1 = Obj.getObj();
            Obj obj2 = Obj.getObj();
            System.out.println(obj1 == obj2);
        }
    }
    
    class Obj {
        // 声明一个静态对象
        //  并且使Obj的对象的改变立即更新到内存,在下面的双重检查中判断是否为nul
        private static volatile Obj obj;
    
        // 私有化构造函数
        private Obj() {
        }
    
        // 提供获取单例对象的方法
        public static Obj getObj() {
            // 如果还没有创建对象再去创建,不会发生内存的浪费
            if (obj == null) {
                // 同步代码块
                synchronized (Obj.class) {
                    // 再一次检查是否为null
                    if (obj == null) {
                        obj = new Obj();
                    }
                }
            }
            return obj;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    优缺点说明

    • 双重检查概念是多线程开发中常用到的,如代码中所示,我们进行了两次if (obj == null )的检查,这样就可以保证线程安全
    • 这样,实例化代码也只执行一次,后面再次访问时,判断if 直接return实例化对象,也避免了反复进行方法同步
    • 线程安全,延迟加载,效率较高
    • 结论:在开发中,推荐使用这种单例设计模式

    静态内部类(推荐方式)

    package 单例模式.静态内部类;
    
    import com.sun.org.apache.xpath.internal.operations.Variable;
    
    /**
     * 静态内部类
     *
     * @author Han
     * @data 2023/10/27
     * @apiNode
     */
    public class Test1 {
        public static void main(String[] args) {
            Obj obj1 = Obj.getObj();
            Obj obj2 = Obj.getObj();
            System.out.println(obj1 == obj2);
        }
    }
    
    class Obj {
    
        // 私有化构造函数
        private Obj() {
        }
    
        // 使用静态内部类
        public static Obj getObj() {
            // 使用静态内部类中属性
            // 类加载时是线程安全的
            return StaticObj.OBJ;
        }
    
        // 静态内部类在类加载时不会马上加载,解决懒加载
        // 只有使用到静态内部类中的属性时,静态内部类才会加载
        static class StaticObj {
            private static final Obj OBJ = new Obj();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    说明

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

    枚举方式(推荐方式)

    package 单例模式.枚举方式;
    
    enum Type {
    
        INSTANCE,
        USER("小韩", 12, "学生");
    
        String name;
        String job;
        int i;
    
        Type(String name, int i, String job) {
            this.i = i;
            this.job = job;
            this.name = name;
        }
    
        Type() {
        }
    
        public void sayOk() {
            System.out.println("ok");
        }
    
        @Override
        public String toString() {
            return "Type{" +
                    "name='" + name + '\'' +
                    ", job='" + job + '\'' +
                    ", i=" + i +
                    '}';
        }
    }
    
    /**
     * @author Han
     * @data 2023/10/28
     * @apiNode
     */
    public class Test {
        public static void main(String[] args) {
            Type instance = Type.INSTANCE;
            Type instance2 = Type.INSTANCE;
            Type user1 = Type.USER;
            Type user2 = Type.USER;
            System.out.println(Type.INSTANCE);
            System.out.println(Type.USER);
            System.out.println(instance == instance2); // true
            System.out.println(user2 == user1); // true
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    优点说明

    • 借助了JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程问题,而且还能防止反序列化重新创建新的对象
    • 这种方式推荐使用

    单例模式在JDK中的使用

    image-20231028131029096

    单例模式注意事项和细节说明

    • 单例模式保障了系统中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
    • 当想实例化一个单例对象的使用,必须要记住使用相应的获取对象的方法,而不是使用new

    单例模式的使用场景

    • 需要频繁的进行创建和销毁对象
    • 创建对象是耗时过多或者耗费资源过多,但是又经常使用到的对象,工厂类对象
    • 频繁访问数据库或文件的对象(比如数据源,session工厂等)
  • 相关阅读:
    【培训课程专用】ShareMemory的建立代码导读
    植物内生细菌——降解石油污染的新方向!
    SQL RDBMS 概念
    Css实现右上角飘带效果
    利用OPNET进行网络任意源组播(ASM)仿真的设计、配置及注意点
    MyBatis源码学习五之插件
    js设计模式:适配器模式
    2022-7-5 随机过程之条件期望及其性质 (三)
    Flink应用
    基于ABP实现DDD--仓储实践
  • 原文地址:https://blog.csdn.net/TCDHanyongpeng/article/details/134090039