单例模式总结

来源:互联网 发布:手机拍摄淘宝照片 编辑:程序博客网 时间:2024/06/01 09:48

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

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年又有什么用呢,和两三年工作经验的人水平差不了多少。一定要趁着年轻多积累多研究,加油!

原创粉丝点击