• Java - 单例模式详解


    概述

    Singleton:在 Java 中指单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。

    例如:代表 JVM 运行环境的 Runtime 类:

    public class Runtime {
        private static Runtime currentRuntime = new Runtime();
    
        public static Runtime getRuntime() {
            return currentRuntime;
        }
    
        /** Don't let anyone else instantiate this class */
        private Runtime() {}
    	
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    常见形式

    特点:

    • 构造器私有化
    • 由类的一个静态变量来保存唯一的实例
    • 使用 public 对外暴露或者用静态变量的 get 方法获取

    饿汉式直接创建对象,不存在线程安全问题

    1、直接实例化饿汉式(简洁)

    public class Single1 {
        public static final Single1 INSTANCE = new Single1();
    
        private Single1() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Java - static 关键字 这篇文章中我们知道,static 关键字修饰的变量会在类加载的时候就创建。这种方式创建的单例模式有一个问题,就是说比如我们在这个单例类内部还有一个静态方法:

    public class Single1 {
        public static final Single1 INSTANCE = new Single1();
    
        private Single1() {
        }
        
        public static String getSource() {
            return "";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当我们调用 getSource() 方法的时候,也会创建一个 INSTANCE 单例对象,虽然我们并不需要这个单例对象。

    2、枚举类(最简洁)

    枚举类型:表示该类型的对象是有限的几个,我们可以限定为 1 个,这样就成了一个单例类。直接通过 类名 + INSTANCE 获取

    public enum Single2 {
        INSTANCE
    }
    
    • 1
    • 2
    • 3

    3、静态代码块饿汉式(适合复杂实例化)

    public class Single31 {
    
        public static final Single31 INSTANCE;
    
        static {
            INSTANCE = new Single31();
        }
    
        private Single31() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这种方式其实跟第一种方式 直接实例化 是一样的,我们来比较一下两种方式的字节码:

    • 直接实例化单例模式的字节码:

    在这里插入图片描述

    静态代码块单例模式的字节码:

    在这里插入图片描述

    可以看出,两种方式的字节码是一样的。不过,静态代码块的使用场景要复杂一点,当初始化涉及到一些其他操作的时候,就需要用这种方式,比如:

    public class Single3 {
    
        public static final Single3 INSTANCE;
        private String info;
    
        static {
            try {
                Properties properties = new Properties();
                properties.load(Single3.class.getClassLoader().getResourceAsStream("single3.properties"));
                INSTANCE = new Single3(properties.getProperty("info"));
            } catch (IOException e) {
                throw new RuntimeException();
            }
    
        }
    
        private Single3(String info) {
            this.info = info;
        }
    
        public String getInfo() {
            return info;
        }
    
        public void setInfo(String info) {
            this.info = info;
        }
    
        @Override
        public String toString() {
            return "Single3{" +
                    "info='" + info + '\'' +
                    '}';
        }
    }
    
    • 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

    懒汉式延迟创建对象

    1、线程不安全(适用于单线程)

    public class Single41 {
        
        private static Single41 INSTANCE;
    
        private Single41() {
        }
        
        public static Single41 getInstance() {
            if (INSTANCE == null) {
                INSTANCE = new Single41();
            }
            return INSTANCE;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 单线程环境下
    void test() {
    	Single41 instance1 = Single41.getInstance();
        Single41 instance2 = Single41.getInstance();
        System.out.println(instance1 == instance2);	// true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 多线程环境下
    void test() throws ExecutionException, InterruptedException {
        Callable<Single41> callable = Single41::getInstance;
    
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Single41> f1 = es.submit(callable);
        Future<Single41> f2 = es.submit(callable);
    
        System.out.println(f1.get() == f2.get());	// 有时为 true,有时为 false
    
        es.shutdown();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2、线程安全(适用于多线程)

    在上面代码的基础上,使用 synchronized 关键字,加锁:

    public class Single42 {
    
        private static Single42 INSTANCE;
    
        private Single42() {
        }
    
        public static Single42 getInstance() {
        	// 当有多个线程进来后,会在这里排队来请求获取锁
            synchronized (Single42.class) {
                if (INSTANCE == null) {
                	// 假设这里比较耗时
                    INSTANCE = new Single42();
                }
            }
            return INSTANCE;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    上面这种方式会有性能问题,当大量线程进来,会都堵在获取锁的地方,所以我们可以修改为:

    public class Single42 {
    
        private static Single42 INSTANCE;
    
        private Single42() {
        }
    
        public static Single42 getInstance() {
            if (INSTANCE == null) {
                synchronized (Single42.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new Single42();
                    }
                }
            }
            return INSTANCE;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3、静态内部类形式(适用于多线程)

    上面这种方式,还是比较复杂的,我们可以使用 静态内部类 的方式,来简化代码:

    public class Single6 {
    
        private Single6() {
    
        }
    
        private static class Inner {
            private static final Single6 INSTANCE = new Single6();
        }
    
        public static Single6 getInstance() {
            return Inner.INSTANCE;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    静态内部类 不会自动随着外部类的加载和初始化而加载和初始化,它是要单独去加载和初始化的,所以是懒加载,而又因为是在内部类加载的时候创建和初始化的,所以又是线程安全的。

  • 相关阅读:
    使用 Charles 去修改响应信息(真实工作使用场景1)
    关于 Lucene 搜索语法与分词的浅显研究
    ES全文检索支持繁简和IK分词检索
    Springboot实现jwt的token验证(超简单)
    跨域和验证码的实现
    Java中的字符串
    (送源码)SSM&MYSQL民宿预订及个性化服务系统04846-计算机毕业设计
    新版edge浏览器读取谷歌浏览器上的历史记录
    python发送邮件
    工信部等四部门印发重要标准化指南,引领人工智能产业高质量发展
  • 原文地址:https://blog.csdn.net/qiaohao0206/article/details/126283717