zoukankan      html  css  js  c++  java
  • 单例模式的懒汉式和饿汉式实现分析

     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修饰之后,就能够解决上述问题。

  • 相关阅读:
    zyUpload+struct2完成文件上传
    js表单动态添加数据并提交
    Hibernate注解
    ueditor的配置和使用
    设计模式
    静态Include和动态Include测试并总结
    java笔试题
    perf使用示例1
    perf 简介
    ss简单使用
  • 原文地址:https://www.cnblogs.com/yanspecial/p/5435910.html
Copyright © 2011-2022 走看看