Java单例模式(Singleton)

来源:互联网 发布:淘宝抠图教程 编辑:程序博客网 时间:2024/05/22 03:43
单例模式有一下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。
  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。目前有五种方式创建单例模式,分别为:懒汉式,饿汉式,双重校验锁(在jdk1.5及以后的版本中,使用这种方式创建单例不存在问题了),枚举和静态内部类。

1、懒汉式:

//懒汉式单例类public class Singleton01 {// 注意,这里没有final// 定义一个变量来存储创建好的类实例// 因为这个变量要在静态方法中使用,所以需要加上static修饰private static Singleton01 single = null;// 私有化构造方法,好在内部控制创建实例的数目private Singleton01() {}// 静态工厂方法,为客户端提供类实例public synchronized static Singleton01 getInstance() {// 判断存储实例的变量是否有值if (single == null) {// 如果没有,就创建一个类实例,并把值赋值给存储类实例的变量single = new Singleton01();}return single;}}

2、饿汉式:

public class Singleton02 {// 定义一个静态变量来存储创建好的类实例// 直接在这里创建类实例,只会创建一次private static final Singleton02 single = new Singleton02();private Singleton02() {}public static Singleton02 getInstance() {return single;}}
关于饿汉式和懒汉式的名称说明:

       所谓饿汉式,既然饿,那么在创建单例对象实例的时候就比较着急,饿了嘛,于是就在装载类的时候就创建单例对象实例;所谓懒汉式,既然懒,那么创建单例的对象实例的时候就不着急,会一直等到即将要使用单例对象实例的时候才会创建,懒人嘛,总会推脱不开的时候才采取真正执行工作,因此在装载对象的时候不创建对象实例。饿汉式是线程安全的,因为虚拟机保证只装载一次,在装载类的时候是不会发生并发的。懒汉式有一个弊端,那就是每次调用这个getInstance()方法的时候都需要同步,影响性能。

3、双重校验锁
public class Singleton03 {private volatile static Singleton03 INSTANCE;// 由于有了volatile,所以双边检查机制不存在问题了private Singleton03() {}public static Singleton03 getInstance() {if (INSTANCE == null) {synchronized (Singleton03.class) {if (INSTANCE == null) {INSTANCE = new Singleton03();}}}return INSTANCE;}}

      双重检查加锁值得就是:并不是每次进入getInstance()方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查视力是否存在,如果不存在,就在同步的情况下创建一个实例,这就是第二重检查。这样一来,只需要同步一次即可(因为同步完必定能够创建一个实例,后面instance就不为null了),从而减少了多次在同步情况下进行判断所浪费的时间。双重检查加锁机制的实现会使用一个关键字volatile,他的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理变量。如果不加volatile会出现问题,参见文章《单例模式中的双重检查机制》。

多线程缺省同步锁的知识
      在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况下,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:
     1)由静态初始化器(在静态字段上或者static{}块中的初始化器)初始化数据时;
     2)访问final字段时:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this引用传递进去,那么在其他线程中就能看见final字段的值,无需同步就可以被其他线程正确访问。
     3)在创建线程之前创建对象时;
     4)线程可以看见它将要处理的对象时。

4、静态内部类:
public class Singleton04 {// 私有化构造方法private Singleton04() {}/** * 静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系, 而且只有被调用到才会装载,从而实现了延迟加载 */private static class SingletonHolder {// 静态初始化器,由JVM来保证线程安全,该静态内部类在调用的时候才初始化private static Singleton04 INSTANCE = new Singleton04();}public static Singleton04 getInstance() {return SingletonHolder.INSTANCE;}}

类级内部类的相关知识
    1)类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类称为对象级内部类。
    2)类级内部类相当于外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。(java static解析)
    3)类级内部类中,可定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者静态成员变量。
    4)类级内部类相当于外部类的成员,只有在第一次被使用的时候才会被装载。

     说明:以上四种方式在构建单例模式时,如果需要序列化该实例,需要做以下几点工作:①该类需要实现Serializable接口;②所有的实例域都必须是瞬时的(单例模式的类中可以存在其他的实例域);③该类必须提供一个readResolve方法,不然解析回来之后的对象和序列化的对象不是同一个,该方法如下:

private Object readResolve() throws ObjectStreamException {return ELVIS;}

5、枚举:
public enum Singleton04 {INSTANCE;public void leavTheBuilding(){...}}
public enum Singleton05 {INSTANCE("jimmy", 1);private int id;private String name;private Singleton05(String name, int id) {this.name = name;this.id = id;}public static Singleton05 getInstance() {return INSTANCE;}@Overridepublic String toString() {return "name:" + name + " id:" + id;}}
public class Singleton05Test {public static void main(String[] args) {System.out.println(Singleton05.INSTANCE);System.out.println(Singleton05.INSTANCE);}}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,即使是在面对复杂的序列化或者是反射攻击的时候。
原创粉丝点击