第01章 创建型-单例模式

本文内容来源于网络收集

作者:冰河
来源:冰河技术公众号

  • 本章难度:★★☆☆☆
  • 本章重点:介绍创建Java单例对象的七种方式,重点掌握哪些创建方式是线程安全的,哪些方式是线程不安全的,并能够在实际项目中灵活运用设计模式,编写可维护的代码。

单例设计模式

看几个单例对象的示例代码,其中有些代码是线程安全的,有些则不是线程安全的,需要大家细细品味,这些代码也是冰河本人在高并发环境下测试验证过的。

  • 代码一:SingletonExample1

    这个类是懒汉模式,并且是线程不安全的

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    package io.binghe.design.singleton;
    /**
     * @author binghe
     * @version 1.0.0
     * @description 懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程不安全的
     */
    public class SingletonExample1 {
    
        private SingletonExample1(){}
    
        private static SingletonExample1 instance = null;
    
        public static SingletonExample1 getInstance(){
            //多个线程同时调用,可能会创建多个对象
            if (instance == null){
                instance = new SingletonExample1();
            }
            return instance;
        }
    }
  • 代码二:SingletonExample2

    饿汉模式,单例实例在类装载的时候进行创建,是线程安全的

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    package io.binghe.design.singleton;
    /**
     * @author binghe
     * @version 1.0.0
     * @description 饿汉模式,单例实例在类装载的时候进行创建,是线程安全的
     */
    public class SingletonExample2 {
    
        private SingletonExample2(){}
    
        private static SingletonExample2 instance = new SingletonExample2();
    
        public static SingletonExample2 getInstance(){
            return instance;
        }
    }
  • 代码三:SingletonExample3

    懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程安全的,但是这个写法不推荐

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    package io.binghe.design.singleton;
    /**
     * @author binghe
     * @version 1.0.0
     * @description 懒汉模式,单例实例在第一次使用的时候进行创建,这个类是线程安全的,但是这个写法不推荐
     */
    public class SingletonExample3 {
    
        private SingletonExample3(){}
    
        private static SingletonExample3 instance = null;
    
        public static synchronized SingletonExample3 getInstance(){
            if (instance == null){
                instance = new SingletonExample3();
            }
            return instance;
        }
    }
  • 代码四:SingletonExample4

    懒汉模式(双重锁同步锁单例模式),单例实例在第一次使用的时候进行创建,但是,这个类不是线程安全的!!!!!

     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
    
    package io.binghe.design.singleton;
    /**
     * @author binghe
     * @version 1.0.0
     * @description 懒汉模式(双重锁同步锁单例模式)
     *              单例实例在第一次使用的时候进行创建,这个类不是线程安全的
     */
    public class SingletonExample4 {
    
        private SingletonExample4(){}
    
        private static SingletonExample4 instance = null;
    
        //线程不安全
        //当执行instance = new SingletonExample4();这行代码时,CPU会执行如下指令:
        //1.memory = allocate() 分配对象的内存空间
        //2.ctorInstance() 初始化对象
        //3.instance = memory 设置instance指向刚分配的内存
        //单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。
        // 指令重排序对单线程没有影响,单线程下CPU可以按照顺序执行以上三个步骤,但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。
    
        //如果发生了JVM和CPU优化,发生重排序时,可能会按照下面的顺序执行:
        //1.memory = allocate() 分配对象的内存空间
        //3.instance = memory 设置instance指向刚分配的内存
        //2.ctorInstance() 初始化对象
    
    
        //假设目前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程刚执行到第一个 if (instance == null){处,
        //如果按照1.3.2的顺序,假设线程A执行到3.instance = memory 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,就会直接return instance;
        //而实际上,线程A还未执行2.ctorInstance() 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,这个未初始化的instance对象一旦被线程B使用,就会出现问题。
    
    
        public static SingletonExample4 getInstance(){
            if (instance == null){
                synchronized (SingletonExample4.class){
                    if(instance == null){
                        instance = new SingletonExample4();
                    }
                }
            }
            return instance;
        }
    }

    线程不安全分析如下:

    当执行instance = new SingletonExample4();这行代码时,CPU会执行如下指令:

    1.memory = allocate() 分配对象的内存空间 2.ctorInstance() 初始化对象 3.instance = memory 设置instance指向刚分配的内存

    单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。

    指令重排序对单线程没有影响,单线程下CPU可以按照顺序执行以上三个步骤,但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。

    如果发生了JVM和CPU优化,发生重排序时,可能会按照下面的顺序执行:

    1.memory = allocate() 分配对象的内存空间 3.instance = memory 设置instance指向刚分配的内存 2.ctorInstance() 初始化对象

    假设目前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程刚执行到第一个 if (instance == null){处,如果按照1.3.2的顺序,假设线程A执行到3.instance = memory 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,就会直接return instance;而实际上,线程A还未执行2.ctorInstance() 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,这个未初始化的instance对象一旦被线程B使用,就会出现问题。

  • 代码五:SingletonExample5

    懒汉模式(双重锁同步锁单例模式)单例实例在第一次使用的时候进行创建,这个类是线程安全的,使用的是 volatile + 双重检测机制来禁止指令重排达到线程安全

     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
    
    package io.binghe.design.singleton;
    /**
     * @author binghe
     * @version 1.0.0
     * @description 懒汉模式(双重锁同步锁单例模式)
     *              单例实例在第一次使用的时候进行创建,这个类是线程安全的
     */
    public class SingletonExample5 {
    
        private SingletonExample5(){}
    
        //单例对象  volatile + 双重检测机制来禁止指令重排
        private volatile static SingletonExample5 instance = null;
    
        public static SingletonExample5 getInstance(){
            if (instance == null){
                synchronized (SingletonExample5.class){
                    if(instance == null){
                        instance = new SingletonExample5();
                    }
                }
            }
            return instance;
        }
    }
  • 代码六:SingletonExample6

    饿汉模式,单例实例在类装载的时候(使用静态代码块)进行创建,是线程安全的

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    package io.binghe.design.singleton;
    /**
     * @author binghe
     * @version 1.0.0
     * @description 饿汉模式,单例实例在类装载的时候进行创建,是线程安全的
     */
    public class SingletonExample6 {
    
        private SingletonExample6(){}
    
        private static SingletonExample6 instance = null;
    
        static {
            instance = new SingletonExample6();
        }
    
        public static SingletonExample6 getInstance(){
            return instance;
        }
    }
  • 代码七:SingletonExample7

    枚举方式进行实例化,是线程安全的,此种方式也是线程最安全的

     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
    
    package io.binghe.design.singleton;
    /**
     * @author binghe
     * @version 1.0.0
     * @description 枚举方式进行实例化,是线程安全的,此种方式也是线程最安全的
     */
    public class SingletonExample7 {
    
        private SingletonExample7(){}
    
        public static SingletonExample7 getInstance(){
            return Singleton.INSTANCE.getInstance();
        }
    
        private enum Singleton{
            INSTANCE;
            private SingletonExample7 singleton;
    
            //JVM保证这个方法绝对只调用一次
            Singleton(){
                singleton = new SingletonExample7();
            }
            public SingletonExample7 getInstance(){
                return singleton;
            }
        }
    }