单例模式的懒汉式和饿汉式实现分析
1 package com.yan.singleton; 2 3 /** 4 * 单例模式(Singleton):是一种常用的设计模式。 优点(3个): 1.某些类的创建比较频繁,对于一些大型类的对象,这是一笔很大的系统开销。 5 * 2.省去了new操作符,降低了系统内存的使用频率,减轻了GC的压力。 6 * 3.有些类,如交易所的核心交易引擎,控制着交易流程,不能有多个实现。(一个部队有多个司令在指挥,就会乱成一团。) 7 * 8 * @author Yan 9 * 10 */ 11 public class Singleton { 12 // 持有私有静态实例,防止被引用,设置为null是为了延迟加载。 13 private static Singleton instance = null; 14 15 // 单例模式中必须将构造方法私有化,禁止外部创建对象。 16 private Singleton() { 17 } 18 19 /** 20 * 这个例子存在问题: 在多线程环境中,可能存在以下情况,线程A判断instance==null;然后在new 21 * Singleton1()之前,线程B进行instance==null的判断, 发现为空,然后执行new 22 * Singleton1();这时,单例模式失效,仍会有多个对象被创建。 23 * 24 * @return 25 */ 26 public static Singleton getInstance1() { 27 if (instance == null) { 28 instance = new Singleton(); 29 } 30 return instance; 31 } 32 33 /** 34 * 为解决多线程问题,尝试对getInstance()方法加锁(Synchronized),但是这种做法在性能上会降低,因为每次调用就会上锁, 35 * 事实上,只要第一次创建对象时枷锁就行了,之后就不需要了。 36 * 37 * @return 38 */ 39 public static synchronized Singleton getInstance2() { 40 if (instance == null) { 41 instance = new Singleton(); 42 } 43 return instance; 44 } 45 46 /** 47 * 首先判断instance是否为空,若为空则进入同步块,再次判断是否为空(若不判断的话,仍会出现多线程的问题,会创建多个实例)。 48 * 49 * @return 50 */ 51 public static Singleton getInstance3() { 52 if (instance == null) { 53 synchronized (Singleton.class) { 54 if (instance == null) { 55 instance = new Singleton(); 56 } 57 } 58 } 59 return instance; 60 } 61 62 /* 63 * 以上代码虽然将synchronized加到了方法内部,提升了效率,但仍然存在问题,java指令创建对象和赋值给引用是分2步进行的, 也就是说 64 * instance=new Singleton();是分2步进行的。即分配实例对象的内存空间赋值给引用变量,和对实例对象进行初始化。 65 * 但是jvm并不保证这两步操作的顺序,也就是说,jvm可能先分配对象的存储空间,然后将其地址赋值给instance引用, 66 * 然后再去初始化这个实例对象。这就可能出现问题。 67 * 68 * 1.A,B线程都进行了第1个if判断; 2.A线程进入了synchronized块,进行第2个if判断instance==null,执行 69 * instance=new Singleton(); 70 * 3.jvm将分配的实例对象的空间地址赋值给instance,但对象暂未被初始化,A离开了synchronized块 71 * 4.B线程进入了synchronized块,进行第2个if判断instance!=null;然后离开synchronized块, 72 * 直接返回instance 73 * 5.此时线程B并不知道instance未被初始化,因此打算直接使用,那么就出现了错误,因为instance所指向的内存空间并未被初始化。 74 * 75 */ 76 77 /** 78 * 对以上问题进一步优化,使用静态内部类解决以上问题。 79 * 在第1次调用getInstance4()时,Singleton对象被创建 80 * 81 */ 82 83 84 }
单例模式的理想实现方法:懒汉式(包括静态成员属性和静态内部类两种)和饿汉式
Lazy initialization holder class模式
这个模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。
1.相应的基础知识
- 什么是类级内部类?
简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。
- 多线程缺省同步锁的知识
大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:
1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
2.访问final字段时
3.在创建线程之前创建对象时
4.线程可以看见它将要处理的对象时
2.解决方案的思路
要想很简单地实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式。但是这样一来,不是会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。
如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。
注意:无论哪种方法,都必须将构造方法私有。
1 //饿汉式单例,但是每次类加载时候就会初始化一个对象,比较浪费资源 2 public class Singleton { 3 private static Singleton instance = new Singleton(); 4 5 private Singleton() { 6 } 7 8 public static Singleton getInstance() { 9 return instance; 10 } 11 } 12 13 /** 14 * 饿汉式的改进,懒加载模式,解决类加载时资源浪费问题,使用静态内部类初始化, 15 * 类加载时候并不会初始化对象,只在第一次调用方法时初始化 16 */ 17 public class Singleton2 { 18 private Singleton2() { 19 } 20 21 private static class InnerSingleton { 22 private static final Singleton2 instance = new Singleton2(); 23 } 24 25 public static Singleton2 getInstance() { 26 return InnerSingleton.instance; 27 } 28 } 29 30 // 懒汉式单例,使用双重检查减少同步代码行数,提高多线程时的性能 31 public class Singleton3 { 32 private volatile static Singleton3 instance = null; 33 34 private Singleton3() { 35 } 36 37 public static Singleton3 getInstance() { 38 if (instance == null) {//双重检查,如果为空进入同步块,否则直接返回实例,提高了多线程的性能 39 synchronized (Singleton3.class) { 40 if (instance == null) { 41 instance = new Singleton3(); 42 } 43 } 44 } 45 return instance; 46 } 47 }
在懒汉式中,静态变量声明时要加volatile的原因?
解释:
1. 因为在程序加载过程中,JVM会对代码进行自动优化,也就是指令重排序,因此可能存在以下问题,在实例化对象时,先分配了内存空间,但并没有立即装入对象,因此在另一个线程访问实例时,判断instance != null 然后直接返回 instance 并使用,就会出现错误。
2. volatile的作用:(1)内存可见性,保证每次读取到的数据都是主内存中最新的数据。(2)禁止指令重排序优化,Java 5 以后的版本可以解决上述问题,以前的版本在实现上存在问题,并不能解决上述问题。
因此,在添加了volatile修饰之后,就能够解决上述问题。
posted on 2016-04-26 17:31 Yanspecial 阅读(685) 评论(0) 编辑 收藏 举报