详细解说单例模式

1.单例模式分为饿汉式单例模式和懒汉式单例模式; 

2.饿汉式单例模式的写法有两种:

  1).普通的写法:

public class Singleton{

    private static Singleton singleton = new Singleton();

    private Singleton(){
        
    
    }

    public static Singleton getInstance(){
        return singleton;
    }

}

  2)静态代码块形式

public class Singleton{

    private static Singleton singleton;

    static{
        singleton = new Singleton();
    }

    private Singleton(){

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

}

以上两种写法的优缺点都是一样的,只不过静态代码块的实例化过程放在了静态代码块里;

 

优点:在类装载的时候就完成了实例化,避免了线程同步问题。

缺点:在类装载的时候就完成了实例化,而没有达到懒加载的效果,所以如果从始至终都没有使用到这个实例,就会造成内存的浪费。

 

 

3.懒汉式单例模式的写法有四种:

 

  1)普通的懒汉式单例模式写法(只能在单线程环境下使用)

//存在多线程不安全情况
public class Singleton{

    private Static Singleton singleton;

    private Singleton(){

    }
    
    public static Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }


}

2)双重检查锁定式

//可以有效解决多线程安全问题,但是解决不了序列化和反序列化问题
 public class Singleton{
    //volatile的作用:避免实例未完全实例化(因为singleton = new Singleton()不是一个原子操作,    而是做了三件事:
    //1.给singleton分配内存空间;
    //2.调用Singleton的构造函数来初始化singleton;
    //3.将singleton对象指向分配的内存空间(执行完第三步,singleton就不是null了))

    //注意第二步和第三步的顺序(这两步的顺序是不能确定的),存在重排序的优化
    private static volatile Singleton singleton;

    private Singleton(){

    }

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

}

优点:不仅线程安全,并且延迟加载,效率也更高;

常见问题:

为什么要两次check,而不能一次check呢?

假设有两个线程同时调用getInstance方法,并且由于singleton是空的,所以两个线程都通过了第一个if判断,由于有锁机制的存在,会有一个线程先进入第二重if的判断,而另一个线程就在外面等待,而当第一个线程完成实例后,就会推出synchronized保护的区域,这时如果没有第二层singleton==null的话,那么第二个线程就会再次创建实例,这就破坏了单例,这肯定是不行的;

而对于第一个check而言,如果去掉它,那么所有的线程都会串行执行,效率低下,所以两个check都是需要保留的。

 

 

 

3)静态内部类式(由jvm来保证线程安全问题)

public class Singleton{

    private Singleton(){
    
    }

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

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


}

 

4)枚举式(最推荐的单例模式的方式)

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

    }
}

优点:写法简洁,避免多线程同步的问题,而且还能防止反序列化和反射创建新的对象来破坏单例。

原理:通过反编译枚举类可以发现,枚举中的各个枚举项是通过static代码块来定义和初始化的,他们会在类被加载时完成初始化,而java类的加载是由JVM保证线程安全的,所以创建一个Enum的枚举是线程安全的。而对序列化这一件事情,java专门对枚举类的序列化做出了规定,在序列化时,仅仅是将枚举对象的name属性输出到结果中,在反序列化时,就是通过java.lang.Enum的valueOf来根据名字查找对象,而不是创建一个新的对象,所以这就防止了反序列化导致的单例破坏的事件;而对于通过反射破坏单例而言,枚举类同样有防御措施,反射在通过newInstance创建对象时,会检查这个类是否为枚举类,如果是的话,就会抛出IllegalAraumentException("Cannot reflectively create enum objects")这样的异常,反射创建对象失败。

直接通过Singleton.INSTANCE.whateverMethod()的方式调用即可,方便、简洁又安全。

 

为什么需要单例呢?

原因一:节省内存,节省运算;

原因二:保证结果的正确性以及方便管理。

 

什么时候可以用到单例模式?

无状态的工具类:日志工具、字符串工具

全局信息类:全局计数、环境变量

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值