单例模式总结

单例模式是我们面试中经常会遇到的一道题,看似简单,实则有很多玄机,今天来好好总结一下:

1. 饿汉模式

饿汉模式的单例模式是一种较为简单的写法,代码如下:

public class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){
    }
    public static Singleton getInstance(){
        return instance;
    }
}
优点

虽然写法简单,但不要小瞧它,它是线程安全的绝对单例,当面试时被要求写一个线程安全的单例模式时完全可以用这个写法,还可以引申出一些其他的问题。

缺点

这种写法的缺点也比较明显,就是在有其他静态方法时,实例会在还没有使用它们的时候就加载好了,当对象很大时会造成对内存资源的浪费。
还有就是它不能防止反序列化和反射时产生新的实例。

PS:
-反序列化: 序列化是指把JAVA对象转换为字节流的过程,与之相对反序列化就是将字节流重构为JAVA对象的过程。
-反射:指程序在运行过程中可以获取到类的属性和方法,实现动态创建对象。

2. 懒汉模式

懒汉模式的代码如下:

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

解决了饿汉模式下出现的问题,实例在未被使用时不会被创建,采用了延迟加载的办法

缺点

非线程安全,当两个线程发现instance都为null时,就会都去执行初始化操作。
不能防止反序列化和反射产生新的实例。

3.方法锁

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

线程安全的绝对单例,面试时可写。

缺点

由于方法体中可能还存在其他功能,方法加锁会导致并发性能下降。
无法防止反序列化和反射产生新的实例。

4.双重检查锁

public class Singleton{
    private static Singleton instance = null;
    private Singleton(){
    }
    public static Singleton getInstance(){

        if(null == instance){// one
            synchronized(Singleton.class){
                if(null == instance){// two
                    instance = new Singleton();// three
                }
            }
        }

        return instance;
    }
}
优点

与方法锁相比,并发性能更高。

缺点

并非绝对线程安全。
无法防止反序列化和反射产生新的实例。

线程不安全的原因:
java指令乱序执行,代码中three部分执行时分为三部分:
1 在内存中分配一块内存
2 调用构造方法
3 将内存地址指向instance(此时instance不为null)

正常是123的执行顺序,但是由于java指令乱序执行的特点,有可能出现132的情况,例如两个线程A和B,线程A在获得时间片按照132的指令顺序执行,当执行到2时,线程B获得到时间片,此时instance已经不为null,直接返回了instance,但此时构造方法还没有执行,返回的instance是未被初始化的对象,会产生错误。

5.双重检查锁与volatile联用

public class Singleton{
    private static volatile Singleton instance = null;
    private Singleton(){
    }
    public static Singleton getInstance(){

        if(null == instance){
            synchronized(Singleton.class){
                if(null == instance){
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}
优点

线程安全并且绝对单例。
线程安全的原因:
volatile不仅可以每次都从主存中读取变量,而且还屏蔽了指令重排,所以通过volatile修饰后一定是指令一定按照顺序执行的。

缺点

无法防止反序列化和反射产生新的实例。

6.静态内部类

public class Singleton{
    private static class InnerHolder{
        static final Singleton instance = new Singleton();
    }
    private Singleton(){
    }
    public static Singleton getInstance(){
        return InnerHolder.instance;
    }
}
优点

只有在使用的时候才会加载实例,这是这种写法与饿汉模式的区别,而且这种写法也是线程安全并且绝对单例。
线程安全的原因:
static修饰的类只会被初始化一次。

缺点

无法防止反序列化和反射产生新的实例。

7.枚举类

public class SingletonFactory{
    private enum EnumSingleton{
        factory;
        private Singleton instance;
        private EnumSingleton(){
            instance = new Singleton();
        }
        public static Singleton getInstance(){
            return instance;
        }
    }
    public static Singleton getInstance(){
        return EnumSingleton.factory.getInstance();
    }
}

class Singleton{
    public Singleton(){
    }
}
优点

枚举类的构造方法是在类加载时被实例化,因此也是线程安全并且绝对单例,同时防止反射和反序列化产生的新的实例。

缺点

与饿汉模式类似,会影响并发性能。


写在最后

今天老大让和一个有9年工作经验的来面试的人聊聊技术,我就让他写一个线程安全的单例模式,结果他写了一个方法锁的写法,里面用了两次synchronized,我问他问什么要用两次,结果他说这是在网上看到的云云,具体原因也没说出来,感觉就是死记硬背下来的,关键是还没写对。
我觉得通过这事也给自己敲响了警钟,如果做事情不求甚解,这样子混个5年甚至10年又有什么用呢,和两三年工作经验的人水平差不了多少。一定要趁着年轻多积累多研究,加油!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值