单例模式的懒汉式和饿汉式实现分析

 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编辑  收藏  举报