单例设计模式下的线程安全

在项目开发中,我们经常听到单例设计模式,即将类的构造方法私有化,对外只提供获取该类实例的接口;根据类实例的创建方式又可分为立即加载(饿汉模式)和延迟加载(懒汉模式),在实际中需要结合项目实际需求选择合适的加载方式,因此深入理解这两种加载模式下的特点以及多线程环境下的安全问题,成为我们项目开发设计的必备技能之一。

目录

  1. 立即加载
  2. 延迟加载

一、立即加载

立即加载模式,即在类的字节码加载时,同步创建类的实例对象,由于类的字节码是唯一的,所以外部通过接口获取的实例对象也是唯一的,不不存在线程安全问题,通常有以下几种方式实现立即加载模式

1.直接创建实例对象

public class MyObject{
    //立即加载,类加载时就创建类的实例对象
    private static MyObject myObject = new MyObject();
    //类的构造方法私有化,只可在类内部创建实例对象
    private MyObject(){
        /**
        * 让线程睡眠5秒钟,可以模仿实例创建过程中需要加载文件的准
        * 备工作
        */
        Thread.sleep(5000);
        System.out.println("MyObject实例被创建了......");
    }
    //提供外部获取实例对象的接口
    public MyObject getInstance(){
        /**
        * 针对此种方法由于未同步,存在线程不安全问题,我个人不赞同
        * 因为实例对象已经创建完成,该方法只是提供一个公共接口来获
        * 取创建的实例对象,根本不需要考虑线程安全的问题,对于线程
        * 不安全可能是多个线程共同获取到了同一个对象,在使用这个对
        * 象调用一些方法的时候,可能存在一些公共变量需要同步的问题
        */
        return myObject;
    }  
}

2.同步代码块创建实例对象

public class MyObject{
    private static MyObject myObject;
    //使用静态代码块创建实例对象
    static {
        myObject = new MyObject();
    }
    private MyObject(){
        System.out.println("MyObject实例被创建了......");
    }
    public MyObject getInstance(){
        return myObject;
    }  
}

3.静态内部类

public class MyObject{
    //创建静态内部类
    private class MyObjectHandler{
        //创建实例对象
        private static MyObject myObject = new MyObject();
    }
    private MyObject(){
        System.out.println("MyObject实例被创建了......");
    }
    public MyObject getInstance(){
        return MyObjectHandler.myObject;
    }  
}

这三种立即加载模式本质上都是在类的字节码加载时,就完成类实例的创建,针对不同的项目实际需求可能需要选择不同的模式,其实本质上都是采用第一种方式的实现思路。

二、延迟加载模式

延迟加载模式,即在首次获取类的实例对象时创建实例,下次获取的实例对象返回的仍然是第一次创建实例对象,通常采用两种方式来实现该模式,为类的静态成员变量和类的内部枚举。

1.类的静态成员变量

public class MyObject{
    /**
    * volitale关键字保证每次获取myObject变量都为多线程
    * 运行环境下的最新变量值,保证线程安全问题
    */
    private volitale static MyObject myObject;
    //私有化构造方法
    private MyObject(){
        System.out.println("MyObject实例被创建了......");
    }
    public static MyObject getInstance(){
        // 此处获取myObject的变量值为最新的值由关键字volitale保证
        if (myObject != null){
           System.out.println("MyObject实例对象已经创建......");
        } else {
            //线程睡眠5秒钟,用来模拟实例对象创建之前的一些准备工作
            Thread.sleep(5000);
            synchronized(MyObject.class){
                //再次判断myObject对象是否已经被其他线程进行创建
                if (myObject == null){
                    myObject = new MyObject();
                }
            }
        }
        return myObject;
    }

}

2.类的内部枚举

public class MyObject{
    //私有化构造方法
    private MyObject(){
        System.out.println("MyObject实例被创建了.....");
    }
    //类的内部枚举
    public enum MyObjectEnum{
        /**
        * 直接使用枚举名.该属性名(MyObjectEnum.myObjectFactory)可
        * 创建对应的实例对象,由于枚举对象的唯一性,即首次获取进行创建,
        * 下次获取取上次创建的枚举值,保证了获取对象的唯一性
        */
        myObjectFactory;
        private MyObject myObject;
        private MyObjectEnum(){
            //模拟实例对象创建时的一些准备工作
            Thread.sleep(5000);
            myObject = new MyObject();
        }
        //给外界提供获取枚举属性的接口
        public MyObject getMyObject(){
            return myObject;
        }  
    }    
    //给外部提供获取类实例对象的接口
    public MyOjbect getInstance(){
        return MyObjectEnum.myObjectFactory.getMyObject();
    }
    
}

两种延迟加载模式的方法虽然形式有所差异,本质上仍然是一致的,即保证多线程环境下多次获取类的实例对象是唯一的。

单例设计模式在实际中到底采取哪种加载模式,以及各种加载模式对应实现的方法还需要各自开发人员根据实际情况选取,以上简单解析只供参考。

你可能感兴趣的:(后端)