Java 单例模式几种实现的差别

来源:互联网 发布:unix环境高级编程源码 编辑:程序博客网 时间:2024/06/05 18:52

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

思路是通过将该类的构造方法设为private,那么在其他类中不能直接实例化该类。那么为了得到该类的实例,需要有public的static方法来返回该类的实例。(之所以static是为了可以直接用类名来调用该方法,不然的话没有该类的实例,无法调用该类的非static方法)

单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

单例模式主要有以下三种实现方式:懒汉式单例、饿汉式单例、登记式单例。下面来看代码示例:

一、懒汉式单例

//懒汉式单例类.在第一次调用的时候实例化自己 public class Singleton {    private Singleton() {}    private static Singleton single=null;    //静态工厂方法     public static Singleton getInstance() {         if (single == null) {               single = new Singleton();         }          return single;    }}

懒汉式其实是一种比较形象的称谓。既然懒,那么在创建对象实例的时候就不着急。会一直等到马上要使用对象实例的时候才会创建,懒人嘛,总是推脱不开的时候才会真正去执行工作,因此在装载对象的时候不创建对象实例。懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。


二、饿汉式单例

//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton1 {    private Singleton1() {}    private static final Singleton1 single = new Singleton1();    //静态工厂方法     public static Singleton1 getInstance() {        return single;    }}

饿汉式也是一种比较形象的称谓。既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例。饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。


三、登记式单例

//类似Spring里面的方法,将类名注册,下次从里面直接获取。public class Singleton3 {    private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();    static{        Singleton3 single = new Singleton3();        map.put(single.getClass().getName(), single);    }    //保护的默认构造子    protected Singleton3(){}    //静态工厂方法,返还此类惟一的实例    public static Singleton3 getInstance(String name) {        if(name == null) {            name = Singleton3.class.getName();            System.out.println("name == null"+"--->name="+name);        }        if(map.get(name) == null) {            try {                map.put(name, (Singleton3) Class.forName(name).newInstance());            } catch (InstantiationException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            } catch (ClassNotFoundException e) {                e.printStackTrace();            }        }        return map.get(name);    }    //一个示意性的商业方法    public String about() {            return "Hello, I am RegSingleton.";        }        public static void main(String[] args) {        Singleton3 single3 = Singleton3.getInstance(null);        System.out.println(single3.about());    }}
 登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。 


线程安全问题

上文中懒汉式单例的代码是线程不安全的。为了线程安全,可以采用以下方法:

1、在getInstance方法上加synchronized

public static synchronized Singleton getInstance() {         if (single == null) {               single = new Singleton();         }          return single;}
2、双重检查锁定

public static Singleton getInstance() {        if (singleton == null) {              synchronized (Singleton.class) {                 if (singleton == null) {                    singleton = new Singleton();                }              }          }          return singleton;     }
3、静态内部类

public class Singleton {        private Singleton(){}    /**     *    类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例     *    没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。     */    private static class SingletonHolder{        /**         * 静态初始化器,由JVM来保证线程安全         */        private static Singleton instance = new Singleton();    }        public static Singleton getInstance(){        return SingletonHolder.instance;    }}

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

另外就静态内部类中的三个static说明如下:

public static Singleton getInstance(){return SingletonHolder.instance;}

中的static是为了可以用类名直接调用getInstance方法,所以必须为static。那么该方法内的成员变量也必须为static,否则那个成员变量就要用类的实例来访问了,这显然和通过类直接访问不相符。

private static Singleton instance = new Singleton();

中的static是因为instance必须为static才可以在getInstance方法中出现。

public static Singleton getInstance()

中的static是因为若内部类中出现静态成员变量,则该内部类必为静态内部类。


最后来看一个单例模式应用的例子有助于理解:

public class TMain{public static void main(String[] args){Singleton s1=Singleton.getInstance();s1.setName("libai");Singleton s2=Singleton.getInstance();s2.setName("dufu");System.out.println(s1.getName());System.out.println(s2.getName());if(s1==s2){System.out.println("是同一个对象");}else{System.out.println("不是同一个对象");}}}class Singleton{    /* 私有构造方法,防止被实例化 */      private Singleton() {      }      private String name;    public void setName(String s){    this.name=s;    }    public String getName(){    return this.name;    }        /*饿汉式单例*/    private static final Singleton instance = new Singleton();          /* 获取实例 */      public static Singleton getInstance() {      //return new Singleton();    return instance;     }      /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */      public Object readResolve() {          return getInstance();      }  }

输出为:

dufudufu是同一个对象



0 0