继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

JAVA与23种设计模式之间的暧昧关系(一)单例模式

野老盟客
关注TA
已关注
手记 19
粉丝 33
获赞 1498

JAVA与23种设计模式之间的暧昧关系(一)单例模式

什么是单例模式?官方说,就是一个类中只保有他的一个实例,然后将提供给全局使用,简而言之,就是说,我调用的时候,我只需要实例化一次就好了。

那么,为什么要这样做?其实,这样做的好处就是防止类的泛滥实例化。因为我们知道Java是有垃圾自动回收机制,但是,说到自动,带来一个最大的弊端就是你不能把控他,也就是说如果你不能够随自己的心意去回收他,(当然,也是可以通过构造回收类去搞,但是,一般程序都不这样做),那么久而久之,就会照成很多不必要的麻烦,比如内存溢出啊,什么乱七八糟的, 这个时候,如果你用某种技巧性的方式进行勒令他不能实例化或者什么,由你统一生成,这个时候,单例模式的思想就出来了!

我们知道,Java的一个类要使用,只能通过new一个对象的形式去做,(反射的方式除外),那么怎么才让他不能new呢?这个时候,我们可以想到采用java的修饰符,顺道复习下吧,private,私有的,就是说,只有当前类可以使用,protected,保护的,既然是保护,就是当前类和他的子类可以使用。默认修饰符,就是什么都不加,同一个包可以使用,public,都可以访问,还有其它向static什么的,可以去百度下作用。很好,这个时候我们突然发现,原来有个私有的属性,那么我们是不是可以搞点事情出来?

首先,java我们要知道,new完以后会干嘛?当然是调用构造函数,所以,这个时候,我们可以针对构造函数搞事情,嘿嘿,把构造函数私有化呗,这样的话,不就不能new了?也就是说,不能实例化,但是不对啊,既然不能实例化,那么我怎么实例?这个时候就要了解private的属性,它的意思是说只能自己访问是不是,很好,既然只能自己访问,那么我们在当前类里面实例化一个当前类不就得了?说了那么久,我们来看下代码是怎么样的。

public class SingleClass{
    private SingleClass(){}
    public SingleClass instance = new SingleClass();
}

不对,有些看官肯定觉得不顺眼,怎么可以直接用在这里面实例化?那不也可以实例化很多个吗?好了,别急,待我慢慢说来。首先,这个代码是有问题的,既然刚才说了, 这样并不是单例,它仍然还可以生成很多个实例,既然这样,我们先让的instance私有化一下,因为将这个对象隐藏起来,为了后面做处理,比如说,我想让你什么时候生成就什么时候生成对不对,不对,如果私有化以后,不就不能获取实例了吗?简单。这个时候可以提供一个方法让后面调用嘛,看下代码呗。

public class SingleClass{
    private SingleClass(){}
    private static SingleClass instance = new SingleClass();
    public static SingleClass getInstance(){
        return instance;
    }
}

好了,简单的单例模式就出来了,为什么加static,是因为static有这样的作用(static变量是指不管类创建了多少对象,系统仅在第一次调用类的时候为static变量分配内存,所有对象共享该类的static变量,因此可以通过类本身或者某个对象来访问类变量。),所以,这种模式叫做饿汉式,因为就像一个饿了的人,急于吃东西,所以,在装载类的时候就开始创建实例了,这样做的好处是用空间换取时间。

除了饿汉式,还有一种叫做懒汉式,也就是说,我不想装载的时候实例化,我要你调了才弄,有点像是你踢了他一下才开始动,不踢我就不挪动一样。看下面的代码:

public class SingleClass{
    private SingleClass(){}
    private static SingleClass instance ;
    public static SingleClass getInstance(){
        if(instance==null)instance=new SingleClass();
        return instance;
    }
}

代码就不解释了,这种方式的好处是在加载类的时候很快,不过如果你要获取实例,就可能会比较慢,毕竟人家是在你获取的时候才开始干活嘛(针对于饿汉式来说的)。当然,其实这两种做法都一般般,毕竟没有考虑到线程同步啊等等问题,而且还会照成一些不好的(自行百度),既然这样,我们是一个有追求的人,那么这个时候是不是应该有一个新的写法?好了,请看代码:

public class OptSingleton {

    private volatile static OptSingleton instance=null;
    private OptSingleton() {}
    public static OptSingleton getInstance(){
        //先检查实例是否存在,如果不存在才进行下面的同步代码块
        if(instance==null){
            //同步代码块,线程安全的创建实例
            synchronized (OptSingleton.class) {
                //再一次检查是否存在,如果不存在才真正的创建
                if(instance==null){
                    instance = new OptSingleton();
                }
            }
        }
        return instance;
    }
}

这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。 (由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。)也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。

下面还有另一种方式:

public class OptGoodSingleton {
    private OptGoodSingleton(){}
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载
     */
    private static class SingletonHolder{
        /**
         * 静态初始化器,用JVM来保证线程安全
         */
        private static OptGoodSingleton instance = new OptGoodSingleton();
    }

    public static OptGoodSingleton getInstance(){
        return SingletonHolder.instance;
    }
}

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。 这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

好了,到此,单例模式就介绍到这里,如果各位看官觉得还有更好的代码实现方式,不妨在后面评论下,嘿,如果觉得好,请关注下我并点赞,后面会有更多设计模式系列哦,嘿嘿。

打开App,阅读手记
3人推荐
发表评论
随时随地看视频慕课网APP