单例设计模式

单例设计模式

单例模式概述

在程序设计过程中,会接触到很多不同的需求,在不同需求的驱动下,人们为了解决一类特定的问题,提出了许多的设计模式,所谓模式就是解决一类问题的固定步骤,在软件设计中存在23种设计模式,我们通常情况下会接触到以下几种设计模式:

  • 单例设计模式
  • 模板设计模式
  • 装饰者设计模式
  • 观察者设计模式
  • 工厂设计模式
  • 动态代理模式

本文将重点阐述单例设计模式,所谓单例设计模式是指一个类中只能有一个对象,那么你此时可能有疑问,只有一个对象的类这种应用场景会有吗?答案是肯定的,会有,而且是很重要的一个方面,在Java Web设计中,由浏览器发送过来http请求,需要有一个对象去接收,我们称这个对象为servlet对象,大家可以想象这个对象究竟有几个?共享一个还是我们每个请求都创建一个servlet对象呢?仔细一想,当然是所有的http请求共享一个servlet对象,如果每个http请求都有一个servlet对象,那么将耗费多少内存,从节约资源的角度来讲,也只能共享一个,何况管理一个和管理多个的难度是不一样的,而且http请求可能是无限大的,太难去处理了,针对这样的需求,软件设计人员设计了单例模式。单例设计模式分为两种:分别是饿汉单例设计模式和懒汉单例设计模式。

饿汉单例设计模式

单例模式是指一个类只有唯一的一个对象,那么饿汉单例模式是指不管你是否会使用这个对象,我都会提前创建好该对象,放在那里,提供公共的接口供调用者调用。模式是解决一类问题的固定步骤,那么饿汉单例模式的设计步骤是怎样的呢?我们说步骤有三:

  1. 私有化构造函数;
    私有化构造函数的原因会防止任意的开发人员或者用户在其他的类中调用new去创建对象,私有的函数只能在本类中调用。
  2. 声明本类的引用类型变量,并且使用该变量指向本类对象,将其限制为private static 类型 变量名 = new 类型();
    限制为private的原因是封装性,封装是面向对象设计最基本的原则之一。static的原因是:如果不使用static,那么想要使用对象必须要创建对象去调用,但是单例模式中又不允许我们随意的创建对象,显然应该使用static,另外一个原因是:static类型变量被存放在方法区(数据共享区),没有其他的副本,相当的解决资源。
  3. 提供一个公共的方法获取本类的对象。
    由于上述1、2的原因导致我们必须要设计一个共有的接口,以方便我们调用这个独有的对象,此时应该使用public修饰符,保证所有的类都能使用,否则这个类的设计就没有任何作用了,而且也应该保证使用的是使用static修饰,因为不适用static修饰的函数,必须使用对象来调用,而单例模式又不允许我们去创建对象,形成矛盾。

提供一个简单的形式化说明例子,帮助理解上面的过程:

class Single{
    private Single(){  //构造函数可能会有参数等等,视具体情况而定,这里仅仅展示一种极其简单的单例模式的设计,至于构造函数完全遵循面向对象的创建方法
        //构造函数语句
    }
    private static Single s = new Single(); //创建该类的一个对象
    //提供一个公共的接口供调用者使用
    public static Single getInstance(){
        return s;
    }
}

class TestSingle{
    public static void main(String[] args){
        Single s1 = getInstance();
        Single s2 = getInstance();
        System.out.println("对象s1和s2是否是同一个对象?"+(s1==s2));//使用==得到的对象在内存中的地址,使用equals比较的是两个引用类型变量所指向堆内存中变量是否相等,如果s1与s2的地址相同,意味着得到了唯一的对象
    }
}

饿汉单例模式存在一个设计上的缺陷,就是不管其他类是否使用那个唯一的对象,它都会毫不犹豫的直接创建一个对象放在那里,这其实是对内存资源的一种浪费,在设计过程中应该避免,怎么避免呢?随即引入了单例模式的第二种方案。

懒汉单例设计模式

所谓懒汉单例模式是指,在需要的时候去创建对象,其具体的步骤为:

  1. 私有化构造函数;
  2. 声明本类的引用类型变量,但是不一套创建对象;
  3. 提供一个公共的方法获取本类的对象,获取之前首先判断是否已经创建了本例的对象,如果没有先创建,如果有直接返回。

提供一个简单的例子去说明这个过程:

class Single{
    private Single{
        //构造函数语句
    }
    private static Single = s ;//创建引用类型变量,但是不在堆区开辟空间
    public static Single getInstance(){
        if(s==null){
            s = new Single();
        }
        return s ;
    }
}
class TestSingle{
    public static void main(String[] args){
        Single s1 = getInstance();
        Single s2 = getInstance();
        System.out.println("对象s1和s2是否是同一个对象?"+(s1==s2));//使用==得到的对象在内存中的地址,使用equals比较的是两个引用类型变量所指向堆内存中变量是否相等,如果s1与s2的地址相同,意味着得到了唯一的对象
    }

看似这个模式更加的优美,但是实际上懒汉单例模式存在严重的线程安全问题,稍微不注意就会发生及其严重的问题,举个相当平凡的例子,在我们的实际使用计算机的过程中经常会遇到。大家都知道,现在的很多操作系统都包含有时间片抢占式调度方案,也就是说比如现在有两个人,分别是小明和小红都在调用上面的单例模式程序,假设小明刚刚执行到第7行,也就是判断了s为空,但是没来得及创建对象,然后cpu时间就被小红抢占了,小红接着也判断s为空,完成之后创建了s对象,此时小明又一次拿到了cpu时间,由于小明刚刚已经判断了s为空,所以接下来就会接着创建s对象,那么问题来了,这还是单例模式吗?两个人创建了两个对象,显然不可取。线程安全机制可以避免这种问题的发生,比如让小明进行回顾操作等等,关于线程安全的问题不在这里讲述,后续博客中会进行仔细的说明。

总结

单例模式的应用场景及其的广泛,尤其是在Java EE中,在很多面向对象的编程语言中也都有这种模式的使用场景,注意交叉的理解,做到知识的共享。

联系我

单例设计模式_第1张图片

你可能感兴趣的:(java,设计模式,软件设计,Class,设计)