【2.1】设计模式之单例模式

今天看了一下单例模式,随笔记录一下。

一、什么是单例模式

说单例模式之前,先看看一个成语:形单影只。看到这个成语,我们第一感觉就是很孤单,就一个人。

那么再看单例模式,就显而易见,肯定也是包含了孤单,一个,独苗的意思了。

那么单例模式这个“单”是单在哪里呢?我们看下它的定义:

Ensure a class has only one instance,and provide a global point of access to it。

翻译过来的意思:

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

看到这个定义,有两句话是重点:

1、某一个类只有一个实例

2、自行实例化

通俗的讲就是一个类只能生成一个对象。那么该怎么实现呢?

我们都知道对象的产生通常通过new关键字完成(当然也可以通过对象拷贝、反射等完成)。看到这里,我们就脑瓜疼了,这个怎么控制啊?

等等。。。我们是不是忘记了什么?是的,我们忘记了构造函数。。。。。

如果我们把构造函数设置成private权限不就可以禁止创建外部对象了吗?

我们举个例子,例如生活中,古代的皇帝,一般来说同时只能由一个皇帝,是不是特别像我们的单利模式。那么用单例模式怎么来实现呢?看代码清单:

//单例模式
public class Emperor {
    //初始化一个Emperor对象
    private static final Emperor emperor = new Emperor();
    //私有化构造函数
    private Emperor(){};
    //提供统一对外实例接口
    public static Emperor getInstance(){
        return emperor;
    }

    public static void say(){
        System.out.println("我是汉武大帝。。。。");
    }
}

 

通过定义一个私有构造函数,避免被其他类new出一个对象,而Emperor 自己可以new出一个对象,其它类对该类的访问都可以通过getInstance方法获取同一个对象。

那么皇帝有了,我们来听听这个皇帝想说什么:

public class Test {

    public static void main(String[] args){
        Emperor emperor =  Emperor.getInstance();
        emperor.say();
    }
}

运行结果:

我是汉武大帝。。。。

二、单例模式优缺点

单例模式优点:

1、由于单例模式在内存中只有一个实例,减少了内存开支,所以对于一个对象需要被频繁创建、销毁时,而且创建或者销毁时性能又无法优化,单例模式优势就非常明显。

2、由于单利模式只产生一个实例,所以减少了系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其它依赖对象,那么就可以通过在应用启动时,直接产生一个单利对象,然后永久驻留内存的方式来解决(在java EE中采用单利模式需要注意JVM回收机制)

3、单利模式可以避免资源的多重占用,例如一个写文件动作,由于只有一个实例在内存中,避免了对同一个资源文件的同时操作。

单例模式缺点:

1、单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码,基本上没有第二种途径。单例模式为什么不增加接口呢?因为接口对单例模式没有任何意义,他要求“自行实例化”,并提供单一实例,接口或者抽象类是不可以被实例化的。当然了,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。

2、单例模式对测试是不利的,在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。

3、单例模式与上文讲到的单一职责原则有冲突,一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

单例模式的使用场景:

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体场景如下:

1、要求生成唯一序列号的环境

2、在整个项目中需要有一个共享访问点或者共享数据,例如一个web页面上的计数器,可以不需要把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的

3、创建一个对象需要消耗的资源过多,如需访问IO和数据库等资源。

4、需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)

 

其实springmvc就是一个非常经典的单例模式案例。

三、饿汉模式与懒汉模式

看到了上面单例模式的例子,以及结合单例模式的缺点:初始化时生成对象并永久驻留JVM内存中,可能有人在想,启动加载慢,并且永久占用资源,可不可以由别的方式实现单例模式,来规避这些缺点呢?

还真有!下面我们就说说单例模式的两种实现方式:懒汉模式和饿汉模式

上文提到的初始化时生成对象的时饿汉模式,人如其名,非常饥饿,刚出来就要吃内存。

饿汉模式说完了,我们再说说懒汉模式,看代码清单:

//懒汉模式
public class Emperor {
    //创建一个Emperor对象
    private static Emperor emperor = null;
    //私有化构造函数
    private Emperor(){};
    //对外提供getInstance接口
    public static Emperor getInstance(){
        if (null == emperor){
            emperor = new Emperor();
        }
        return emperor;
    }
}

真的不是一家人,不进一家门,显而易见,懒汉模式也是人如其名,非常懒,你不用我,我就不生成对象。

不知你们发现这个代码的缺点了没有,这种写法,在并发量小的时候还好,如果并发了多起来的话,问题就出来了:

例如线程A访问时,判断emperor == null,A创建Emperor 对象是需要时间的,在这个过程中,线程B也访问了,此时 emperor 也是为null,所以B继续运行下去,这就导致了A创建了一个Emperor 对象,B也创建了一个Emperor对象。内存中就存在了两个Emperor 对象。导致线程极不安全。

那么怎么解决这个问题呢?我们可以在getInstance上加上synchronized关键字,也可以在getInstance方法内部代码块增加synchronized关键字。

看代码清单:

在getInstance上加上synchronized关键字:

//懒汉模式
public class Emperor {
    //创建一个Emperor对象
    private static Emperor emperor = null;
    //私有化构造函数
    private Emperor(){};
    //对外提供getInstance接口
    public static synchronized Emperor getInstance(){
         if (null == emperor){
            emperor = new Emperor();
        }
        return emperor;
    }
}

getInstance方法内部代码块增加synchronized关键字:

//懒汉模式
public class Emperor {
    //创建一个Emperor对象
    private static Emperor emperor = null;
    //私有化构造函数
    private Emperor(){};
    //对外提供getInstance接口
    public static synchronized Emperor getInstance(){
        if (null == emperor){
            synchronized (Emperor.class){
                if (null == emperor){
                    emperor = new Emperor();
                }
            }
        }
        return emperor;
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值