设计模式——03单例设计模式

一. 单例设计模式介绍

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

  1. 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
  2. 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
  3. 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

二. 单例设计模式8种方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

三. 单例设计模式具体实现

1. 饿汉式(静态常量)

1. 实现步骤
  1. 构造器私有化(防止 new)
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法(getInstance)
2. 代码展示
  • package com.hjf.singleton;
    
    /**
     * @author Jiang锋时刻
     * @create 2020-04-17 16:37
     *
     * 单例模式:
     *  所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统, 对某个类
     *  只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法).
     */
    
    public class SingletonTest1 {
        public static void main(String[] args) {
            // 测试
            Singleton1 instance1 = Singleton1.getInstance();
            Singleton1 instance2 = Singleton1.getInstance();
            // 返回true, 表名是同一个对象
            System.out.println(instance1 == instance2);
            // 两个对象对应的hashCode相同
            int h1 = instance1.hashCode();
            int h2 = instance2.hashCode();
            System.out.println("instance1的hashCode为: " + h1);
            System.out.println("instance2的hashCode为: " + h2);
    
        }
    }
    
    
    /**
     * 方式一:饿汉式(静态常量)
     * 饿汉式静态常量的优缺点:
     *  1. 优点: 这种写法比较简单, 就是在类装载的时候就完成实例化. 避免了线程同步问题.
     *  2. 缺点: 在类装载的时候就完成实例化, 没有达到Lazy Loading的效果.
     *           如果从始至终从未使用过这个实例, 则会造成内存的浪费
     *  3. 这种方式基于classloder机制避免了多线程的同步问题, 不过, instance在类装载
     *     时就实例化, 在单例模式中大多数都是调用getInstance方法, 但是导致类装载的
     *     原因有很多种, 因此不能确定有其他的方式(或者其他的静态方法)导致类装载,
     *     这时候初始化instance就没有达到lazy loading的效果
     *  4. 结论: 这种单例模式可用, 可能造成内存浪费
     */
    class Singleton1{
        /**
         * 构造器私有化, 外部不能new
         */
        private Singleton1(){
        }
    
        /* 2. 本类内部创建对象实例*/
        private final static Singleton1 instance = new Singleton1();
    
        /**
         * 3. 对外提供公有的静态方法, 返回实例对象
         * @return
         */
        public static Singleton1 getInstance(){
            return instance;
        }
    }
    
    
3. 特点:
  1. 优点:
    这种写法比较简单, 就是在类装载的时候就完成实例化. 避免了线程同步问题.
  2. 缺点: 在类装载的时候就完成实例化, 没有达到Lazy Loading的效果.如果从始至终从未使用过这个实例, 则会造成内存的浪费
  3. 这种方式基于classloder机制避免了多线程的同步问题, 不过, instance在类装载时就实例化, 在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种, 因此不能确定有其他的方式(或者其他的静态方法)导致类装载, 这时候初始化instance就没有达到lazy loading的效果
  4. 结论: 这种单例模式可用, 可能造成内存浪费

2. 饿汉式(静态代码块)

1. 代码演示
  • package com.hjf.singleton;
    
    /**
     * @author Jiang锋时刻
     * @create 2020-04-17 16:37
     *
     * 单例模式:
     *  所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统, 对某个类
     *  只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法).
     */
    
    public class SingletonTest2 {
        public static void main(String[] args) {
            // 测试
            Singleton2 instance1 = Singleton2.getInstance();
            Singleton2 instance2 = Singleton2.getInstance();
            // 返回true, 表名是同一个对象
            System.out.println(instance1 == instance2);
            // 两个对象对应的hashCode相同
            int h1 = instance1.hashCode();
            int h2 = instance2.hashCode();
            System.out.println("instance1的hashCode为: " + h1);
            System.out.println("instance2的hashCode为: " + h2);
    
        }
    }
    
    
    /**
     * 方式二: 饿汉式(静态代码块)
     * 饿汉式静态代码块的特点:
     *  1. 这种方式和上面的静态常量方式其实类似, 只不过将类实例化的过程放在
     *     了静态代码块中, 也是在类装载的时候, 就执行静态代码块中的代码, 初
     *     始化类的实例. 优缺点和静态常量方式是一样的.
     *  2. 结论: 这种单例模式可用, 可能造成内存浪费.
     */
    class Singleton2{
        /** 构造器私有化, 外部不能new */
        private Singleton2(){
    
        }
    
        /**
         * 2. 本类内部创建对象实例
         */
        private static Singleton2 instance ;
    
        // 在静态代码块中, 创建单例对象
        static {
            instance = new Singleton2();
        }
    
        /**
         * 3. 对外提供公有的静态方法, 返回实例对象
         * @return
         */
        public static Singleton2 getInstance(){
            return instance;
        }
    }
    
    
2. 优缺点说明
  1. 这种方式和上面的静态常量方式其实类似, 只不过将类实例化的过程放在了静态代码块中, 也是在类装载的时候, 就执行静态代码块中的代码, 初始化类的实例. 优缺点和静态常量方式是一样的.
  2. 结论: 这种单例模式可用, 可能造成内存浪费.

3. 懒汉式(线程不安全)

1. 代码演示
  • package com.hjf.singleton;
    
    /**
     * @author Jiang锋时刻
     * @create 2020-04-17 16:37
     *
     * 单例模式:
     *  所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统, 对某个类
     *  只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法).
     */
    
    public class SingletonTest3 {
        public static void main(String[] args) {
            // 测试
            Singleton3 instance1 = Singleton3.getInstance();
            Singleton3 instance2 = Singleton3.getInstance();
            // 返回true, 表名是同一个对象
            System.out.println(instance1 == instance2);
            // 两个对象对应的hashCode相同
            int h1 = instance1.hashCode();
            int h2 = instance2.hashCode();
            System.out.println("instance1的hashCode为: " + h1);
            System.out.println("instance2的hashCode为: " + h2);
    
        }
    }
    
    
    /**
     * 方式三: 懒汉式(线程不安全)
     *      提供一个静态的公有方法, 当使用到该方法时, 才会创建instance. 即: 懒汉式
     * 特点:
     *  1. 起到了Lazy Loading的效果, 但是只能在单线程下使用.
     *  2. 如果在多线程下, 一个线程进入了if (singleton == null)判断语句块,
     *     还未来得及往下执行, 另一个线程也通过了这个判断语句, 这时便会产生
     *     多个实例. 所以在多线程环境下不可使用这种方式
     *  3. 结论: 在实际开发中, 不要使用这种方式
     */
    class Singleton3{
        private static Singleton3 instance;
    
        private Singleton3(){
        }
    
        public static Singleton3 getInstance(){
            if (instance == null){
                instance = new Singleton3();
            }
            return instance;
        }
    }
    
    
2. 特点
  1. 起到了Lazy Loading的效果, 但是只能在单线程下使用.
  2. 如果在多线程下, 一个线程进入了if (singleton == null)判断语句块, 还未来得及往下执行, 另一个线程也通过了这个判断语句, 这时便会产生多个实例. 所以在多线程环境下不可使用这种方式
  3. 结论: 在实际开发中, 不要使用这种方式(不可用)

4. 懒汉式(线程安全, 同步方法)

1. 代码演示
  • package com.hjf.singleton;
    
    /**
     * @author Jiang锋时刻
     * @create 2020-04-17 16:37
     *
     * 单例模式:
     *  所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统, 对某个类
     *  只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法).
     */
    
    public class SingletonTest4 {
        public static void main(String[] args) {
            // 测试
            Singleton4 instance1 = Singleton4.getInstance();
            Singleton4 instance2 = Singleton4.getInstance();
            // 返回true, 表名是同一个对象
            System.out.println(instance1 == instance2);
            // 两个对象对应的hashCode相同
            int h1 = instance1.hashCode();
            int h2 = instance2.hashCode();
            System.out.println("instance1的hashCode为: " + h1);
            System.out.println("instance2的hashCode为: " + h2);
    
        }
    }
    
    
    /**
     * 方式四: 懒汉式(线程安全, 同步方法)
     *      提供一个静态的公有方法, 当使用到该方法时, 才会创建instance. 即: 懒汉式
     * 特点:
     *  1. 解决了线程不安全问题.
     *  2. 效率太低了, 每个线程在想获得类的实例时候, 执行getInstance()方法都要进行
     *     同步. 而其实这个方法只执行一次实例化代码就够了, 后面的想获得该类实例,
     *     直接return就行了. 方法进行同步效率太低
     *  3. 结论: 在实际开发中, 不要使用这种方式 [如果是单线程的项目, 可以使用]
     */
    class Singleton4{
        private static Singleton4 instance;
    
        private Singleton4(){
        }
    
        /** 加入同步处理的代码, 解决线程安全问题 */
        public static synchronized Singleton4 getInstance(){
            if (instance == null){
                instance = new Singleton4();
            }
            return instance;
        }
    }
    
    
2. 特点
  1. 解决了线程不安全问题.
  2. 效率太低了, 每个线程在想获得类的实例时候, 执行getInstance()方法都要进行同步. 而其实这个方法只执行一次实例化代码就够了, 后面的想获得该类实例, 直接return就行了. 方法进行同步效率太低
  3. 结论: 在实际开发中, 不要使用这种方式 [如果是单线程的项目, 可以使用]

5. 懒汉式(线程安全, 同步代码块)

1. 代码演示
  • package com.hjf.singleton;
    
    /**
     * @author Jiang锋时刻
     * @create 2020-04-17 16:37
     *
     * 单例模式:
     *  所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统, 对某个类
     *  只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法).
     */
    
    public class SingletonTest5 {
        public static void main(String[] args) {
            // 测试
            Singleton5 instance1 = Singleton5.getInstance();
            Singleton5 instance2 = Singleton5.getInstance();
            // 返回true, 表名是同一个对象
            System.out.println(instance1 == instance2);
            // 两个对象对应的hashCode相同
            int h1 = instance1.hashCode();
            int h2 = instance2.hashCode();
            System.out.println("instance1的hashCode为: " + h1);
            System.out.println("instance2的hashCode为: " + h2);
    
        }
    }
    
    
    /**
     * 方式五: 懒汉式(线程不安全, 同步代码块)
     *  特点:
     *      1. 这种方式, 本意是想对第四种实现方式的改进, 因为前面同步方法
     *         效率太低, 改为同步产生实例化的的代码块
     *      2. 但是这种同步并不能起到线程同步的作用. 跟第3种实现方式遇到的情形一
     *         致, 假如一个线程进入了 if(singleton == null)判断语句块, 还未来
     *         得及往下执行, 另一个线程也通过了这个判断语句, 这时便会产生多个实例
     *      3. 结论: 在实际开发中, 不能使用这种方式
     */
    class Singleton5{
        private static Singleton5 instance;
    
        private Singleton5(){
        }
    
    
        public static Singleton5 getInstance(){
            if (instance == null){
                /** 这里并不能解决线程安全问题 */
                synchronized(Singleton5.class){
                    instance = new Singleton5();
                }
    
            }
            return instance;
        }
    }
    
    
2. 特点:
  1. 这种方式, 本意是想对第四种实现方式的改进, 因为前面同步方法效率太低, 改为同步产生实例化的的代码块
  2. 但是这种同步并不能起到线程同步的作用. 跟第3种实现方式遇到的情形一致, 假如一个线程进入了 if(singleton == null)判断语句块, 还未来得及往下执行, 另一个线程也通过了这个判断语句, 这时便会产生多个实例
  3. 结论: 在实际开发中, 不能使用这种方式

6. 双重检查

1. 代码演示
  • package com.hjf.singleton;
    
    /**
     * @author Jiang锋时刻
     * @create 2020-04-17 16:37
     *
     * 单例模式:
     *  所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统, 对某个类
     *  只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法).
     */
    
    public class SingletonTest6 {
        public static void main(String[] args) {
            // 测试
            Singleton6 instance1 = Singleton6.getInstance();
            Singleton6 instance2 = Singleton6.getInstance();
            // 返回true, 表名是同一个对象
            System.out.println(instance1 == instance2);
            // 两个对象对应的hashCode相同
            int h1 = instance1.hashCode();
            int h2 = instance2.hashCode();
            System.out.println("instance1的hashCode为: " + h1);
            System.out.println("instance2的hashCode为: " + h2);
    
        }
    }
    
    
    /**
     * 方式六: 懒汉式(双重检查)
     *  特点:
     *      1. Double Check概念是多线程开发中常使用到的, 如代码中所示, 我们进行了两
     *         次if(singleton == null)检查, 这样就可以保证线程安全了
     *      2. 这样, 实例化代码只用执行一次, 后面再次访问时, 判断if(singleton == null)
     *         直接return实例化对象, 也避免的反复进行方法同步
     *      3. 线程安全; 延迟加载; 效率较高
     *      4. 结论: 在实际开发中, 推荐使用这种单例设计模式
     */
    class Singleton6{
        /**
         * volatile关键字的作用: 保证了变量的可见性(visibility)
         * 被volatile关键字修饰的变量, 如果值发生了变更, 其他线程立马可见, 避免出现脏读的现象.
         */
        private static volatile Singleton6 instance;
    
        private Singleton6(){
        }
    
        /**
         * 提供一个静态的公有方法, 加入双重检查代码,
         * 既可以解决线程安全问题, 同时又可以解决懒加载问题
         * @return
         */
        public static Singleton6 getInstance(){
            /*
                即便在外层if 条件语句中存在线程不安全的情况, 但是只要之前的
                线程创建了对象, instance 会立马将变更的值写入内存, 然后再
                内层if条件判断时, 得知其已经被创建了, 从而解决线程安全问题
             */
            if (instance == null){
                synchronized(Singleton6.class){
                    if (instance == null){
                        instance = new Singleton6();
                    }
                }
            }
            return instance;
        }
    }
    
    
2. 特点
  1. Double Check概念是多线程开发中常使用到的, 如代码中所示, 我们进行了两次if(singleton == null)检查, 这样就可以保证线程安全了
  2. 这样, 实例化代码只用执行一次, 后面再次访问时, 判断if(singleton == null) 直接return实例化对象, 也避免的反复进行方法同步
  3. 线程安全; 延迟加载; 效率较高
  4. 结论: 在实际开发中, 推荐使用这种单例设计模式

7. 静态内部类

1. 代码演示
  • package com.hjf.singleton;
    
    /**
     * @author Jiang锋时刻
     * @create 2020-04-17 16:37
     *
     * 单例模式:
     *  所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统, 对某个类
     *  只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法).
     */
    
    public class SingletonTest7 {
        public static void main(String[] args) {
            // 测试
            Singleton7 instance1 = Singleton7.getInstance();
            Singleton7 instance2 = Singleton7.getInstance();
            // 返回true, 表名是同一个对象
            System.out.println(instance1 == instance2);
            // 两个对象对应的hashCode相同
            int h1 = instance1.hashCode();
            int h2 = instance2.hashCode();
            System.out.println("instance1的hashCode为: " + h1);
            System.out.println("instance2的hashCode为: " + h2);
    
        }
    }
    
    
    /**
     * 方式七: 静态内部类
     *  特点:
     *      1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程.
     *      2. 静态内部类方式在Singleton7类被装载时并不会立即实例化, 而是在需
     *         要实例化时, 调用getInstance方法, 才会装载SingletonInstance类,
     *         从而完成Singleton7的实例化
     *      3. 类的静态属性只会在第一次加载类的时候初始化, 所以在这里, JVM帮助
     *         我们保证了线程的安全性, 在类进行初始化时, 别的线程是无法进入的.
     *      4. 优点: 避免了线程不安全, 利用静态内部类特点实现延迟加载, 效率高
     *      5. 结论: 推荐使用.
     */
    class Singleton7{
        private Singleton7(){
        }
    
        /**
         * 写一个静态内部类, 该类中有一个静态属性Singleton7
         *  外部类的加载,不会导致内部类的加载, 达到了懒加载的特性
         */
        private static class SingletonInstance{
            private static final Singleton7 INSTANCE = new Singleton7();
        }
    
        /**
         * 提供一个静态的公有方法, 直接返回SingletonInstance.INSTANCE
         * 装载类时, 线程是安全的
         * 通过这两个内部类的特性, 达到了单例的要求
         * @return
         */
        public static Singleton7 getInstance(){
            return SingletonInstance.INSTANCE;
        }
    }
    
    
2. 特点
  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程.
  2. 静态内部类方式在Singleton7类被装载时并不会立即实例化, 而是在需要实例化时, 调用getInstance方法, 才会装载SingletonInstance类, 从而完成Singleton7的实例化
  3. 类的静态属性只会在第一次加载类的时候初始化, 所以在这里, JVM帮助我们保证了线程的安全性, 在类进行初始化时, 别的线程是无法进入的.
  4. 优点: 避免了线程不安全, 利用静态内部类特点实现延迟加载, 效率高
  5. 结论: 推荐使用.

8. 枚举

1. 代码演示
  • package com.hjf.singleton;
    
    /**
     * @author Jiang锋时刻
     * @create 2020-04-17 16:37
     *
     * 单例模式:
     *  所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统, 对某个类
     *  只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法).
     */
    
    public class SingletonTest8 {
        public static void main(String[] args) {
            // 测试
            Singleton8 instance1 = Singleton8.INSTANCE;
            Singleton8 instance2 = Singleton8.INSTANCE;
            // 返回true, 表名是同一个对象
            System.out.println(instance1 == instance2);
            // 两个对象对应的hashCode相同
            int h1 = instance1.hashCode();
            int h2 = instance2.hashCode();
            System.out.println("instance1的hashCode为: " + h1);
            System.out.println("instance2的hashCode为: " + h2);
    
        }
    }
    
    /**
     * 方式八: 枚举类
     *  枚举类真正实现了单例, 把反序列化和反射创建第二对象的路都堵死了.
     *  特点:
     *      1. 这借助JDK1.5中添加的枚举来实现单例模式. 不仅能避免多线程同步问题,
     *         而且还能防止反序列化重新创建新的对象.
     *      2. 这种方式是Effective Java作者Josh Bloch提倡的方式
     *      3. 结论: 推荐使用
     */
    enum Singleton8{
        INSTANCE;
    //    public void sayOK(){
    //        System.out.println("ok");
    //    }
    }
    
    
2. 特点
  1. 这借助JDK1.5中添加的枚举来实现单例模式. 不仅能避免多线程同步问题, 而且还能防止反序列化重新创建新的对象.
  2. 这种方式是Effective Java作者Josh Bloch提倡的方式
  3. 结论: 推荐使用

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

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值