浅谈单例模式

单例模式为23种经典设计模式中最简单的一种,然而很多人实现的单例并不是真正的单例,或者说是"不安全"的单例,下面我们就从最漏的单例说起,最后为现在业界使用的单例实现方法:

直接上代码:

第一种:

public class Singleton {
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if (instance==null){
            return instance = new Singleton();
        }
        return instance;
    }
}
下面说下这种方法的缺点,其实这种方法并不是真正的单例,为什么呢?因为它的对象的创建不是完整的原子,所以在多线程竞争下,可能导致多个实例

第二种:

继第一种,因为创建的时候不能保证原子性,所以我们可以通过加同步锁

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

但是这种方法明显也有缺点,并且是致命的,因为方法加同步,每次都要同步判断,太浪费时间,太慢,而这个慢可能造成一些列意想不到的结果

第三种:

继第二种,因为方法加同步太慢,所以呢,有人就说,那么我可不可以不在方法上加锁,在代码块中加锁呢?

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

如上,加双重校验锁,注意方法中进行两次校验,这样如果instance不为null就直接返回,不用走同步,如果instance!=null再进行同步校验,这样的确提高了效率,只有第一次加载的时候慢点,其他时候就根本不用走同步,的确快!但是这种方法就没有问题了吗?大家想一想!假如在高并发的情况下,假如我A,B线程同时进入方法,假如A拿到锁之后呢,B等待对吧!但是,假如我A刚执行完同步代码块,但是这时候,我还没有把instance改变之后的值加载到公共内存中,这时候B进入同步代码块了,因为A还没有将代码块写入公共内存,所以B进来之后,instance依然为null,又会进行创建一次,这时候发生了什么?所以我们要保证A对内存的修改对B是可见的.

第四种:

继第三种,在声明instance的时候加上volatile关键字,其他不变,但是这样写太麻烦了有没有简单的办法,不使用同步锁的呢?有

第五种:静态内部类

public class Singleton{
    private Singleton(){}
    public static Singleton getInstance(){
        return Instance.instance;
    }
    private static class Instance{
        private static Singleton instance = new Singleton();
    }
}

解释下,为什么静态内部类可以,因为静态内部类在jdk类加载的时候,只会被加载一次,所以,自然也就是单例了.

貌似现在一切问题都解决了,貌似"完美"了,但真的是这样么?no!!!不要忘了java中有反射,你不是构造私有化么,我用反射把你私有改为共有,我不就可以创建实例了么?所以,以上所有方法貌似都不可以了!那该怎么办呢?如此就引申出最后一种解决方法了.

第六种:枚举

public enum Singleton{
    INSTANCE;
    
    public void doSomething(){
        
    }
}
这种在调用的时候,完全可以Singleton.INSTANCE.doSomething()来调用

下面解释下为什么可以啊,因为枚举,在java中天生就是构造私有的,枚举不允许有共有构造,所以即使反射也不可以,所以就可以用来做单例的载体,并且枚举可以声明一些其他的方法,并进行处理所以就完全可以实现单例!

下面附上测试代码,和测试截图

为了测试我们把单例中添加如此两句输出语句:

public enum Singleton{
    INSTANCE;

    private Singleton(){
        System.out.println("这是构造方法...");
    }

    public void doSomething(){
        System.out.println("这是业务...");
    }
}

测试代码:

public static void main(String[] args){
    for(int i=0;i<20;i++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().sleep(1000*3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Singleton.INSTANCE.doSomething();
            }
        }).start();
    }
}
测试结果:



看测试结果就可以看出,构造只被执行一次,并且枚举是java内部定义的,你去反射吧!

如果以上见解有不到的地方请谅解,如果有说错的地方请指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值