单例模式

作用


保证一个类只有一个实例,并且向外提供一个访问点。

应用场景


windows中的任务管理器就是一个很典型的单例模式。

读取配置文件的类一般只new一个对象,没必要在每次读取配置文件时重新new一个对象。

网站计数器也采用单例模式,否则很难同步

应用程序的日志文件通常只用一个对象来维护

数据库链接池的设计也采用单例模式。

文件系统也是一个单例模式,一个操作系统只能有一个文件系统。

在Spring中,每个Bean默认是单例的,这样Spring容器易于管理。

javaweb中的每个Servlet都是一个单例

优点


 

因为只产生一个实例,所以减少了系统开销。可以在系统设置全局访问点,优化共享资源访问。

分类


主要  

  • 饿汉式(线程安全,调用效率高,但不能延时加载)

  • 懒汉式(线程安全,调用效率不高,可以延时加载)

其它

  • 双重检测锁式 (由于JVM的原因,有时会出现问题)
  • 静态内部类式(线程安全,调用效率高,可以延时加载)
  • 枚举单例(线程安全,调用效率不高,不能延时加载)

 举例


  • 饿汉式

一上来就创建对象,立即加载,存在的问题就这个对象可能会一直用不到,白白浪费内存资源

package com.dy.xidian;

public class SingleDemo1 {
    //类初始化是立即加载对象
    //jvm只会将类加载一次,在加载类时就创建了一个实例
    private static SingleDemo1 instance = new SingleDemo1();
    //构造器私有化,不能被new对象
    private SingleDemo1(){
    }
    
    //对外提供一个访问接口,因为只有一个实例存在,所以线程安全
    public static SingleDemo1 getInstance(){return instance;}
}
  • 懒汉式

用时才创建对象,延时加载。因为加入了同步机制,所以调用效率不高

package com.dy.xidian;

public class SingleDemo2 {
    private static SingleDemo2 instance;
    private SingleDemo2() {

    }
    // 在调用的时候才会实例化一个对象
    // 存在竞态条件,应进行同步
    public static synchronized SingleDemo2 getInstance() {
        if (instance == null)
            instance = new SingleDemo2();
        return instance;
    }
}

 

  •  双重检测锁实现

将同步块放到了方法的内部,这样不仅能延时加载,而且提高的调用效率。但是由于编译器优化以及JVM底层内部模型原因,偶而会出现问题,不建议使用package com.dy.xidian;public class SingleDemo3     private static SingleDemo3 instance;

private SingleDemo3() {
    }

    public static SingleDemo3 getInstance() {
        if (instance == null) {
      synchronized(SingleDemo3.class) {
        if (instance == null)
          instance = new SingleDemo3();
      }
    }
    return instance;
} }

 

  • 静态内部类模式

JVM初始化类SingleDemo4时并不会去初始话其内部内,所以避免了向饿汉式那样立即加载对象。

只有真正调用getInstance(),才会加载内部类。加载类时是线程安全的,而final能保证内存中只有这样一个实例存在,兼备了高效调用与延迟加载的优势。

package com.dy.xidian;

public class SingleDome4 {
    private SingleDome4(){}
    
    private static class SingleClassInstance{
        private static final SingleDome4 instance = new SingleDome4();
    }
    
    public static SingleDome4 getInstance(){
        return SingleClassInstance.instance;
    }
}
  • 枚举方式

枚举类本来就是单例的,但是它不能延时加载

package com.dy.xidian;

public enum SingleDome4 {
    INSTANCE;
    String a = new String();
    // 处理方法
    public static void main(String[] args) {
        System.out.println(SingleDome4.INSTANCE);
        SingleDome4.INSTANCE.a = "ff";
        System.out.println(SingleDome4.INSTANCE.a);
    }
}

 存在问题


单例模式的关键点就是构造器私有化,但是通过反射机制将构造器变成可被外部调用,这样的话单例就会被破坏,对于这种情况解决方案如下。

package com.dy.xidian;

public class SingleDome4 {
    private static SingleDome4 instance;
        //在构造器中抛出异常
    private SingleDome4() {
        if(instance != null )
            throw new RuntimeException();
    }

    public synchronized SingleDome4 getInstance() {
        if (instance == null)
            instance = new SingleDome4();
        return instance;
    }
}

我们可以通过反序列化的方式产生多个的对象,如果一个单例对象被多次反序列化,那么该对象不再是单例的了,解决方案如下:

package com.dy.xidian;

import java.io.ObjectStreamException;
import java.io.Serializable;

public class SingleDome4 implements Serializable{
    private static SingleDome4 instance;
    private SingleDome4() {
        if(instance != null )
            throw new RuntimeException();
    }

    public synchronized SingleDome4 getInstance() {
        if (instance == null)
            instance = new SingleDome4();
        return instance;
    }
    
    //基于回调的,在反序列化时该方法会直接被调用,而不是去创建一个新对象
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
}

  

 

posted @ 2016-06-28 00:25  被罚站的树  阅读(275)  评论(0编辑  收藏  举报