zoukankan      html  css  js  c++  java
  • [工作中的设计模式]单例模式singleton

    一、模式解析:

    单例模式是最简单和最常用的设计模式,面试的时候,不管新毕业的学生还是已经工作多年的筒子,对单例模式基本都能聊上两句。单例模式主要体现在如下方面:

      1、类的构造函数私有化,保证外部不能直接使用构造函数创建类的实例

      2、提供获取实例的方法,外部可以通过此方法获取已经创建好的实例对象,

      3、获取实例的方法必须保证实例唯一性。

      4、根据获取实例方法保证唯一性的方式,单例模式又分为以下几种:

    二、模式代码

    1、懒汉模式

    /**
     * 单例模式-懒汉模式
     * 懒汉模式意味着此模式比较懒惰,直到系统发起调用时候才会创建此对象的实例。
     * 需要注意的是要保证线程的同步,防止出现多个实例创建的情况
     * @author zjl
     *
     */
    public class Singleton1 {
        //创建元素实例
        private static Singleton1 singleton;
        //构造对象置为私有变量,不能从外部进行创建
        private Singleton1(){
            
        }
        /**
         * public方法,在外部调用时候,获取此对象的实例
         * 注意需要使用同步方法,防止多线程时候产生多实例
         * @return 单例对象实例
         */
        public static synchronized Singleton1 getInstance(){
            if(singleton==null){
                singleton=new Singleton1();
            }
            return singleton;
        }
    }

    懒汉模式实现了对象的懒加载,不过缺点是由于getInstance上加了同步关键字,导致此方法只能有一个线程访问,效率会比较低

    2、为了解决懒汉模式的速度问题,引入检测,也就是加锁之前先做一次判定,并将实例声明为volatile

    public class Singleton {
        private volatile static Singleton instance; //声明成 volatile
        private Singleton (){}
    
        public static Singleton getSingleton() {
            if (instance == null) {                         
                synchronized (Singleton.class) {
                    if (instance == null) {       
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
       
    }

    3、痴汉模式

    /**
     * 单例模式的痴汉模式
     * 痴汉模式表示此模式很急,一旦对象初始化,立马创建一个实例,以后获得实例的都采用此实例
     * @author zjl
     *
     */
    public class Singleton2 {
        /**
         * 私有构造函数,保证不能从外部创建实例
         */
        private Singleton2(){}
        //痴汉模式重点,对象初始化直接创建实例
        private static Singleton2 singleton=new Singleton2();
        
        /**
         * 直接返回之前创建的实例
         * @return 对象实例
         */
        public static Singleton2 getInstance() {
            return singleton;
        }
    }

    痴汉模式不会存在线程同步问题,但是缺点是不是懒加载,对象创建后立马创建实例。

    4、静态内部类

    /**
     * 使用静态内部类来创建单例
     * @author zjl
     *
     */
    public class Singleton3 {
        //私有化构造函数,使他不能在外部被创建
        private Singleton3(){};
        //创建静态内部类,初始化成员变量
        private static class SingletonHodler{
            private static final Singleton3 INSTANCE=new Singleton3();
        }
        /**
         * 获取实例方法,执行时候才去创建实例
         * @return
         */
        public static final Singleton3 getInstance(){
            return SingletonHodler.INSTANCE;
        }
    }

    静态内部类创建的方法使用不多,也是在面试中很少了解到的,但却是最为推荐的方法,因为他实现了懒加载,线程安全且不需要依赖jdk版本。

    三、应用场景

    单例模式在框架中应用较多,比如spring的bean管理可以设置是否为单例模式,数据库对象实例化

    四、场景代码

    由于比较简单,略过。

    五、疑问解决

    为什么我们在做懒汉模式的双重检测的时候,需要将对象改变为使用volatile进行修饰,此处与java的内存模式有关,并涉及到synchronized和volatile的内存操作

    1、java的内存模型主要分为主内存和工作内存,对应计算机的结构可以认为是计算机内存和cpu的高速缓存,对于任何主内存的数据进行操作时候,必须先将数据读取到工作内存形成一个备份,当对工作内存的数据操作完成后,将工作内存数据重新同步到主内存。

      对于工作内存和主内存的操作主要为:读取过程-read,load,use,写入过程 assign、store、write,这六个操作均具有原子性,但整个流程不是原子性。因此为了保证可见性和原子性,增加了lock和unlock操作,主要针对主内存区数据的锁定,一旦主内存区数据被lock,表示此线程独占了变量,不可被其他线程更改

    内存模型

    2、synchronized的作用主要有两点:程序临界区和内存同步。

      程序临界区:是一段仅允许一个线程进行访问的程序,一旦一个线程进入临界区,另外线程进入临界区只能在临界区外进行等待,等临界区线程执行完毕后,其他线程开始争夺资源,胜利者进入临界区,其他线程继续等待。

      内存同步:主要是主内存和工作内存的同步。进入临界区时,如果有工作内存的数据未被同步的主内存,则先进行同步,成为其他线程可见状态。工作内存的数据会被丢失,如果要使用,需要重新进行read和load操作。退出临界区时,将工作内存数据写入主内存,保证修改可见。

    3、volatile的作用是保证数据可见性,对于任何工作内存的assign操作会立刻store和write到主内存中,同时使其他工作内存放弃原有持有数据。但是volatile不保证操作原子性。

    4、具体分析可能出现的问题:

    public class Singleton {
        private static Singleton instance; //声明成 volatile
        private Date date=new Date;
        private Singleton (){}
    
        public static Singleton getSingleton() {        //1
            if (instance == null) {                     //2
                synchronized (Singleton.class) {        //3
                    if (instance == null) {             //4
                        instance = new Singleton();     //5
                    }
                }                                       //6
            }
            return instance;                             //7
        }
        public Date getDate(){
           return date;  
        }  
       
    } 

      如上边的代码,我们将原有懒汉模式稍作修改,增加了date字段,模拟双线程A与B的创建过程

    由于new Singleton()不是一个原子操作,程序在进入和出临界区时候,均会同步主内存的内容,除此之外B线程如果在3和6之间调用,就会发生A线程的工作线程内的内容不一定全部写入了主内存,假设此阶段instance写入了主内存,但是date没有写入,B线程将对data内容不可见,因此getDate将返回null。

    如果对instance添加了volatile,那么针对instance的修改,随时b线程都是可见的。

  • 相关阅读:
    常用不等式例题整理
    洛谷P1233 木棍加工题解 LIS
    莫比乌斯反演
    CSS样式使用
    相邻元素的层级(仿淘宝页面效果)
    js基础知识梳理(最简版)
    css2基础知识梳理
    html4基础知识梳理
    mysql密码遗忘和登陆报错问题
    mysql修改密码过期时间以及密码复杂性问题
  • 原文地址:https://www.cnblogs.com/jyyzzjl/p/5150110.html
Copyright © 2011-2022 走看看