您的位置:首页 > 其它

单例模式

2016-07-07 21:18 357 查看
      单例模式有以下特点:

  1、单例类只能有一个实例。

  2、单例类必须自己创建自己的唯一实例。

  3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。总之,选择单例模式就是为了避免不一致状态。
    双重检测机制

public class SingletonClass { 
  private static volatile SingletonClass instance = null; 
  public static SingletonClass getInstance() { 
    if (instance == null) //先检查实例是否存在,如果不存在才进入下面的同步块

       { //同步块,线程安全的创建实例
      synchronized (SingletonClass.class) { 
        if (instance == null) { 

//这里要进一步检查是否为null,因为两个线程都可能穿过第一个判断为空的条件,

 当一个线程创建好,另一个线程苏醒过来,再次进入同步块,需要进一步进行判定!
          instance = new SingletonClass(); 

         } 
      } 
    } 
    return instance; 
  } 
  private SingletonClass() { } 
}

在某些情况下,JVM已经隐含的为您执行了同步,这些情况下就不用自己再来进行同步控制了。
 *   这些情况包括:
 *   (1)由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
 *   (2)访问final字段时
 *   (3)在创建线程之前创建对象时
 *   (4)线程可以看见它将要处理的对象时

解决方案的思路
 *        要想很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的
 *   安全性。比如前面的饿汉式实现方式。但是这样一来,不是会浪费一定的空间吗?因为这种
 *   实现方式,会在类装载的时候就初始化对象,不管你需不需要。
 *        如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的
 *   方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,
 *   那就不会创建对象实例,从而同步实现延迟加载和线程安全。

静态初始化器是由static修饰的一对花括号“{}”括起来的语句组。它的作用和构造方法有待你相似,都是用来完成初始化工作的,但是静态初始化器与构造方法有以下几点根本不同。  a、构造方法是对每一个新创建的对象初始化,而静态方法是对类自身进行初始化。  b、构造方法是在new运算符创建新对象的时候由系统执行,而静态初始化器一般不能由程序调用,它是在所属类被加载入内存时由系统调用执行的。  c、用new运算符创建多少个新的对象,构造方法就被调用那个多少次,但是静态初始化器则是在被类加载入内存时只执行一次,与创建多少个对象无关。 2、如果有多个静态初始化器,则它们在类的初始化时会依次执行。 3、类是在第一次被使用的时候才被装载,而不是在程序启动时就装载程序中得所有可能用到的类。 4、静态初始化器的作用是对整个类完成初始化操作,包括给static成员变量赋初值,它在系统向内存加载时自动完成。


public class SingletonClass { 
  private static class SingletonClassInstance { 
    private static  SingletonClass instance = new SingletonClass(); 
  } 
  public static SingletonClass getInstance() { 
    return SingletonClassInstance.instance; 
  } 
  private SingletonClass() { } 
} 
在这段代码中,因为SingletonClass没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例,没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
饿汉式单例模式1.     //饿汉式单例类.在类初始化时,已经自行实例化   2.     public class Singleton1 {  4.         private Singleton1() {}  5.         //已经自行实例化   6.         private static final Singleton1 single = new Singleton1();  7.         //静态工厂方法   8.         public static Singleton1 getInstance() {  9.             return single;  10.     }  11. }  

饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
登记式单例实际上维护了一组单例类的实例。

线程安全:

饿汉式是线程安全的,可以直接用于多线程而不会出现问题,懒汉式就不行,它是线程不安全的,如果用于多线程可能会被实例化多次,失去单例的作用。

如果要把懒汉式用于多线程,有两种方式保证安全性,一种是在getInstance方法上加同步,另一种是在使用该单例方法前后加双锁。

资源加载:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,会占据一定的内存,相应的在调用时速度也会更快,而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次掉用时要初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。

a. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。

b. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。

什么是类级内部类?

简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。

类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。

类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。

类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。

在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:

由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时

访问final字段时

在创建线程之前创建对象时

线程可以看见它将要处理的对象时



编写一个包含单个元素的枚举类型[极推荐]。代码如下:

public enum MaYun {

 himself; //定义一个枚举的元素,就代表MaYun的一个实例 

private String anotherField; 

MaYun() { //MaYun诞生要做的事情 

//这个方法也可以去掉。将构造时候需要做的事情放在instance赋值的时候: 

/** himself = MaYun() { * //MaYun诞生要做的事情 * } **/ }

 public void splitAlipay() { System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”); 





Call:MaYun.himself.splitAlipay();

Feature:从Java1.5开始支持;无偿提供序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。

有几个原因关于为什么在Java中宁愿使用一个枚举量来实现单例模式:

       1、 自由序列化;

       2、 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);

       3、 线程安全;

参考:http://callmegod.iteye.com/blog/1474441








                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: