单例模式

来源:互联网 发布:复杂网络同步 编辑:程序博客网 时间:2024/05/16 13:45

      单例模式有以下特点:
  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



0 0
原创粉丝点击