Java设计模式之单例模式

Java设计模式之单例模式


单例模式用通俗易懂的语言来描述就是始终保持一个对象只有一个实例。那么如何保证一个类始终只有一个实例呢?单例的实现方式有很多。我们先来看看最基本的单例模式的写法。

  • 私有化构造方法
  • 对外提供方法,使得外部可以利用方法来生成对象
普通模式
    public class SingleInstance {
        //静态实例
        private static SingleInstance mSingleInstance;
        //构造方法私有化
        private SingleInstance(){
        }

        // 对外提供一个公共的静态方法返回一个单一的实例
        public static SingleInstance getSingleInstance(){
            if(mSingleInstance == null){
                mSingleInstance = new SingleInstance();
            }
            return mSingleInstance;
        }
    }

以上的限制有:

  1. static关键字,被static关键字修饰的属性或者变量有且只有一份
  2. 私有化构造方法,外部不能通过new关键字去创建对象
  3. 判空说明:在创建实例对象的时候,先判空,然后再根据情况来选择直接返回之前的对象还是新建一个对象返回。

以上的写法是最简单的单例模式,这在普通的情况下确实能够保证始终只有一个单例,但是在多线程的情况下就不能保证了, 我们可以使用代码测试一下,在这里我提供一下代码的思路,有兴趣的小伙伴可以尝试一下。

利用线程池对象,创建100个线程,然后在每个线程中执行创建对象的行为,创建完成后将创建好的对象放入set中,利用set的特性,对重复的对象set会自动去重,最后执行完成之后,将set的大小打印出来即可,或者打印出set中的内容。我这里测试的100次,最多创造了4个对象,可见这种模式并不保险。

既然上面的方法并不保险,那我们该如何改进呢?可能有小伙伴想到了,不就是并发的情况,也就是多线程的情况会出问题嘛, 加个synchronized不就可以了,方向是对了,那么应该在哪里加呢?

于是有了下面的单例版本二号

单锁模式
    public class SingleInstance {
        //静态示例
        private static SingleInstance mSingleInstance;
        //构造方法私有化
        private SingleInstance(){
        }

        // 添加了synchronized关键字,保证创造单例
        public static synchronized SingleInstance getSingleInstance(){
            if(mSingleInstance == null){
                mSingleInstance = new SingleInstance();
            }
            return mSingleInstance;
        }
    }

这种模式是对整个方法都进行同步锁,被synchronized修饰的方法相当于临界区资源,当有对象进入临界区资源的时候,其他对象是不可以进入临界区访问资源的,需要在外面等待,直到该对象访问完成后释放临界区资源,其他对象才能访问临界区。举个现实生活中比较常用的例子,就相当于一群人去上厕所,厕所只有一个位置,厕所便是临界区资源,一群人都想去上厕所,只能按照排队的方式去分别访问临界区的资源。不能说别人在厕所蹲的好好的,你一脚就踹门进去了,这样就属于抢占式了。虽然这种方法满足了单例模式的条件,但是在性能上仅此于上面的方法。

可能又有人有疑问了,这不是和单例要求一样嘛?怎么就不可以了,你试想一下,现在有一百个人,都要上厕所,于是排队从一到一百。现在一号进去了,谁也不知道他是拉屎还是拉尿,也不知道他拉屎或者拉尿需要多长时间,同时也不知道他有没有坏肚子等等等等一些乱七八糟的原因。排队的人谁也不了解谁,如果仅仅只是按照这样排下去的话,不说最后一个,估计排到第十个,第十个已经大小便失禁了。更何况等着访问临界区资源的线程何止100个。

当然程序不会这样,只是这样的单例模式在性能上实在是太差了。

现在延伸第三个版本的单例模式

双锁模式
    public class SingleInstance {
        private static SingleInstance mSingleInstance;
        private SingleInstance(){
        }
        public static synchronized SingleInstance getSingleInstance(){
            if(mSingleInstance == null){
                synchronized (SingleInstance.class){
                        if(mSingleInstance == null){
                            mSingleInstance = new SingleInstance();
                    }
                }
            }
            return mSingleInstance;
        }
    }

双锁模式比上面的单锁模式就要好多了,有没有小伙伴知道优点在哪里呢?从正面描述可能有些问题,我们不妨从反面看看有没有什么问题。

假设我们去掉同步块中的判空操作,现在有AB两个线程,A先进入同步块中,创建完实例直接返回,退出同步块,此时B获得互斥锁,进入同步块,此时同步块中没有判空操作,B也同样会返回一个实例出来,这样就违反了单例的原则。
从上面看,双锁模式应该是没有太大的问题了,但是如果了解JVM运转的同学来说,上述代码还是存在问题的。楼主自己本身对JVM虚拟机的运行不是太了解,这里也就不坑你们了,哈哈,下面的静态内部类就是根据JVM的调度来实现单例的,有兴趣的小伙伴可以读一读JVM相关的书籍。
单例模式并不是只有这几种写法,相反,单例模式的写法很多。在这里,我在给出几种比较典型的单例写法。

饿汉式(使用静态常量)
public class Singleton {

    private final static Singleton INSTANCE = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return INSTANCE;
    }
}

比较简单,在类装载的时候就完成了实例化,避免了线程同步的问题

没有达到LazyLoading(懒加载)的效果,如果这个实例一直都没有被利用,那么该实例占用的资源就是浪费了。

饿汉式2(使用静态代码块)
public class Singleton {

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    private Singleton() {}

    public Singleton getInstance() {
        return instance;
    }
}

静态代码块也是在类装载的时候实例化的,所以这个方法的优缺点和上面的是一样的。

静态内部类
public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

枚举
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {

    }
}

总结

大概我所了解的单例模式就这一些。常用的写法就是双互锁,以及静态内部类。当然,单例模式还有一些其他的写法,这里我就不赘述了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值