Java 懒汉式单例 饿汉式单例

来源:互联网 发布:空气刘海定型喷雾知乎 编辑:程序博客网 时间:2024/05/19 23:54

转载请注明出处:http://blog.csdn.net/mr_liabill/article/details/48374921   来自《LiaBin的博客》

单例模式很常见,在面试中也会经常直接让你写一个单例出来

单例模式写法一般分为两种,懒汉式和饿汉式


饿汉式

public class SingleTon {    //加载类的时候会初始化static的instance,从这以后,这个static的instance对象便一直占着这段内存,永远不会被回收掉。    private static SingleTon instance = new SingleTon();    //将构造函数private掉,避免直接new SingleTon()    private SingleTon() {    }    /**     * 因为是单例,所以只能通过static方法来获取实例,因此必须是static的。     * 方法实现较为简单,因为instance已经在加载类的时候被初始化好了,所以不存在多线程并发造成的问题     */    public static SingleTon getInstance() {        return instance;    }}

优点:

不需要考虑多线程问题,因为instance是静态的,在类加载的时候就已经实例化了,同时也避免了synchronized所造成的性能问题,

缺点:

但这种方式也有点弊端,因为初始化类的时候就需要构造实例,(即便你还没有用到这个实例),因此在某些特定条件下会耗费内存。


懒汉式

方式1: 基于volatile的双重检查锁定的解决方案

为什么需要按如下这么复杂的去实现,参考有详细的解释 http://blog.csdn.net/guolin_blog/article/details/8860649

public class SingleTon {    private static SingleTon instance = null;    //将构造函数private掉,避免直接new SingleTon()    private SingleTon() {    }    //synchronized避免多线程带来的问题,但同时效率降低,另一方面采取多重锁定,提高效率    public static SingleTon getInstance() {        if (null == instance) {            synchronized (SingleTon.class) {                if (null == instance) {                    instance = new SingleTon();                }            }        }        return instance;    }}
这样写是没问题的,因为私有构造函数中没有初始化任何属性,否则的话上面的代码还需要改进

public class SingleTon {    private static volatile SingleTon instance = null;    private String name;    //将构造函数private掉,避免直接new SingleTon()    private SingleTon() {        name = "SingleTon";    }    //synchronized避免多线程带来的问题,但同时效率降低,另一方面采取多重锁定,提高效率    public static SingleTon getInstance() {        if (null == instance) {            synchronized (SingleTon.class) {                if (null == instance) {                    instance = new SingleTon();                }            }        }        return instance;    }}
这里为什么需要使用volatile关键字呢?

假设线程thread1走到了第15行的if判断发现instance==null成立,于是都进入了外部的if体。这时候thread1先获取了synchronized块的锁,于是thread1线程会执行第18行的instance = new SingleTon();这句代码,问题就出在这里,这条语句它不是原子性执行的。在Java里,实例化一个对象的过程简单地讲,可以分为两步1)先为instance对象分配一块内存,2)在这块内存里为instance对象里的成员变量赋值(比如第11行里为url赋值)。假设当thread1执行完第1)步而还没有执行第2)步的时候,另外一个线程thread2走到了第15行,这时候instance已经不是null了,于是thread2直接返回了这个instance对象。有什么问题呢?instance对象的初始化(变量赋值等操作)还没执行完呢!thread2里直接得到了一个没有初始化完全的对象,就有可能导致很严重的问题了。

那么volatile关键字有啥作用呢?当用volatile修饰了instance变量之后,对instance的写操作”先行发生“于对它的读操作。(这是Java虚拟机里的先行发生原则)这样就保证了,thread1中的instance变量被完全初始化之后,thread2才能读取它,当没有完成初始化时,thread2只能等会儿啦。


优点:

需要的时候才去加载,内存消耗好一些。(因为在需要的时候才加载,所以叫懒汉式)

缺点:

代码如此复杂,,还有synchronized带来的运行效率问题,调用同步方法会慢不少


方式2: 基于类初始化的解决方案

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

内部类的初始化是延迟的,外部类初始化时不会初始化内部类,只有在使用的时候才会初始化内部类。而Java语言规范规定,对于每一个类或接口C,都有一个唯一的初始化锁LC与之对应。也就是说,SingletonHolder在各个线程初始化的时候是同步执行的,且全权由JVM承包了。


两种延迟初始化方案总结

延迟初始化降低了初始化类或创建实例的开销,但增加了访问被延迟初始化的字段的开销。在大多数时候,正常的初始化要优于延迟初始化。如果确实需要对实例字段使用线程安全的延迟初始化,请使用上面介绍的基于volatile的延迟初始化的方案;如果确实需要对静态字段使用线程安全的延迟初始化,请使用上面介绍的基于类初始化的方案。


总结

为了省麻烦,就用恶汉式吧。但是我一般用懒汉式,比较需要的时候才加载,可以节省内存。至于synchronized引起的效率问题,基本很少有这样的场景,因为很少有两个线程并发调用getInstance方法。

0 0
原创粉丝点击