Java设计模式——单例模式

来源:互联网 发布:linkedin大数据 编辑:程序博客网 时间:2024/05/04 13:42

简介

java中,有些对象我们只需要一个,比如:配置文件、线程池、日志对象等。如果创造出多个实例,就会导致很多问题,比如占用过多资源、不一致的结果等。

单例模式的实现方式有多种,万变不离其宗,单例模式的中心思想就是构造方法私有化。也就是说不允许在类的外部创造实例,那么类的实例从哪来?类自己提供,具体来说是类中的类方法提供。主要的实现方式有两种:恶汉模式和懒汉模式。

恶汉模式与懒汉模式

代码

/** * 单例模式测试类 * @author Goser(mailto:goskalrie@163.com) * @Since 2016年8月30日 */public class Singleton {public static void main(String[] args) {SingletonHungry sh1 = SingletonHungry.getInstance();SingletonHungry sh2 = SingletonHungry.getInstance();compare(sh1, sh2);//trueSingletonLazy sl1 = SingletonLazy.getInstance();SingletonLazy sl2 = SingletonLazy.getInstance();compare(sl1, sl2);//true}public static <T> void compare(T obj1, T obj2){System.err.println(obj1==obj2);}}//恶汉模式class SingletonHungry{//1.将构造方法私有化,不允许外部直接创建对象private SingletonHungry(){}//2.创建类的唯一实例private static SingletonHungry singletonHungry =new SingletonHungry();//3.提供一个用于获取实例的方法public static SingletonHungry getInstance(){return singletonHungry;}}//懒汉模式class SingletonLazy{private SingletonLazy(){}private static SingletonLazy singletonLazy;public static SingletonLazy getInstance(){if(singletonLazy == null){singletonLazy = new SingletonLazy();}return singletonLazy;}}

上面的测试代码中都打印出了true,说明单例模式是成功的。

从上面的代码中不难总结,实现单例模式的主要步骤:

1.构造方法私有化;

2.声明或创造实例;

3.提供实例的方法。

代码讲解:

第一步使用private关键字,将构造方法私有化,也就是说不允许外部实例化该类,好多资料都将单例模式和古代的皇帝进行比较,那么这一步就相当于中央集权。

第二步static声明或实例化,既然外部没有实例化的权利,但是类只要是想要使用,那么就需要实例化,总要有个人做这件事,这里有类本身进行实例化。

第三步将第二步的实例返回给调用getInsance方法的外部。

更进一步

以上三步,每一步都很重要,为什么使用上面的三步就能实现单例模式呢?前面说的单例模式的中心思想是构造方法私有化,但是,底层关键的是第二步的static关键字。static关键字的特点是,被static修饰的变量一般称为类变量,没有被static修饰的变量一般称为实例变量。从这点就能看出,类变量是与类对应的,类是唯一的,那么类变量也应该是唯一的。实际上,被static修饰的类变量,JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,也就是说,类变量的内存是静止的(但是内存中的内容是可以改变的,就像是房子,房子盖好后不能再随便拆掉再盖,但是里面的人可以变)。由static关键字的特点可以得出,SingletonHungry中的singletonHungry变量SingletonLazy中的singletonLazy变量在内存中是唯一的,以此来实现单例,步骤一中的构造方法私有化是保障,步骤二中的static才是实现关键。

恶汉模式VS懒汉模式

恶汉与懒汉的命名由其实例化的时机而来。恶汉模式是在类加载的时候就实例化,形象的说就是,该种模式比较饥饿,需要先吃饱(实例化)才能工作,而懒汉模式则是在调用getInstance方法时才实例化,相较恶汉比较懒。

恶汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快(线程安全),因为在类加载时就实例化好了,已经与线程无关了。

懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢(线程不安全)

在实际使用时,一般两者区分很小,在对线程安全有要求时才会加以区别,或是使用其他的变体。但是面试的时候,基本上写出中心的步骤就可以了,如果能将static的底层关键表述出来,远比多说几种变体好的多。(面试的时候不一定面试官问你什么你就说什么,可以适当的明修栈道暗渡陈仓……声东击西,往往会得到意外的收货,但是相关性是第一的,问你java你不能回答C吧,但是问你单例模式的几种实现方式,你只还记得恶汉模式,那么回答完恶汉模式的实现肯定是不行的,会扣分的,但是补充上底层的实现,绝对是加分项)。

懒汉模式的线程安全改进

synchronized

线程安全是个专门的课程,一般的是使用同步synchronized关键字

//懒汉模式class SingletonLazy{private SingletonLazy(){}private static SingletonLazy singletonLazy;public static synchronized  SingletonLazy getInstance(){if(singletonLazy == null){singletonLazy = new SingletonLazy();}return singletonLazy;}
有些资料上还会介绍什么双重检查,如:

//public static SingletonLazy getInstance() {  //if (singletonLazy == null) {    //  synchronized (SingletonLazy.class) {    //     if (SingletonLazy == null) {    //        SingletonLazy = new SingletonLazy();   //     }    //  }    //}    //return singletonLazy;   //}
但是根本没什么用的,所以所有的代码注释掉了,没有什么实际的作用,还影响性能。

静态内部类方式

//静态内部类方式实现的单例模式class SingletonLazy2 {        private static class LazyHolder {       private static final SingletonLazy2 INSTANCE = new SingletonLazy2();    }    private SingletonLazy2(){}    public static final SingletonLazy2 getInstance() {       return LazyHolder.INSTANCE;    }}
这种方式,实际上是什么呢?将开始的代码做以下改变:

class SingletonLazy{private static class SingletonHungry {private static final SingletonLazy SINGLETONLAZY = new SingletonLazy();}private SingletonLazy(){}public static final SingletonLazy getInstance(){return SingletonHungry.SINGLETONLAZY;}}
经过对比不难发现,所谓的静态内部类方式只不过是将恶汉模式作为懒汉模式的静态内部类来使用了,将懒汉和恶汉的优点结合起来,就成了即线程安全,又不影响性能的静态内部类模式了。

总结

单例模式基本有两种实现方式:恶汉模式和懒汉模式,恶汉模式线程安全,懒汉模式不安全,但是可以通过修改实现线程安全,单例模式还有其他的实现方式,但是常用的就是上面讲到的。



0 0