单例模式
2017-06-08 15:55
43 查看
第一种 懒汉 线程不安全
public class Singleton { private static Singleton instance; private Singleton() { } //静态工厂方法 public static Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; } }
这种写法lazy loading很明显,等到第一次使用时再创建实例,因此称为懒汉。问题是该写法没有考虑线程安全问题,并发环境下可能会出现多个Singleton实例,在多线程下不能正常工作。但是提供了单例的基本思路:构造器私有。将构造器限制为private能够避免类在外部被实例化。
以下三种是对懒汉模式的改造,保证线程安全
第二种 懒汉 synchronized 保证线程安全
public class Singleton { private static Singleton instance; private Singleton() { } //静态工厂方法 采用同步 public static synchronized Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; } }
该写法能够保证线程安全,但是效率很低。不论instance是否被实例化,当调用getInstance获取实例的时候,都会加锁。
第三种 懒汉 双重校验锁
public class Singleton { private volatile static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) {//第一次判断 synchronized (Singleton.class) { if (instance == null)//第二次判断 instance = new Singleton(); } } return instance; } }
1.为何synchronized不加在getInstance方法上,而是移到方法体内?
将synchronized写到方法体内,是为了降低锁的粒度。考虑存在这样的情况:Singleton类中还会存在其他的static方法如f(),当synchronized直接加到getInstance方法上的时候,就会对整个Singleton类加锁,其他程序想要调用类中f(),无法执行只能等待getInstance方法执行完再调用。
将synchronized写进方法内,只有instance实例未被实例化的时候,才会将整个Singleton类进行加锁。所以将synchronized写到方法体内是为了不影响其他方法的调用。
2.为何会进行两次instance==null的判断?去掉第一个可以吗?去掉第二个可以吗?
去掉第一个也可以,但是不推荐。如果去掉第一个if判断,那么当调用getInstance方法的时候,无论instance是否被实例化,都会对整个类进行加锁,不去掉第一个的话,只有instance== null的时候,才会对整个类加锁。
去掉第二个不可以。很可能在第一个if判断的时候,instance== null成立,等到执行到同步块中的时候,instance可能已经被其他线程实例化了。所以需要再加一次判断。
3.关于volatile的作用?
volatile的作用是避免指令的重排序。初始化一个实例:instance= newInstance()在字节码中有四个步骤:
1.申请内存空间
2.初始化默认值
3.执行构造器方法
4.连接引用和实例
这四个步骤后面两个可能会出现重排序,1234或者1243,1243出现的结果就是造成未初始化完全的对象会被发布然后被使用。volatile可以禁止指令的重排序,从而避免这个问题。
第四种 懒汉 静态内部类
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() {} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
第五种 饿汉 非延迟加载
public class Singleton { //加载类的时候会初始化static的instance, private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
instance在类装载时就被实例化,因此是天生安全。
第六种 饿汉
public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return instance; } }
该种写法与第五种类似,同样都是在类初始化的时候创建静态对象,因此也是线程安全的。
第七种 枚举
public enum Singleton { INSTANCE; }
枚举实现单例模式,不仅可以避免多线程问题,还可以防止反序列化建立新对象。枚举单例模式有点类似饿汉模式,没有实现lazy loading。
饿汉和懒汉的区别:
饿汉保证类一旦加载完成,就把单例实例化,等到getInstance()时,单例已经存在。饿汉天生线程安全,因此可直接用于多线程。
懒汉顾名思
4000
义,lazy loading为 延迟加载,等到getInstance()时,再去实例化单例。
懒汉本身不是线程安全的,但是可以通过加同步,静态内部类,双重锁校验的方式,人为实现线程安全。
资源加载的性能:
饿汉模式下当类加载完成后,也完成了单例的实例化,单例就会存在于内存中,不管后面会不会用到,都会占用一定内存。如果该单例占用资源很多的情况下,采用饿汉模式,会导致性能降低。但是由于单例已经初始化完成,因此在第一次调用的时候速度会非常快。
懒汉模式下,延迟加载,对于占用很多资源的单例比较实用。等到使用该单例时再进行加载,会导致性能延迟。
从速度和反应角度看,饿汉模式较优;从资源利用率看,懒汉模式较优。
相关文章推荐
- 好书整理系列之-设计模式:可复用面向对象软件的基础 9
- OpenSource产品的商业操作模式
- COM组件中的线程模式
- 打开你的 DMA 模式 (转)
- 水木-Emacs 的 texinfo 模式 (三)
- 水木-Emacs 的 texinfo 模式 (二)
- 水木-Emacs 的 texinfo 模式(一)
- 虚拟8086模式的内存管理
- 16位BIT模式下的ALPHA运算
- 简析J2EE应用程序数据库类设计模式
- 探讨对等网络(P2P)商务模式
- 让DMA66 硬盘工作在DMA33 模式
- 闲聊设计模式和类
- MFC,STL与设计模式?
- 好书整理系列之-设计模式:可复用面向对象软件的基础 8
- [导入]让DMA66 硬盘工作在DMA33 模式
- 好书整理系列之-设计模式:可复用面向对象软件的基础 7
- 好书整理系列之-设计模式:可复用面向对象软件的基础 6
- 好书整理系列之-设计模式:可复用面向对象软件的基础 5.5