Java 懒汉式单例 饿汉式单例
2015-09-11 17:08
357 查看
转载请注明出处:http://blog.csdn.net/mr_liabill/article/details/48374921 来自《LiaBin的博客》
单例模式很常见,在面试中也会经常直接让你写一个单例出来
单例模式写法一般分为两种,懒汉式和饿汉式
优点:
不需要考虑多线程问题,因为instance是静态的,在类加载的时候就已经实例化了,同时也避免了synchronized所造成的性能问题,
缺点:
但这种方式也有点弊端,因为初始化类的时候就需要构造实例,(即便你还没有用到这个实例),因此在某些特定条件下会耗费内存。
为什么需要按如下这么复杂的去实现,参考有详细的解释 http://blog.csdn.net/guolin_blog/article/details/8860649
假设线程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的延迟初始化的方案;如果确实需要对静态字段使用线程安全的延迟初始化,请使用上面介绍的基于类初始化的方案。
单例模式很常见,在面试中也会经常直接让你写一个单例出来
单例模式写法一般分为两种,懒汉式和饿汉式
饿汉式
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方法。相关文章推荐
- PropertyChangeListener简单理解
- 什么是设计模式
- 设计模式之创建型模式 - 特别的变量问题
- 七、设计模式——装饰模式
- 设计模式总结
- 设计模式之创建型模式
- 浅谈设计模式的学习
- PHP设计模式之装饰者模式代码实例
- php设计模式之单例模式实例分析
- 介绍php设计模式中的工厂模式
- PHP设计模式之适配器模式代码实例
- 深入浅出23种设计模式
- 浅谈c#设计模式之单一原则
- C#设计模式之观察者模式实例讲解
- C#设计模式之单例模式实例讲解
- 深入理解JavaScript系列(28):设计模式之工厂模式详解
- 面向对象设计模式的核心法则
- JavaScript设计模式之单件模式介绍
- 深入理解JavaScript系列(25):设计模式之单例模式详解
- JavaScript设计模式之外观模式实例